opentelemetry-erlang-contrib/instrumentation/opentelemetry_elli/src/otel_elli.erl

114 lines
4.0 KiB
Erlang

-module(otel_elli).
-export([start_span/1]).
-include_lib("opentelemetry_api/include/otel_tracer.hrl").
-include_lib("opentelemetry_api/include/opentelemetry.hrl").
-include_lib("elli/include/elli.hrl").
start_span(Req) ->
Method = elli_request:method(Req),
RawPath = elli_request:raw_path(Req),
%% TODO: fix in elli. it currently always returns `undefined'
%% Scheme = elli_request:scheme(Req),
%% TODO: update elli to keep the whole url
%% Url = elli_request:url(Req),
BinMethod = to_binary(Method),
SpanName = <<"HTTP ", BinMethod/binary>>,
UserAgent = elli_request:get_header(<<"User-Agent">>, Req, <<>>),
Host = case elli_request:host(Req) of
undefined ->
elli_request:get_header(<<"Host">>, Req, <<>>);
H ->
H
end,
%% TODO: attribute `http.route' should be an option to this function
{PeerIp, PeerPort} = peer_ip_and_port(Req),
{HostIp, HostPort} = host_ip_and_port(Req),
{ok, HostName} = inet:gethostname(),
Flavor = flavor(Req),
SpanCtx = ?start_span(SpanName, #{kind => ?SPAN_KIND_SERVER,
attributes => [{<<"http.target">>, RawPath},
{<<"http.host">>, Host},
%% {<<"http.url">>, Url},
%% {<<"http.scheme">>, Scheme},
{<<"http.flavor">>, Flavor},
{<<"http.user_agent">>, UserAgent},
{<<"http.method">>, BinMethod},
{<<"net.peer.ip">>, PeerIp},
{<<"net.peer.port">>, PeerPort},
{<<"net.peer.name">>, Host},
{<<"net.transport">>, <<"IP.TCP">>},
{<<"net.host.ip">>, HostIp},
{<<"net.host.port">>, HostPort},
{<<"net.host.name">>, HostName}
| optional_attributes(Req)]}),
?set_current_span(SpanCtx),
ok.
flavor(#req{version={1,1}}) ->
<<"1.1">>;
flavor(#req{version={1,0}}) ->
<<"1.0">>;
flavor(_) ->
<<>>.
to_binary(Method) when is_atom(Method) ->
atom_to_binary(Method, utf8);
to_binary(Method) ->
Method.
optional_attributes(Req) ->
lists:filtermap(fun({Attr, Fun}) ->
case Fun(Req) of
undefined ->
false;
Value ->
{true, {Attr, Value}}
end
end, [{<<"http.client_ip">>, fun client_ip/1},
{<<"http.server_name">>, fun server_name/1}]).
client_ip(Req) ->
case elli_request:get_header(<<"X-Forwarded-For">>, Req, undefined) of
undefined ->
undefined;
Ip ->
Ip
end.
server_name(_) ->
application:get_env(opentelemetry_elli, server_name, undefined).
peername({plain, Socket}) ->
inet:peername(Socket);
peername({ssl, Socket}) ->
ssl:peername(Socket).
sockname({plain, Socket}) ->
inet:sockname(Socket);
sockname({ssl, Socket}) ->
ssl:sockname(Socket).
peer_ip_and_port(#req{socket=Socket}) ->
case peername(Socket) of
{ok, {Address, Port}} ->
{list_to_binary(inet_parse:ntoa(Address)), Port};
_ ->
undefined
end.
host_ip_and_port(#req{socket=Socket}) ->
case sockname(Socket) of
{ok, {Address, Port}} ->
{list_to_binary(inet_parse:ntoa(Address)), Port};
_ ->
undefined
end.