emqx/apps/emqx_opentelemetry/src/emqx_otel_config.erl

199 lines
6.3 KiB
Erlang

%%--------------------------------------------------------------------
%% Copyright (c) 2020-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_otel_config).
-behaviour(emqx_config_handler).
-include_lib("emqx/include/logger.hrl").
-define(OPTL, [opentelemetry]).
-define(CERTS_PATH, filename:join(["opentelemetry", "exporter"])).
-define(OTEL_EXPORTER, opentelemetry_exporter).
-define(OTEL_LOG_HANDLER, otel_log_handler).
-define(OTEL_LOG_HANDLER_ID, opentelemetry_handler).
-export([add_handler/0, remove_handler/0]).
-export([pre_config_update/3, post_config_update/5]).
-export([update/1]).
-export([add_otel_log_handler/0, remove_otel_log_handler/0]).
-export([otel_exporter/1]).
update(Config) ->
case
emqx_conf:update(
?OPTL,
Config,
#{rawconf_with_defaults => true, override_to => cluster}
)
of
{ok, #{raw_config := NewConfigRows}} ->
{ok, NewConfigRows};
{error, Reason} ->
{error, Reason}
end.
add_handler() ->
ok = emqx_config_handler:add_handler(?OPTL, ?MODULE),
ok.
remove_handler() ->
ok = emqx_config_handler:remove_handler(?OPTL),
ok.
pre_config_update(?OPTL, RawConf, RawConf) ->
{ok, RawConf};
pre_config_update(?OPTL, NewRawConf, _RawConf) ->
{ok, convert_certs(NewRawConf)}.
post_config_update(?OPTL, _Req, Old, Old, _AppEnvs) ->
ok;
post_config_update(?OPTL, _Req, New, Old, AppEnvs) ->
application:set_env(AppEnvs),
MetricsRes = ensure_otel_metrics(New, Old),
LogsRes = ensure_otel_logs(New, Old),
TracesRes = ensure_otel_traces(New, Old),
case {MetricsRes, LogsRes, TracesRes} of
{ok, ok, ok} -> ok;
Other -> {error, Other}
end;
post_config_update(_ConfPath, _Req, _NewConf, _OldConf, _AppEnvs) ->
ok.
add_otel_log_handler() ->
ensure_otel_logs(emqx:get_config(?OPTL), #{}).
remove_otel_log_handler() ->
remove_handler_if_present(?OTEL_LOG_HANDLER_ID).
otel_exporter(ExporterConf) ->
#{
endpoint := Endpoint,
protocol := Proto,
ssl_options := SSLOpts
} = ExporterConf,
{?OTEL_EXPORTER, #{
endpoint => Endpoint,
protocol => Proto,
ssl_options => ssl_opts(Endpoint, SSLOpts)
}}.
%% Internal functions
convert_certs(#{<<"exporter">> := ExporterConf} = NewRawConf) ->
NewRawConf#{<<"exporter">> => convert_exporter_certs(ExporterConf)};
convert_certs(#{exporter := ExporterConf} = NewRawConf) ->
NewRawConf#{exporter => convert_exporter_certs(ExporterConf)};
convert_certs(NewRawConf) ->
NewRawConf.
convert_exporter_certs(#{<<"ssl_options">> := SSLOpts} = ExporterConf) ->
ExporterConf#{<<"ssl_options">> => do_convert_certs(SSLOpts)};
convert_exporter_certs(#{ssl_options := SSLOpts} = ExporterConf) ->
ExporterConf#{ssl_options => do_convert_certs(SSLOpts)};
convert_exporter_certs(ExporterConf) ->
ExporterConf.
do_convert_certs(SSLOpts) ->
case emqx_tls_lib:ensure_ssl_files(?CERTS_PATH, SSLOpts) of
{ok, undefined} ->
SSLOpts;
{ok, SSLOpts1} ->
SSLOpts1;
{error, Reason} ->
?SLOG(error, Reason#{msg => "bad_ssl_config", name => "opentelemetry_exporter"}),
throw({bad_ssl_config, Reason})
end.
ensure_otel_metrics(
#{metrics := MetricsConf, exporter := Exporter},
#{metrics := MetricsConf, exporter := Exporter}
) ->
ok;
ensure_otel_metrics(#{metrics := #{enable := true}} = Conf, _Old) ->
ok = emqx_otel_cpu_sup:stop_otel_cpu_sup(),
_ = emqx_otel_cpu_sup:start_otel_cpu_sup(Conf),
_ = emqx_otel_metrics:stop_otel(),
emqx_otel_metrics:start_otel(Conf);
ensure_otel_metrics(#{metrics := #{enable := false}}, _Old) ->
ok = emqx_otel_cpu_sup:stop_otel_cpu_sup(),
emqx_otel_metrics:stop_otel();
ensure_otel_metrics(_, _) ->
ok.
ensure_otel_logs(
#{logs := LogsConf, exporter := Exporter},
#{logs := LogsConf, exporter := Exporter}
) ->
ok;
ensure_otel_logs(#{logs := #{enable := true}} = Conf, _OldConf) ->
ok = remove_handler_if_present(?OTEL_LOG_HANDLER_ID),
HandlerConf = tr_handler_conf(Conf),
%% NOTE: should primary logger level be updated if it's higher than otel log level?
logger:add_handler(?OTEL_LOG_HANDLER_ID, ?OTEL_LOG_HANDLER, HandlerConf);
ensure_otel_logs(#{logs := #{enable := false}}, _OldConf) ->
remove_handler_if_present(?OTEL_LOG_HANDLER_ID).
ensure_otel_traces(
#{traces := TracesConf, exporter := Exporter},
#{traces := TracesConf, exporter := Exporter}
) ->
ok;
ensure_otel_traces(#{traces := #{enable := true}} = Conf, _OldConf) ->
_ = emqx_otel_trace:stop(),
emqx_otel_trace:start(Conf);
ensure_otel_traces(#{traces := #{enable := false}}, _OldConf) ->
emqx_otel_trace:stop().
remove_handler_if_present(HandlerId) ->
case logger:get_handler_config(HandlerId) of
{ok, _} ->
ok = logger:remove_handler(HandlerId);
_ ->
ok
end.
tr_handler_conf(#{logs := LogsConf, exporter := ExporterConf}) ->
#{
level := Level,
max_queue_size := MaxQueueSize,
exporting_timeout := ExportingTimeout,
scheduled_delay := ScheduledDelay
} = LogsConf,
#{
level => Level,
config => #{
max_queue_size => MaxQueueSize,
exporting_timeout_ms => ExportingTimeout,
scheduled_delay_ms => ScheduledDelay,
exporter => otel_exporter(ExporterConf)
}
}.
ssl_opts(Endpoint, SSLOpts) ->
case is_ssl(Endpoint) of
true ->
%% force enable ssl
emqx_tls_lib:to_client_opts(SSLOpts#{enable => true});
false ->
[]
end.
is_ssl(<<"https://", _/binary>>) ->
true;
is_ssl(<<"http://", _/binary>>) ->
false.