%%%------------------------------------------------------------------------ %% Copyright 2017, OpenCensus Authors %% 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. %% %% @doc Elli middleware for tracing requests and recording stats. %% @end %%%------------------------------------------------------------------------- -module(oc_elli_middleware). -include_lib("opencensus/include/opencensus.hrl"). -export([preprocess/2, handle/2, handle_event/3]). preprocess(Req, _) -> Headers = elli_request:headers(Req), RawPath = elli_request:raw_path(Req), Method = elli_request:method(Req), %% TODO: add configuration option to args to set propagation format ParentSpanCtx = oc_propagation_http_tracecontext:from_headers(Headers), %% update tags from header and new request tags BinMethod = to_binary(Method), UserAgent = elli_request:get_header(<<"User-Agent">>, Req, <<>>), Host = elli_request:get_header(<<"Host">>, Req, <<>>), %% TODO: decode tagmap from headers when w3c correlation context is ready %% TagMap = #{http_server_method => BinMethod, %% http_server_path => RawPath, %% http_server_host => Host}, _ = ocp:with_span_ctx(oc_trace:start_span(RawPath, ParentSpanCtx, #{remote_parent => true, kind => ?SPAN_KIND_SERVER, attributes => #{<<"http.url">> => RawPath, <<"http.host">> => Host, <<"http.user_agent">> => UserAgent, <<"http.method">> => BinMethod}})), Req. handle(_Req, _Config) -> ignore. handle_event(elli_startup, _Args, _Config) -> %% register measures, if not already registered oc_elli:register_measures(), ok; handle_event(request_complete, Args, Config) -> handle_full_response(request_complete, Args, Config); handle_event(chunk_complete, Args, Config) -> handle_full_response(chunk_complete, Args, Config); handle_event(request_timeout, _, _Config) -> finish_exception(request_timeout, request_timeout); handle_event(request_parse_error, [Reason], _Config) -> finish_exception(request_parse_error, Reason); handle_event(client_closed, [RequestPart], _Config) -> finish_exception(client_closed, {request_part, RequestPart}); handle_event(client_timeout, [RequestPart], _Config) -> finish_exception(client_timeout, {request_part, RequestPart}); handle_event(bad_request, [Reason], _Config) -> finish_exception(bad_request, Reason); handle_event(request_error, [_Req, Exception, Stacktrace], _Config) -> finish_exception(Exception, Stacktrace); handle_event(request_throw, [_Req, Exception, Stacktrace], _Config) -> finish_exception(Exception, Stacktrace); handle_event(request_exit, [_Req, Exception, Stacktrace], _Config) -> finish_exception(Exception, Stacktrace); handle_event(_Event, _Args, _Config) -> ok. %% handle_full_response(_Type, [_Req, Code, _Hs, _B, {Timings, Sizes}], _Config) -> %% TODO: add tags when tagmap feature is added to opencensus %% ocp:update_tags(#{http_server_status => integer_to_list(Code)}), case proplists:get_value(req_body, Sizes) of undefined -> ok; UncompressedReqSize -> ocp:record('opencensus.io/http/server/received_bytes', UncompressedReqSize), ocp:add_time_event(oc_trace:message_event(?MESSAGE_EVENT_TYPE_RECEIVED, 0, UncompressedReqSize, UncompressedReqSize)) end, ServerLatency = proplists:get_value(request_end, Timings) - proplists:get_value(headers_start, Timings), ocp:record('opencensus.io/http/server/server_latency', ServerLatency), UncompressedRespSize = size(Sizes, response_body), ocp:record('opencensus.io/http/server/sent_bytes', UncompressedRespSize), ocp:add_time_event(oc_trace:message_event(?MESSAGE_EVENT_TYPE_SENT, 0, UncompressedRespSize, UncompressedRespSize)), ocp:put_attribute(<<"http.status_code">>, Code), ocp:set_status(opencensus:http_status_to_trace_status(Code), <<>>), ocp:finish_span(). size(Sizes, response_body) -> case proplists:get_value(chunks, Sizes) of undefined -> case proplists:get_value(file, Sizes) of undefined -> proplists:get_value(resp_body, Sizes); FileSize -> FileSize end; ChunksSize -> ChunksSize end. finish_exception(Exception, Stacktrace) -> ocp:put_attributes(#{<<"stacktrace">> => term_to_string(Stacktrace), <<"error.message">> => term_to_string(Exception)}). to_binary(Method) when is_atom(Method) -> atom_to_binary(Method, utf8); to_binary(Method) -> Method. term_to_string(Term) -> list_to_binary(io_lib:format("~p", [Term])).