Просмотр исходного кода

Handle error_response command in cowboy_metrics_h

Grigory Starinkin 6 лет назад
Родитель
Сommit
5ab5615ff0
3 измененных файлов с 145 добавлено и 0 удалено
  1. 10 0
      src/cowboy_metrics_h.erl
  2. 16 0
      test/handlers/crash_h.erl
  3. 119 0
      test/metrics_SUITE.erl

+ 10 - 0
src/cowboy_metrics_h.erl

@@ -222,6 +222,16 @@ fold([{response, Status, Headers, Body}|Tail],
 		resp_end=Resp,
 		resp_body_length=resp_body_length(Body)
 	});
+fold([{error_response, Status, Headers, Body}|Tail],
+		State=#state{resp_status=RespStatus}) ->
+	%% The error_response command only results in a response
+	%% if no response was sent before.
+	case RespStatus of
+		undefined ->
+			fold([{response, Status, Headers, Body}|Tail], State);
+		_ ->
+			fold(Tail, State)
+	end;
 fold([{headers, Status, Headers}|Tail],
 		State=#state{resp_headers_filter=RespHeadersFilter}) ->
 	RespStart = erlang:monotonic_time(),

+ 16 - 0
test/handlers/crash_h.erl

@@ -0,0 +1,16 @@
+%% This module crashes immediately.
+
+-module(crash_h).
+
+-behaviour(cowboy_handler).
+
+-export([init/2]).
+
+-spec init(_, _) -> no_return().
+init(_, no_reply) ->
+	ct_helper:ignore(?MODULE, init, 2),
+	error(crash);
+init(Req, reply) ->
+	_ = cowboy_req:reply(200, Req),
+	ct_helper:ignore(?MODULE, init, 2),
+	error(crash).

+ 119 - 0
test/metrics_SUITE.erl

@@ -71,6 +71,8 @@ init_compress_opts(Config) ->
 init_routes(_) -> [
 	{"localhost", [
 		{"/", hello_h, []},
+		{"/crash/no_reply", crash_h, no_reply},
+		{"/crash/reply", crash_h, reply},
 		{"/default", default_h, []},
 		{"/full/:key", echo_h, []},
 		{"/resp/:key[/:arg]", resp_h, []},
@@ -434,3 +436,120 @@ do_ws(Config) ->
 		error(timeout)
 	end,
 	gun:close(ConnPid).
+
+error_response(Config) ->
+	doc("Confirm metrics are correct when an error_response command is returned."),
+	%% Perform a GET request.
+	ConnPid = gun_open(Config),
+	Ref = gun:get(ConnPid, "/crash/no_reply", [
+		{<<"accept-encoding">>, <<"gzip">>},
+		{<<"x-test-pid">>, pid_to_list(self())}
+	]),
+	{response, fin, 500, RespHeaders} = gun:await(ConnPid, Ref),
+	timer:sleep(100),
+	gun:close(ConnPid),
+	%% Receive the metrics and validate them.
+	receive
+		{metrics, From, Metrics} ->
+			%% Ensure the timestamps are in the expected order.
+			#{
+				req_start := ReqStart, req_end := ReqEnd,
+				resp_start := RespStart, resp_end := RespEnd
+			} = Metrics,
+			true = (ReqStart =< RespStart)
+				and (RespStart =< RespEnd)
+				and (RespEnd =< ReqEnd),
+			%% We didn't send a body.
+			#{
+				req_body_start := undefined,
+				req_body_end := undefined,
+				req_body_length := 0
+			} = Metrics,
+			%% We got a 500 response without a body.
+			#{
+				resp_status := 500,
+				resp_headers := ExpectedRespHeaders,
+				resp_body_length := 0
+			} = Metrics,
+			ExpectedRespHeaders = maps:from_list(RespHeaders),
+			%% The request process executed normally.
+			#{procs := Procs} = Metrics,
+			[{_, #{
+				spawn := ProcSpawn,
+				exit := ProcExit,
+				reason := {crash, _StackTrace}
+			}}] = maps:to_list(Procs),
+			true = ProcSpawn =< ProcExit,
+			%% Confirm other metadata are as expected.
+			#{
+				ref := _,
+				pid := From,
+				streamid := 1,
+				reason := {internal_error, {'EXIT', _Pid, {crash, _StackTrace}}, 'Stream process crashed.'},
+				req := #{},
+				informational := []
+			} = Metrics,
+			%% All good!
+			ok
+	after 1000 ->
+		error(timeout)
+	end.
+
+error_response_after_reply(Config) ->
+	doc("Confirm metrics are correct when an error_response command is returned "
+		"after a response was sent."),
+	%% Perform a GET request.
+	ConnPid = gun_open(Config),
+	Ref = gun:get(ConnPid, "/crash/reply", [
+		{<<"accept-encoding">>, <<"gzip">>},
+		{<<"x-test-pid">>, pid_to_list(self())}
+	]),
+	{response, fin, 200, RespHeaders} = gun:await(ConnPid, Ref),
+	timer:sleep(100),
+	gun:close(ConnPid),
+	%% Receive the metrics and validate them.
+	receive
+		{metrics, From, Metrics} ->
+			%% Ensure the timestamps are in the expected order.
+			#{
+				req_start := ReqStart, req_end := ReqEnd,
+				resp_start := RespStart, resp_end := RespEnd
+			} = Metrics,
+			true = (ReqStart =< RespStart)
+				and (RespStart =< RespEnd)
+				and (RespEnd =< ReqEnd),
+			%% We didn't send a body.
+			#{
+				req_body_start := undefined,
+				req_body_end := undefined,
+				req_body_length := 0
+			} = Metrics,
+			%% We got a 200 response without a body.
+			#{
+				resp_status := 200,
+				resp_headers := ExpectedRespHeaders,
+				resp_body_length := 0
+			} = Metrics,
+			ExpectedRespHeaders = maps:from_list(RespHeaders),
+			%% The request process executed normally.
+			#{procs := Procs} = Metrics,
+			[{_, #{
+				spawn := ProcSpawn,
+				exit := ProcExit,
+				reason := {crash, _StackTrace}
+			}}] = maps:to_list(Procs),
+			true = ProcSpawn =< ProcExit,
+			%% Confirm other metadata are as expected.
+			#{
+				ref := _,
+				pid := From,
+				streamid := 1,
+				reason := {internal_error, {'EXIT', _Pid, {crash, _StackTrace}}, 'Stream process crashed.'},
+				req := #{},
+				informational := []
+			} = Metrics,
+			%% All good!
+			ok
+	after 1000 ->
+		error(timeout)
+	end.