opentelemetry-erlang-contrib/instrumentation/opentelemetry_cowboy/src/opentelemetry_cowboy.erl

142 lines
5.6 KiB
Erlang
Raw Normal View History

-module(opentelemetry_cowboy).
-export([
setup/0,
setup/1,
handle_event/4]).
-include_lib("opentelemetry_api/include/opentelemetry.hrl").
-define(TRACER_ID, ?MODULE).
-spec setup() -> ok.
setup() ->
setup([]).
-spec setup([]) -> ok.
setup(_Opts) ->
attach_event_handlers(),
ok.
attach_event_handlers() ->
Events = [
[cowboy, request, early_error],
[cowboy, request, start],
[cowboy, request, stop],
[cowboy, request, exception]
],
telemetry:attach_many(opentelemetry_cowboy_handlers, Events, fun ?MODULE:handle_event/4, #{}).
handle_event([cowboy, request, start], _Measurements, #{req := Req} = Meta, _Config) ->
Headers = maps:get(headers, Req),
2021-10-14 03:11:26 +00:00
otel_propagator_text_map:extract(maps:to_list(Headers)),
{RemoteIP, _Port} = maps:get(peer, Req),
Method = maps:get(method, Req),
Attributes = #{
'http.client_ip' => client_ip(Headers, RemoteIP),
'http.flavor' => http_flavor(Req),
'http.host' => maps:get(host, Req),
'http.host.port' => maps:get(port, Req),
'http.method' => Method,
'http.scheme' => maps:get(scheme, Req),
'http.target' => maps:get(path, Req),
'http.user_agent' => maps:get(<<"user-agent">>, Headers, <<"">>),
'net.host.ip' => iolist_to_binary(inet:ntoa(RemoteIP)),
'net.transport' => 'IP.TCP'
},
SpanName = iolist_to_binary([<<"HTTP ">>, Method]),
Opts = #{attributes => Attributes, kind => ?SPAN_KIND_SERVER},
otel_telemetry:start_telemetry_span(?TRACER_ID, SpanName, Meta, Opts);
handle_event([cowboy, request, stop], Measurements, Meta, _Config) ->
Ctx = otel_telemetry:set_current_telemetry_span(?TRACER_ID, Meta),
Status = maps:get(resp_status, Meta),
Attributes = #{
'http.request_content_length' => maps:get(req_body_length, Measurements),
'http.response_content_length' => maps:get(resp_body_length, Measurements)
},
otel_span:set_attributes(Ctx, Attributes),
StatusCode = transform_status_to_code(Status),
case StatusCode of
undefined ->
case maps:get(error, Meta, undefined) of
{ErrorType, Error, Reason} ->
otel_span:add_events(Ctx, [opentelemetry:event(ErrorType, #{error => Error, reason => Reason})]),
otel_span:set_status(Ctx, opentelemetry:status(?OTEL_STATUS_ERROR, Reason));
_ ->
% do nothing first as I'm unsure how should we handle this
ok
end;
StatusCode when StatusCode >= 500 ->
otel_span:set_attribute(Ctx, 'http.status_code', StatusCode),
otel_span:set_status(Ctx, opentelemetry:status(?OTEL_STATUS_ERROR, <<"">>));
StatusCode when StatusCode >= 400 ->
otel_span:set_attribute(Ctx, 'http.status_code', StatusCode);
StatusCode when StatusCode < 400 ->
otel_span:set_attribute(Ctx, 'http.status_code', StatusCode)
end,
2021-10-14 03:11:26 +00:00
otel_telemetry:end_telemetry_span(?TRACER_ID, Meta),
otel_ctx:clear();
handle_event([cowboy, request, exception], Measurements, Meta, _Config) ->
Ctx = otel_telemetry:set_current_telemetry_span(?TRACER_ID, Meta),
#{
kind := Kind,
reason := Reason,
stacktrace := Stacktrace,
resp_status := Status
} = Meta,
otel_span:record_exception(Ctx, Kind, Reason, Stacktrace, []),
otel_span:set_status(Ctx, opentelemetry:status(?OTEL_STATUS_ERROR, <<"">>)),
StatusCode = transform_status_to_code(Status),
otel_span:set_attributes(Ctx, #{
'http.status_code' => StatusCode,
'http.request_content_length' => maps:get(req_body_length, Measurements),
'http.response_content_length' => maps:get(resp_body_length, Measurements)
}),
2021-10-14 03:11:26 +00:00
otel_telemetry:end_telemetry_span(?TRACER_ID, Meta),
otel_ctx:clear();
handle_event([cowboy, request, early_error], Measurements, Meta, _Config) ->
#{
reason := {ErrorType, Error, Reason},
resp_status := Status
} = Meta,
StatusCode = transform_status_to_code(Status),
Attributes = #{
'http.status_code' => StatusCode,
'http.response_content_length' => maps:get(resp_body_length, Measurements)
},
Opts = #{attributes => Attributes, kind => ?SPAN_KIND_SERVER},
Ctx = otel_telemetry:start_telemetry_span(?TRACER_ID, <<"HTTP Error">>, Meta, Opts),
otel_span:add_events(Ctx, [opentelemetry:event(ErrorType, #{error => Error, reason => Reason})]),
otel_span:set_status(Ctx, opentelemetry:status(?OTEL_STATUS_ERROR, Reason)),
2021-10-14 03:11:26 +00:00
otel_telemetry:end_telemetry_span(?TRACER_ID, Meta),
otel_ctx:clear().
transform_status_to_code(Status) when is_binary(Status) ->
[CodeString | _Message] = string:split(Status, " "),
{Code, _Rest} = string:to_integer(CodeString),
Code;
transform_status_to_code(Status) ->
Status.
http_flavor(Req) ->
case maps:get(version, Req, undefined) of
'HTTP/1.0' -> '1.0';
'HTTP/1.1' -> '1.1';
'HTTP/2' -> '2.0';
'SPDY' -> 'SPDY';
'QUIC' -> 'QUIC';
_ -> <<"">>
end.
client_ip(Headers, RemoteIP) ->
case maps:get(<<"x-forwarded-for">>, Headers, undefined) of
undefined ->
iolist_to_binary(inet:ntoa(RemoteIP));
Addresses ->
hd(binary:split(Addresses, <<",">>))
end.