114 lines
4.0 KiB
Erlang
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.
|