diff options
author | ILYA Khlopotov <iilyak@apache.org> | 2019-11-12 16:21:36 +0000 |
---|---|---|
committer | Paul J. Davis <paul.joseph.davis@gmail.com> | 2020-03-02 12:26:21 -0600 |
commit | 0521bcaabcdbea6387b85c79be4d4fe424c053fc (patch) | |
tree | b3e30971fec87a80efa1fb77e904ecaa34a5bb4b | |
parent | b49ca70dd144a5e6c4f104cdbb85dd074f6afb6c (diff) | |
download | couchdb-0521bcaabcdbea6387b85c79be4d4fe424c053fc.tar.gz |
Trace http endpoints
-rw-r--r-- | src/chttpd/src/chttpd.app.src | 1 | ||||
-rw-r--r-- | src/chttpd/src/chttpd.erl | 114 | ||||
-rw-r--r-- | src/chttpd/src/chttpd_handlers.erl | 7 | ||||
-rw-r--r-- | src/chttpd/src/chttpd_httpd_handlers.erl | 30 |
4 files changed, 146 insertions, 6 deletions
diff --git a/src/chttpd/src/chttpd.app.src b/src/chttpd/src/chttpd.app.src index 3526745df..af330e0df 100644 --- a/src/chttpd/src/chttpd.app.src +++ b/src/chttpd/src/chttpd.app.src @@ -26,6 +26,7 @@ couch_stats, config, couch, + ctrace, ets_lru, fabric ]}, diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl index a15537f85..625e4eb55 100644 --- a/src/chttpd/src/chttpd.erl +++ b/src/chttpd/src/chttpd.erl @@ -259,6 +259,7 @@ handle_request_int(MochiReq) -> case after_request(HttpReq2, HttpResp) of #httpd_resp{status = ok, response = Resp} -> + span_ok(HttpResp), {ok, Resp}; #httpd_resp{status = aborted, reason = Reason} -> couch_log:error("Response abnormally terminated: ~p", [Reason]), @@ -266,6 +267,7 @@ handle_request_int(MochiReq) -> end. before_request(HttpReq) -> + ctrace:is_enabled() andalso start_span(HttpReq), try chttpd_stats:init(), chttpd_plugin:before_request(HttpReq) @@ -316,6 +318,8 @@ process_request(#httpd{mochi_req = MochiReq} = HttpReq) -> end. handle_req_after_auth(HandlerKey, HttpReq) -> + #httpd{user_ctx = #user_ctx{name = User}} = HttpReq, + ctrace:tag(#{user => User}), try HandlerFun = chttpd_handlers:url_handler(HandlerKey, fun chttpd_db:handle_request/1), @@ -1052,16 +1056,20 @@ send_error(#httpd{} = Req, Code, ErrorStr, ReasonStr) -> send_error(Req, Code, [], ErrorStr, ReasonStr, []). send_error(Req, Code, Headers, ErrorStr, ReasonStr, []) -> - send_json(Req, Code, Headers, + Return = send_json(Req, Code, Headers, {[{<<"error">>, ErrorStr}, - {<<"reason">>, ReasonStr}]}); + {<<"reason">>, ReasonStr}]}), + span_error(Code, ErrorStr, ReasonStr, []), + Return; send_error(Req, Code, Headers, ErrorStr, ReasonStr, Stack) -> log_error_with_stack_trace({ErrorStr, ReasonStr, Stack}), - send_json(Req, Code, [stack_trace_id(Stack) | Headers], + Return = send_json(Req, Code, [stack_trace_id(Stack) | Headers], {[{<<"error">>, ErrorStr}, {<<"reason">>, ReasonStr} | case Stack of [] -> []; _ -> [{<<"ref">>, stack_hash(Stack)}] end - ]}). + ]}), + span_error(Code, ErrorStr, ReasonStr, Stack), + Return. update_timeout_stats(<<"timeout">>, #httpd{requested_path_parts = PathParts}) -> update_timeout_stats(PathParts); @@ -1230,6 +1238,104 @@ maybe_trace_fdb("true") -> maybe_trace_fdb(_) -> ok. +start_span(Req) -> + #httpd{ + mochi_req = MochiReq, + begin_ts = Begin, + peer = Peer, + nonce = Nonce, + method = Method, + path_parts = PathParts + } = Req, + {OperationName, ExtraTags} = get_action(Req), + Tags = maps:merge(#{ + peer => Peer, + 'http.method' => Method, + nonce => Nonce, + 'http.url' => MochiReq:get(raw_path), + path_parts => PathParts, + 'span.kind' => <<"server">>, + component => <<"couchdb.chttpd">> + }, ExtraTags), + + ctrace:start_span(OperationName, [ + {tags, Tags}, + {time, Begin} + ] ++ maybe_root_span(MochiReq)). + +maybe_root_span(MochiReq) -> + case get_trace_headers(MochiReq) of + [undefined, _, _] -> + []; + [TraceId, SpanId, ParentSpanId] -> + Span = ctrace:external_span(TraceId, SpanId, ParentSpanId), + [{root, Span}] + end. + +parse_trace_id(undefined) -> + undefined; +parse_trace_id(Hex) -> + to_int(Hex, 32). + +parse_span_id(undefined) -> + undefined; +parse_span_id(Hex) -> + to_int(Hex, 16). + +to_int(Hex, N) when length(Hex) =:= N -> + try + list_to_integer(Hex, 16) + catch error:badarg -> + undefined + end. + +get_trace_headers(MochiReq) -> + case MochiReq:get_header_value("b3") of + undefined -> + [ + parse_trace_id(MochiReq:get_header_value("X-B3-TraceId")), + parse_span_id(MochiReq:get_header_value("X-B3-SpanId")), + parse_span_id(MochiReq:get_header_value("X-B3-ParentSpanId")) + ]; + Value -> + case binary:split(Value, <<"-">>, [global]) of + [TraceIdStr, SpanIdStr, _SampledStr, ParentSpanIdStr] -> + [ + parse_trace_id(TraceIdStr), + parse_span_id(SpanIdStr), + parse_span_id(ParentSpanIdStr) + ]; + _ -> + [undefined, undefined, undefined] + end + end. + +get_action(#httpd{} = Req) -> + try + chttpd_handlers:handler_info(Req) + catch Tag:Error -> + couch_log:error("Cannot set tracing action ~p:~p", [Tag, Error]), + {undefind, #{}} + end. + +span_ok(#httpd_resp{code = Code}) -> + ctrace:tag(#{ + error => false, + 'http.status_code' => Code + }), + ctrace:finish_span(). + +span_error(Code, ErrorStr, ReasonStr, Stack) -> + ctrace:tag(#{ + error => true, + 'http.status_code' => Code + }), + ctrace:log(#{ + 'error.kind' => ErrorStr, + message => ReasonStr, + stack => Stack + }), + ctrace:finish_span(). -ifdef(TEST). diff --git a/src/chttpd/src/chttpd_handlers.erl b/src/chttpd/src/chttpd_handlers.erl index 930563230..c07b2097b 100644 --- a/src/chttpd/src/chttpd_handlers.erl +++ b/src/chttpd/src/chttpd_handlers.erl @@ -15,7 +15,8 @@ -export([ url_handler/2, db_handler/2, - design_handler/2 + design_handler/2, + handler_info/1 ]). -define(SERVICE_ID, chttpd_handlers). @@ -35,6 +36,10 @@ db_handler(HandlerKey, DefaultFun) -> design_handler(HandlerKey, DefaultFun) -> select(collect(design_handler, [HandlerKey]), DefaultFun). +handler_info(HttpReq) -> + Default = {'unknown.unknown', #{}}, + select(collect(handler_info, [HttpReq]), Default). + %% ------------------------------------------------------------------ %% Internal Function Definitions %% ------------------------------------------------------------------ diff --git a/src/chttpd/src/chttpd_httpd_handlers.erl b/src/chttpd/src/chttpd_httpd_handlers.erl index 5e86ea87d..54d6dfce2 100644 --- a/src/chttpd/src/chttpd_httpd_handlers.erl +++ b/src/chttpd/src/chttpd_httpd_handlers.erl @@ -12,7 +12,10 @@ -module(chttpd_httpd_handlers). --export([url_handler/1, db_handler/1, design_handler/1]). +-export([url_handler/1, db_handler/1, design_handler/1, handler_info/1]). + +-include_lib("couch/include/couch_db.hrl"). + url_handler(<<>>) -> fun chttpd_misc:handle_welcome_req/1; url_handler(<<"favicon.ico">>) -> fun chttpd_misc:handle_favicon_req/1; @@ -44,3 +47,28 @@ design_handler(<<"_update">>) -> fun chttpd_show:handle_doc_update_req/3; design_handler(<<"_info">>) -> fun chttpd_db:handle_design_info_req/3; design_handler(<<"_rewrite">>) -> fun chttpd_rewrite:handle_rewrite_req/3; design_handler(_) -> no_match. + +%% TODO Populate in another PR +handler_info(#httpd{path_parts=[<<"_all_dbs">>], method=Method}) + when Method =:= 'HEAD' orelse Method =:= 'GET' -> + {'all-dbs.read', #{}}; + +handler_info(#httpd{path_parts=[<<"_session">>], method=Method}) + when Method =:= 'HEAD' orelse Method =:= 'GET' -> + {'session.read', #{}}; +handler_info(#httpd{path_parts=[<<"_session">>], method='POST'}) -> + {'session.write', #{}}; +handler_info(#httpd{path_parts=[<<"_session">>], method='DELETE'}) -> + {'session.delete', #{}}; + +handler_info(#httpd{path_parts=[_Db], method=Method}) + when Method =:= 'HEAD' orelse Method =:= 'GET' -> + {'database-info.read', #{}}; +handler_info(#httpd{path_parts=[_Db], method='POST'}) -> + {'document.write', #{}}; +handler_info(#httpd{path_parts=[_Db], method='PUT'}) -> + {'database.create', #{}}; +handler_info(#httpd{path_parts=[_Db], method='DELETE'}) -> + {'database.delete', #{}}; + +handler_info(_) -> no_match. |