cowboy_tracer_h.erl 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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. -module(cowboy_tracer_h).
  15. -behavior(cowboy_stream).
  16. -export([init/3]).
  17. -export([data/4]).
  18. -export([info/3]).
  19. -export([terminate/3]).
  20. -export([early_error/5]).
  21. -export([set_trace_patterns/0]).
  22. -export([tracer_process/3]).
  23. -export([system_continue/3]).
  24. -export([system_terminate/4]).
  25. -export([system_code_change/4]).
  26. -type match_predicate()
  27. :: fun((cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts()) -> boolean()).
  28. -type tracer_match_specs() :: [match_predicate()
  29. | {method, binary()}
  30. | {host, binary()}
  31. | {path, binary()}
  32. | {path_start, binary()}
  33. | {header, binary()}
  34. | {header, binary(), binary()}
  35. | {peer_ip, inet:ip_address()}
  36. ].
  37. -export_type([tracer_match_specs/0]).
  38. -spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
  39. -> {cowboy_stream:commands(), any()}.
  40. init(StreamID, Req, Opts) ->
  41. init_tracer(StreamID, Req, Opts),
  42. cowboy_stream:init(StreamID, Req, Opts).
  43. -spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
  44. -> {cowboy_stream:commands(), State} when State::any().
  45. data(StreamID, IsFin, Data, Next) ->
  46. cowboy_stream:data(StreamID, IsFin, Data, Next).
  47. -spec info(cowboy_stream:streamid(), any(), State)
  48. -> {cowboy_stream:commands(), State} when State::any().
  49. info(StreamID, Info, Next) ->
  50. cowboy_stream:info(StreamID, Info, Next).
  51. -spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), any()) -> any().
  52. terminate(StreamID, Reason, Next) ->
  53. cowboy_stream:terminate(StreamID, Reason, Next).
  54. -spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),
  55. cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
  56. when Resp::cowboy_stream:resp_command().
  57. early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
  58. cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).
  59. %% API.
  60. %% These trace patterns are most likely not suitable for production.
  61. -spec set_trace_patterns() -> ok.
  62. set_trace_patterns() ->
  63. erlang:trace_pattern({'_', '_', '_'}, [{'_', [], [{return_trace}]}], [local]),
  64. erlang:trace_pattern(on_load, [{'_', [], [{return_trace}]}], [local]),
  65. ok.
  66. %% Internal.
  67. init_tracer(StreamID, Req, Opts=#{tracer_match_specs := List, tracer_callback := _}) ->
  68. case match(List, StreamID, Req, Opts) of
  69. false ->
  70. ok;
  71. true ->
  72. start_tracer(StreamID, Req, Opts)
  73. end;
  74. %% When the options tracer_match_specs or tracer_callback
  75. %% are not provided we do not enable tracing.
  76. init_tracer(_, _, _) ->
  77. ok.
  78. match([], _, _, _) ->
  79. true;
  80. match([Predicate|Tail], StreamID, Req, Opts) when is_function(Predicate) ->
  81. case Predicate(StreamID, Req, Opts) of
  82. true -> match(Tail, StreamID, Req, Opts);
  83. false -> false
  84. end;
  85. match([{method, Value}|Tail], StreamID, Req=#{method := Value}, Opts) ->
  86. match(Tail, StreamID, Req, Opts);
  87. match([{host, Value}|Tail], StreamID, Req=#{host := Value}, Opts) ->
  88. match(Tail, StreamID, Req, Opts);
  89. match([{path, Value}|Tail], StreamID, Req=#{path := Value}, Opts) ->
  90. match(Tail, StreamID, Req, Opts);
  91. match([{path_start, PathStart}|Tail], StreamID, Req=#{path := Path}, Opts) ->
  92. Len = byte_size(PathStart),
  93. case Path of
  94. <<PathStart:Len/binary, _/bits>> -> match(Tail, StreamID, Req, Opts);
  95. _ -> false
  96. end;
  97. match([{header, Name}|Tail], StreamID, Req=#{headers := Headers}, Opts) ->
  98. case Headers of
  99. #{Name := _} -> match(Tail, StreamID, Req, Opts);
  100. _ -> false
  101. end;
  102. match([{header, Name, Value}|Tail], StreamID, Req=#{headers := Headers}, Opts) ->
  103. case Headers of
  104. #{Name := Value} -> match(Tail, StreamID, Req, Opts);
  105. _ -> false
  106. end;
  107. match([{peer_ip, IP}|Tail], StreamID, Req=#{peer := {IP, _}}, Opts) ->
  108. match(Tail, StreamID, Req, Opts);
  109. match(_, _, _, _) ->
  110. false.
  111. %% We only start the tracer if one wasn't started before.
  112. start_tracer(StreamID, Req, Opts) ->
  113. case erlang:trace_info(self(), tracer) of
  114. {tracer, []} ->
  115. TracerPid = proc_lib:spawn_link(?MODULE, tracer_process, [StreamID, Req, Opts]),
  116. %% The default flags are probably not suitable for production.
  117. Flags = maps:get(tracer_flags, Opts, [
  118. send, 'receive', call, return_to,
  119. procs, ports, monotonic_timestamp,
  120. %% The set_on_spawn flag is necessary to catch events
  121. %% from request processes.
  122. set_on_spawn
  123. ]),
  124. erlang:trace(self(), true, [{tracer, TracerPid}|Flags]),
  125. ok;
  126. _ ->
  127. ok
  128. end.
  129. %% Tracer process.
  130. -spec tracer_process(_, _, _) -> no_return().
  131. tracer_process(StreamID, Req=#{pid := Parent}, Opts=#{tracer_callback := Fun}) ->
  132. %% This is necessary because otherwise the tracer could stop
  133. %% before it has finished processing the events in its queue.
  134. process_flag(trap_exit, true),
  135. State = Fun(init, {StreamID, Req, Opts}),
  136. tracer_loop(Parent, Fun, State).
  137. tracer_loop(Parent, Fun, State0) ->
  138. receive
  139. Msg when element(1, Msg) =:= trace_ts ->
  140. State = Fun(Msg, State0),
  141. tracer_loop(Parent, Fun, State);
  142. {'EXIT', Parent, Reason} ->
  143. tracer_terminate(Reason, Fun, State0);
  144. {system, From, Request} ->
  145. sys:handle_system_msg(Request, From, Parent, ?MODULE, [], {Fun, State0});
  146. Msg ->
  147. error_logger:error_msg("~p: Tracer process received stray message ~9999p~n",
  148. [?MODULE, Msg]),
  149. tracer_loop(Parent, Fun, State0)
  150. end.
  151. tracer_terminate(Reason, Fun, State) ->
  152. _ = Fun(terminate, State),
  153. exit(Reason).
  154. %% System callbacks.
  155. -spec system_continue(pid(), _, {fun(), any()}) -> no_return().
  156. system_continue(Parent, _, {Fun, State}) ->
  157. tracer_loop(Parent, Fun, State).
  158. -spec system_terminate(any(), _, _, _) -> no_return().
  159. system_terminate(Reason, _, _, {Fun, State}) ->
  160. tracer_terminate(Reason, Fun, State).
  161. -spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::any().
  162. system_code_change(Misc, _, _, _) ->
  163. {ok, Misc}.