| %% @doc A reporter that sends the spans to an jaeger collector using HTTP. |
| %% |
| %% To start a reporter process, please use {@link jaeger_passage_reporter:start/1} or {@link jaeger_passage_reporter:start/2}. |
| %% |
| %% |
| %% === Examples === |
| %% |
| %% ``` |
| %% %% Starts `example_reporter' |
| %% {ok, Reporter} = jaeger_passage_reporter:start(example_reporter, [{protocol, http}]). |
| %% [example_reporter] = jaeger_passage_reporter:which_reporters(). |
| %% |
| %% %% Registers `example_tracer' |
| %% Context = jaeger_passage_span_context. |
| %% Sampler = passage_sampler_all:new(). |
| %% ok = passage_tracer_registry:register(example_tracer, Context, Sampler, Reporter). |
| %% |
| %% %% Starts and finishes a span |
| %% Span = passage:start_span(example, [{tracer, example_tracer}]). |
| %% |
| %% passage:finish_span(Span). % The span will send to the jaeger collector on the localhost |
| %% ''' |
| -module(jaeger_passage_reporter_http). |
| |
| -behaviour(gen_server). |
| |
| -include("constants.hrl"). |
| |
| %%------------------------------------------------------------------------------ |
| %% Exported API |
| %%------------------------------------------------------------------------------ |
| -export([httpc_client/5]). |
| |
| -export_type([start_option/0, start_options/0]). |
| |
| %%------------------------------------------------------------------------------ |
| %% Application Internal API |
| %%------------------------------------------------------------------------------ |
| -export([start_link/2]). |
| |
| %%------------------------------------------------------------------------------ |
| %% 'gen_server' Callback API |
| %%------------------------------------------------------------------------------ |
| -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). |
| |
| %%------------------------------------------------------------------------------ |
| %% Macros & Records |
| %%------------------------------------------------------------------------------ |
| -define(STATE, ?MODULE). |
| |
| -record(?STATE, |
| { |
| endpoint :: string(), |
| options :: start_options(), |
| http_client :: http_client(), |
| default_service_name :: atom(), |
| process_tags :: passage:tags() |
| }). |
| |
| -define(CONTENT_TYPE, {"Content-Type", "application/x-thrift"}). |
| |
| %%------------------------------------------------------------------------------ |
| %% Exported Types |
| %%------------------------------------------------------------------------------ |
| |
| -type start_options() :: [start_option()]. |
| %% Options for {@link jaeger_passage_reporter:start/2}. |
| |
| -type start_option() :: {default_service_name, atom()} |
| | {process_tags, passage:tags()} |
| | {endpoint, string()} |
| | {http_client, http_client()} |
| | (HttpClientSpecificOption :: any()). |
| %% <ul> |
| %% <li><b>default_service_name</b>: The default service name. If a reporting span has `location.application' tag, the value is used as the service name instead of this. The default value is `ReporterId'.</li> |
| %% <li><b>process_tags</b>: The tags of the reporting process. The default value is `#{}'.</li> |
| %% <li><b>endpoint</b>: The jaeger endpoint URL for sending thrift messages. The default value is `http://127.0.0.1:14268'.</li> |
| %% <li><b>http_client</b>: The callback to call to send span to jaeger. {@link httpc_client/5} is used by default.</li> |
| %% </ul> |
| %% |
| %% Example of a `http_client' calback: |
| %% ``` |
| %% Client = fun(Url, Method, Headers, Body, ReporterOptions) -> |
| %% User = proplists:get_value(user, ReporterOptions), |
| %% Password = proplists:get_value(password, ReporterOptions), |
| %% ibrowse:send_req(Url, Headers, Method, Body, [{basic_auth, {User, Password}}]) |
| %% end. |
| %% ''' |
| |
| -type http_client() :: fun(( |
| Url :: string(), |
| Method :: post, |
| Headers :: [{string(), string()}], |
| Body :: string() | binary(), |
| ReporterOptions :: start_options()) -> |
| ok). |
| %% Type of HTTP client. |
| |
| %%------------------------------------------------------------------------------ |
| %% Exported Functions |
| %%------------------------------------------------------------------------------ |
| %% @doc The default HTTP client based on the standard `httpc' module. |
| httpc_client(Url, Method, _Headers, Body, _ReporterOptions) -> |
| httpc:request(Method, {Url, [], "application/x-thrift", Body}, [], []), |
| ok. |
| |
| %%------------------------------------------------------------------------------ |
| %% Application Internal Functions |
| %%------------------------------------------------------------------------------ |
| %% @private |
| -spec start_link(jaeger_passage_reporter:reporter_id(), start_options()) -> {ok, pid()} | {error, Reason} when |
| Reason :: {already_started, pid()} | term(). |
| start_link(ReporterId, Options) -> |
| Name = jaeger_passage_local_ns:reporter_name(ReporterId), |
| gen_server:start_link(Name, ?MODULE, {ReporterId, Options}, []). |
| |
| %%------------------------------------------------------------------------------ |
| %% 'gen_server' Callback Functions |
| %%------------------------------------------------------------------------------ |
| %% @private |
| init({ReporterId, Options}) -> |
| Endpoint = proplists:get_value(endpoint, Options, "http://127.0.0.1:14268"), |
| EndpointURL = Endpoint ++ "/api/traces", |
| |
| HttpClient = proplists:get_value(http_client, Options, fun httpc_client/5), |
| is_function(HttpClient, 5) orelse error(badarg, [ReporterId, Options]), |
| |
| DefaultServiceName = proplists:get_value(default_service_name, Options, ReporterId), |
| Tags0 = proplists:get_value(process_tags, Options, #{}), |
| |
| {ok, Hostname} = inet:gethostname(), |
| {ok, Version} = application:get_key(vsn), |
| Tags1 = |
| maps:merge( |
| Tags0, |
| #{ |
| ?JAEGER_CLIENT_VERSION_TAG_KEY => list_to_binary(["jaeger_passage-", Version]), |
| ?TRACER_HOSTNAME_TAG_KEY => list_to_binary(Hostname), |
| 'erlang.node' => node() |
| }), |
| State = |
| #?STATE{ |
| endpoint = EndpointURL, |
| http_client = HttpClient, |
| options = Options, |
| default_service_name = DefaultServiceName, |
| process_tags = Tags1 |
| }, |
| {ok, State}. |
| |
| %% @private |
| handle_call(_Request, _From, State) -> |
| {noreply, State}. |
| |
| %% @private |
| handle_cast({report, Span}, State) -> |
| handle_report(Span, State); |
| handle_cast(_Request, State) -> |
| {noreply, State}. |
| |
| %% @private |
| handle_info(_Info, State) -> |
| {noreply, State}. |
| |
| %% @private |
| terminate(_Reason, _State) -> |
| ok. |
| |
| %% @private |
| code_change(_OldVsn, State, _Extra) -> |
| {ok, State}. |
| |
| %%------------------------------------------------------------------------------ |
| %% Internal Functions |
| %%------------------------------------------------------------------------------ |
| -spec handle_report(passage_span:span(), #?STATE{}) -> {noreply, #?STATE{}}. |
| handle_report(Span, State = #?STATE{default_service_name = DefaultName, process_tags = Tags, endpoint = URI, http_client = HttpClient, options = Options}) -> |
| Name = maps:get('location.application', passage_span:get_tags(Span), DefaultName), |
| Message = jaeger_passage_thrift:make_batch(Name, Tags, [Span]), |
| Encoded = thrift_protocol:encode_struct(Message, binary), |
| Headers = [?CONTENT_TYPE], |
| HttpClient(URI, post, Headers, Encoded, Options), |
| {noreply, State}. |