cowboy_tracer_h.erl (6702B)
1 %% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu> 2 %% 3 %% Permission to use, copy, modify, and/or distribute this software for any 4 %% purpose with or without fee is hereby granted, provided that the above 5 %% copyright notice and this permission notice appear in all copies. 6 %% 7 %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15 -module(cowboy_tracer_h). 16 -behavior(cowboy_stream). 17 18 -export([init/3]). 19 -export([data/4]). 20 -export([info/3]). 21 -export([terminate/3]). 22 -export([early_error/5]). 23 24 -export([set_trace_patterns/0]). 25 26 -export([tracer_process/3]). 27 -export([system_continue/3]). 28 -export([system_terminate/4]). 29 -export([system_code_change/4]). 30 31 -type match_predicate() 32 :: fun((cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts()) -> boolean()). 33 34 -type tracer_match_specs() :: [match_predicate() 35 | {method, binary()} 36 | {host, binary()} 37 | {path, binary()} 38 | {path_start, binary()} 39 | {header, binary()} 40 | {header, binary(), binary()} 41 | {peer_ip, inet:ip_address()} 42 ]. 43 -export_type([tracer_match_specs/0]). 44 45 -type tracer_callback() :: fun((init | terminate | tuple(), any()) -> any()). 46 -export_type([tracer_callback/0]). 47 48 -spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts()) 49 -> {cowboy_stream:commands(), any()}. 50 init(StreamID, Req, Opts) -> 51 init_tracer(StreamID, Req, Opts), 52 cowboy_stream:init(StreamID, Req, Opts). 53 54 -spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State) 55 -> {cowboy_stream:commands(), State} when State::any(). 56 data(StreamID, IsFin, Data, Next) -> 57 cowboy_stream:data(StreamID, IsFin, Data, Next). 58 59 -spec info(cowboy_stream:streamid(), any(), State) 60 -> {cowboy_stream:commands(), State} when State::any(). 61 info(StreamID, Info, Next) -> 62 cowboy_stream:info(StreamID, Info, Next). 63 64 -spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), any()) -> any(). 65 terminate(StreamID, Reason, Next) -> 66 cowboy_stream:terminate(StreamID, Reason, Next). 67 68 -spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(), 69 cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp 70 when Resp::cowboy_stream:resp_command(). 71 early_error(StreamID, Reason, PartialReq, Resp, Opts) -> 72 cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts). 73 74 %% API. 75 76 %% These trace patterns are most likely not suitable for production. 77 -spec set_trace_patterns() -> ok. 78 set_trace_patterns() -> 79 erlang:trace_pattern({'_', '_', '_'}, [{'_', [], [{return_trace}]}], [local]), 80 erlang:trace_pattern(on_load, [{'_', [], [{return_trace}]}], [local]), 81 ok. 82 83 %% Internal. 84 85 init_tracer(StreamID, Req, Opts=#{tracer_match_specs := List, tracer_callback := _}) -> 86 case match(List, StreamID, Req, Opts) of 87 false -> 88 ok; 89 true -> 90 start_tracer(StreamID, Req, Opts) 91 end; 92 %% When the options tracer_match_specs or tracer_callback 93 %% are not provided we do not enable tracing. 94 init_tracer(_, _, _) -> 95 ok. 96 97 match([], _, _, _) -> 98 true; 99 match([Predicate|Tail], StreamID, Req, Opts) when is_function(Predicate) -> 100 case Predicate(StreamID, Req, Opts) of 101 true -> match(Tail, StreamID, Req, Opts); 102 false -> false 103 end; 104 match([{method, Value}|Tail], StreamID, Req=#{method := Value}, Opts) -> 105 match(Tail, StreamID, Req, Opts); 106 match([{host, Value}|Tail], StreamID, Req=#{host := Value}, Opts) -> 107 match(Tail, StreamID, Req, Opts); 108 match([{path, Value}|Tail], StreamID, Req=#{path := Value}, Opts) -> 109 match(Tail, StreamID, Req, Opts); 110 match([{path_start, PathStart}|Tail], StreamID, Req=#{path := Path}, Opts) -> 111 Len = byte_size(PathStart), 112 case Path of 113 <<PathStart:Len/binary, _/bits>> -> match(Tail, StreamID, Req, Opts); 114 _ -> false 115 end; 116 match([{header, Name}|Tail], StreamID, Req=#{headers := Headers}, Opts) -> 117 case Headers of 118 #{Name := _} -> match(Tail, StreamID, Req, Opts); 119 _ -> false 120 end; 121 match([{header, Name, Value}|Tail], StreamID, Req=#{headers := Headers}, Opts) -> 122 case Headers of 123 #{Name := Value} -> match(Tail, StreamID, Req, Opts); 124 _ -> false 125 end; 126 match([{peer_ip, IP}|Tail], StreamID, Req=#{peer := {IP, _}}, Opts) -> 127 match(Tail, StreamID, Req, Opts); 128 match(_, _, _, _) -> 129 false. 130 131 %% We only start the tracer if one wasn't started before. 132 start_tracer(StreamID, Req, Opts) -> 133 case erlang:trace_info(self(), tracer) of 134 {tracer, []} -> 135 TracerPid = proc_lib:spawn_link(?MODULE, tracer_process, [StreamID, Req, Opts]), 136 %% The default flags are probably not suitable for production. 137 Flags = maps:get(tracer_flags, Opts, [ 138 send, 'receive', call, return_to, 139 procs, ports, monotonic_timestamp, 140 %% The set_on_spawn flag is necessary to catch events 141 %% from request processes. 142 set_on_spawn 143 ]), 144 erlang:trace(self(), true, [{tracer, TracerPid}|Flags]), 145 ok; 146 _ -> 147 ok 148 end. 149 150 %% Tracer process. 151 152 -spec tracer_process(_, _, _) -> no_return(). 153 tracer_process(StreamID, Req=#{pid := Parent}, Opts=#{tracer_callback := Fun}) -> 154 %% This is necessary because otherwise the tracer could stop 155 %% before it has finished processing the events in its queue. 156 process_flag(trap_exit, true), 157 State = Fun(init, {StreamID, Req, Opts}), 158 tracer_loop(Parent, Opts, State). 159 160 tracer_loop(Parent, Opts=#{tracer_callback := Fun}, State0) -> 161 receive 162 Msg when element(1, Msg) =:= trace; element(1, Msg) =:= trace_ts -> 163 State = Fun(Msg, State0), 164 tracer_loop(Parent, Opts, State); 165 {'EXIT', Parent, Reason} -> 166 tracer_terminate(Reason, Opts, State0); 167 {system, From, Request} -> 168 sys:handle_system_msg(Request, From, Parent, ?MODULE, [], {Opts, State0}); 169 Msg -> 170 cowboy:log(warning, "~p: Tracer process received stray message ~9999p~n", 171 [?MODULE, Msg], Opts), 172 tracer_loop(Parent, Opts, State0) 173 end. 174 175 -spec tracer_terminate(_, _, _) -> no_return(). 176 tracer_terminate(Reason, #{tracer_callback := Fun}, State) -> 177 _ = Fun(terminate, State), 178 exit(Reason). 179 180 %% System callbacks. 181 182 -spec system_continue(pid(), _, {cowboy:opts(), any()}) -> no_return(). 183 system_continue(Parent, _, {Opts, State}) -> 184 tracer_loop(Parent, Opts, State). 185 186 -spec system_terminate(any(), _, _, _) -> no_return(). 187 system_terminate(Reason, _, _, {Opts, State}) -> 188 tracer_terminate(Reason, Opts, State). 189 190 -spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::any(). 191 system_code_change(Misc, _, _, _) -> 192 {ok, Misc}.