summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorILYA Khlopotov <iilyak@apache.org>2019-11-12 16:21:36 +0000
committerPaul J. Davis <paul.joseph.davis@gmail.com>2019-11-20 16:58:56 -0600
commit98bc5ea80a3606302cd7646ad623f8314e016a8c (patch)
treec6663814504de385fba1ca6be5958aefd3fb17b6
parente93d1b45064ee76da9603c49bd43552fa12214fe (diff)
downloadcouchdb-98bc5ea80a3606302cd7646ad623f8314e016a8c.tar.gz
Trace http endpoints
-rw-r--r--src/chttpd/src/chttpd.app.src1
-rw-r--r--src/chttpd/src/chttpd.erl114
-rw-r--r--src/chttpd/src/chttpd_handlers.erl7
-rw-r--r--src/chttpd/src/chttpd_httpd_handlers.erl30
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 2f4bbcf4a..46b3b2411 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),
@@ -1050,16 +1054,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);
@@ -1228,6 +1236,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 000f29b2f..de7f3a01c 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.