tracer_SUITE.erl 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  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(tracer_SUITE).
  15. -compile(export_all).
  16. -compile(nowarn_export_all).
  17. -import(ct_helper, [config/2]).
  18. -import(ct_helper, [doc/1]).
  19. -import(cowboy_test, [gun_open/1]).
  20. -import(cowboy_test, [gun_down/1]).
  21. %% ct.
  22. %% We initialize trace patterns here. Appropriate would be in
  23. %% init_per_suite/1, but this works just as well.
  24. all() ->
  25. case code:is_module_native(?MODULE) of
  26. true ->
  27. {skip, "The Cowboy tracer is not compatible with native code."};
  28. false ->
  29. cowboy_tracer_h:set_trace_patterns(),
  30. cowboy_test:common_all()
  31. end.
  32. %% We want tests for each group to execute sequentially
  33. %% because we need to modify the protocol options. Groups
  34. %% can run in parallel however.
  35. groups() ->
  36. Tests = ct_helper:all(?MODULE),
  37. [
  38. {http, [], Tests},
  39. {https, [], Tests},
  40. {h2, [], Tests},
  41. {h2c, [], Tests},
  42. {http_compress, [], Tests},
  43. {https_compress, [], Tests},
  44. {h2_compress, [], Tests},
  45. {h2c_compress, [], Tests}
  46. ].
  47. init_per_group(Name = http, Config) ->
  48. cowboy_test:init_http(Name, init_plain_opts(Config), Config);
  49. init_per_group(Name = https, Config) ->
  50. cowboy_test:init_http(Name, init_plain_opts(Config), Config);
  51. init_per_group(Name = h2, Config) ->
  52. cowboy_test:init_http2(Name, init_plain_opts(Config), Config);
  53. init_per_group(Name = h2c, Config) ->
  54. Config1 = cowboy_test:init_http(Name, init_plain_opts(Config), Config),
  55. lists:keyreplace(protocol, 1, Config1, {protocol, http2});
  56. init_per_group(Name = http_compress, Config) ->
  57. cowboy_test:init_http(Name, init_compress_opts(Config), Config);
  58. init_per_group(Name = https_compress, Config) ->
  59. cowboy_test:init_http(Name, init_compress_opts(Config), Config);
  60. init_per_group(Name = h2_compress, Config) ->
  61. cowboy_test:init_http2(Name, init_compress_opts(Config), Config);
  62. init_per_group(Name = h2c_compress, Config) ->
  63. Config1 = cowboy_test:init_http(Name, init_compress_opts(Config), Config),
  64. lists:keyreplace(protocol, 1, Config1, {protocol, http2}).
  65. end_per_group(Name, _) ->
  66. cowboy:stop_listener(Name).
  67. init_plain_opts(Config) ->
  68. #{
  69. env => #{dispatch => cowboy_router:compile(init_routes(Config))},
  70. stream_handlers => [cowboy_tracer_h, cowboy_stream_h]
  71. }.
  72. init_compress_opts(Config) ->
  73. #{
  74. env => #{dispatch => cowboy_router:compile(init_routes(Config))},
  75. stream_handlers => [cowboy_tracer_h, cowboy_compress_h, cowboy_stream_h]
  76. }.
  77. init_routes(_) -> [
  78. {"localhost", [
  79. {"/", hello_h, []},
  80. {"/longer/hello/path", hello_h, []}
  81. ]}
  82. ].
  83. do_get(Path, Config) ->
  84. %% Perform a GET request.
  85. ConnPid = gun_open(Config),
  86. Ref = gun:get(ConnPid, Path, [
  87. {<<"accept-encoding">>, <<"gzip">>},
  88. {<<"x-test-pid">>, pid_to_list(self())}
  89. ]),
  90. {response, nofin, 200, _Headers} = gun:await(ConnPid, Ref),
  91. {ok, _Body} = gun:await_body(ConnPid, Ref),
  92. gun:close(ConnPid).
  93. %% We only care about cowboy_req:reply/4 calls and init/terminate events.
  94. do_tracer_callback(Pid) ->
  95. fun
  96. (Event, _) when Event =:= init; Event =:= terminate ->
  97. Pid ! Event,
  98. 0;
  99. (Event={trace_ts, _, call, {cowboy_req, reply, _}, _}, State) ->
  100. Pid ! Event,
  101. Pid ! {state, State},
  102. State + 1;
  103. (_, State) ->
  104. State + 1
  105. end.
  106. %% Tests.
  107. init(Config) ->
  108. doc("Ensure the init event is triggered."),
  109. Ref = config(ref, Config),
  110. Opts = ranch:get_protocol_options(Ref),
  111. ranch:set_protocol_options(Ref, Opts#{
  112. tracer_callback => do_tracer_callback(self()),
  113. tracer_match_specs => [fun(_,_,_) -> true end]
  114. }),
  115. do_get("/", Config),
  116. receive
  117. init ->
  118. ok
  119. after 100 ->
  120. error(timeout)
  121. end.
  122. terminate(Config) ->
  123. doc("Ensure the terminate event is triggered."),
  124. Ref = config(ref, Config),
  125. Opts = ranch:get_protocol_options(Ref),
  126. ranch:set_protocol_options(Ref, Opts#{
  127. tracer_callback => do_tracer_callback(self()),
  128. tracer_match_specs => [fun(_,_,_) -> true end]
  129. }),
  130. do_get("/", Config),
  131. receive
  132. terminate ->
  133. ok
  134. after 100 ->
  135. error(timeout)
  136. end.
  137. state(Config) ->
  138. doc("Ensure the returned state is used."),
  139. Ref = config(ref, Config),
  140. Opts = ranch:get_protocol_options(Ref),
  141. ranch:set_protocol_options(Ref, Opts#{
  142. tracer_callback => do_tracer_callback(self()),
  143. tracer_match_specs => [fun(_,_,_) -> true end]
  144. }),
  145. do_get("/", Config),
  146. receive
  147. {state, St} ->
  148. true = St > 0,
  149. ok
  150. after 100 ->
  151. error(timeout)
  152. end.
  153. empty(Config) ->
  154. doc("Empty match specs unconditionally enable tracing."),
  155. Ref = config(ref, Config),
  156. Opts = ranch:get_protocol_options(Ref),
  157. ranch:set_protocol_options(Ref, Opts#{
  158. tracer_callback => do_tracer_callback(self()),
  159. tracer_match_specs => []
  160. }),
  161. do_get("/", Config),
  162. receive
  163. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  164. ok
  165. after 100 ->
  166. error(timeout)
  167. end.
  168. predicate_true(Config) ->
  169. doc("Predicate function returns true, unconditionally enable tracing."),
  170. Ref = config(ref, Config),
  171. Opts = ranch:get_protocol_options(Ref),
  172. ranch:set_protocol_options(Ref, Opts#{
  173. tracer_callback => do_tracer_callback(self()),
  174. tracer_match_specs => [fun(_,_,_) -> true end]
  175. }),
  176. do_get("/", Config),
  177. receive
  178. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  179. ok
  180. after 100 ->
  181. error(timeout)
  182. end.
  183. predicate_false(Config) ->
  184. doc("Predicate function returns false, unconditionally disable tracing."),
  185. Ref = config(ref, Config),
  186. Opts = ranch:get_protocol_options(Ref),
  187. ranch:set_protocol_options(Ref, Opts#{
  188. tracer_callback => do_tracer_callback(self()),
  189. tracer_match_specs => [fun(_,_,_) -> false end]
  190. }),
  191. do_get("/", Config),
  192. receive
  193. Msg when element(1, Msg) =:= trace_ts ->
  194. error(Msg)
  195. after 100 ->
  196. ok
  197. end.
  198. method(Config) ->
  199. doc("Method is the same as the request's, enable tracing."),
  200. Ref = config(ref, Config),
  201. Opts = ranch:get_protocol_options(Ref),
  202. ranch:set_protocol_options(Ref, Opts#{
  203. tracer_callback => do_tracer_callback(self()),
  204. tracer_match_specs => [{method, <<"GET">>}]
  205. }),
  206. do_get("/", Config),
  207. receive
  208. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  209. ok
  210. after 100 ->
  211. error(timeout)
  212. end.
  213. method_no_match(Config) ->
  214. doc("Method is different from the request's, disable tracing."),
  215. Ref = config(ref, Config),
  216. Opts = ranch:get_protocol_options(Ref),
  217. ranch:set_protocol_options(Ref, Opts#{
  218. tracer_callback => do_tracer_callback(self()),
  219. tracer_match_specs => [{method, <<"POST">>}]
  220. }),
  221. do_get("/", Config),
  222. receive
  223. Msg when element(1, Msg) =:= trace_ts ->
  224. error(Msg)
  225. after 100 ->
  226. ok
  227. end.
  228. host(Config) ->
  229. doc("Host is the same as the request's, enable tracing."),
  230. Ref = config(ref, Config),
  231. Opts = ranch:get_protocol_options(Ref),
  232. ranch:set_protocol_options(Ref, Opts#{
  233. tracer_callback => do_tracer_callback(self()),
  234. tracer_match_specs => [{host, <<"localhost">>}]
  235. }),
  236. do_get("/", Config),
  237. receive
  238. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  239. ok
  240. after 100 ->
  241. error(timeout)
  242. end.
  243. host_no_match(Config) ->
  244. doc("Host is different from the request's, disable tracing."),
  245. Ref = config(ref, Config),
  246. Opts = ranch:get_protocol_options(Ref),
  247. ranch:set_protocol_options(Ref, Opts#{
  248. tracer_callback => do_tracer_callback(self()),
  249. tracer_match_specs => [{host, <<"ninenines.eu">>}]
  250. }),
  251. do_get("/", Config),
  252. receive
  253. Msg when element(1, Msg) =:= trace_ts ->
  254. error(Msg)
  255. after 100 ->
  256. ok
  257. end.
  258. path(Config) ->
  259. doc("Path is the same as the request's, enable tracing."),
  260. Ref = config(ref, Config),
  261. Opts = ranch:get_protocol_options(Ref),
  262. ranch:set_protocol_options(Ref, Opts#{
  263. tracer_callback => do_tracer_callback(self()),
  264. tracer_match_specs => [{path, <<"/longer/hello/path">>}]
  265. }),
  266. do_get("/longer/hello/path", Config),
  267. receive
  268. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  269. ok
  270. after 100 ->
  271. error(timeout)
  272. end.
  273. path_no_match(Config) ->
  274. doc("Path is different from the request's, disable tracing."),
  275. Ref = config(ref, Config),
  276. Opts = ranch:get_protocol_options(Ref),
  277. ranch:set_protocol_options(Ref, Opts#{
  278. tracer_callback => do_tracer_callback(self()),
  279. tracer_match_specs => [{path, <<"/some/other/path">>}]
  280. }),
  281. do_get("/longer/hello/path", Config),
  282. receive
  283. Msg when element(1, Msg) =:= trace_ts ->
  284. error(Msg)
  285. after 100 ->
  286. ok
  287. end.
  288. path_start(Config) ->
  289. doc("Start of path is the same as request's, enable tracing."),
  290. Ref = config(ref, Config),
  291. Opts = ranch:get_protocol_options(Ref),
  292. ranch:set_protocol_options(Ref, Opts#{
  293. tracer_callback => do_tracer_callback(self()),
  294. tracer_match_specs => [{path_start, <<"/longer/hello">>}]
  295. }),
  296. do_get("/longer/hello/path", Config),
  297. receive
  298. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  299. ok
  300. after 100 ->
  301. error(timeout)
  302. end.
  303. path_start_no_match(Config) ->
  304. doc("Start of path is different from the request's, disable tracing."),
  305. Ref = config(ref, Config),
  306. Opts = ranch:get_protocol_options(Ref),
  307. ranch:set_protocol_options(Ref, Opts#{
  308. tracer_callback => do_tracer_callback(self()),
  309. tracer_match_specs => [{path_start, <<"/shorter/hello">>}]
  310. }),
  311. do_get("/longer/hello/path", Config),
  312. receive
  313. Msg when element(1, Msg) =:= trace_ts ->
  314. error(Msg)
  315. after 100 ->
  316. ok
  317. end.
  318. header_defined(Config) ->
  319. doc("Header is defined in the request, enable tracing."),
  320. Ref = config(ref, Config),
  321. Opts = ranch:get_protocol_options(Ref),
  322. ranch:set_protocol_options(Ref, Opts#{
  323. tracer_callback => do_tracer_callback(self()),
  324. tracer_match_specs => [{header, <<"accept-encoding">>}]
  325. }),
  326. do_get("/", Config),
  327. receive
  328. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  329. ok
  330. after 100 ->
  331. error(timeout)
  332. end.
  333. header_defined_no_match(Config) ->
  334. doc("Header is not defined in the request, disable tracing."),
  335. Ref = config(ref, Config),
  336. Opts = ranch:get_protocol_options(Ref),
  337. ranch:set_protocol_options(Ref, Opts#{
  338. tracer_callback => do_tracer_callback(self()),
  339. tracer_match_specs => [{header, <<"accept-language">>}]
  340. }),
  341. do_get("/", Config),
  342. receive
  343. Msg when element(1, Msg) =:= trace_ts ->
  344. error(Msg)
  345. after 100 ->
  346. ok
  347. end.
  348. header_value(Config) ->
  349. doc("Header value is the same as the request's, enable tracing."),
  350. Ref = config(ref, Config),
  351. Opts = ranch:get_protocol_options(Ref),
  352. ranch:set_protocol_options(Ref, Opts#{
  353. tracer_callback => do_tracer_callback(self()),
  354. tracer_match_specs => [{header, <<"accept-encoding">>, <<"gzip">>}]
  355. }),
  356. do_get("/", Config),
  357. receive
  358. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  359. ok
  360. after 100 ->
  361. error(timeout)
  362. end.
  363. header_value_no_match(Config) ->
  364. doc("Header value is different from the request's, disable tracing."),
  365. Ref = config(ref, Config),
  366. Opts = ranch:get_protocol_options(Ref),
  367. ranch:set_protocol_options(Ref, Opts#{
  368. tracer_callback => do_tracer_callback(self()),
  369. tracer_match_specs => [{header, <<"accept-encoding">>, <<"nope">>}]
  370. }),
  371. do_get("/", Config),
  372. receive
  373. Msg when element(1, Msg) =:= trace_ts ->
  374. error(Msg)
  375. after 100 ->
  376. ok
  377. end.
  378. peer_ip(Config) ->
  379. doc("Peer IP is the same as the request's, enable tracing."),
  380. Ref = config(ref, Config),
  381. Opts = ranch:get_protocol_options(Ref),
  382. ranch:set_protocol_options(Ref, Opts#{
  383. tracer_callback => do_tracer_callback(self()),
  384. tracer_match_specs => [{peer_ip, {127, 0, 0, 1}}]
  385. }),
  386. do_get("/", Config),
  387. receive
  388. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  389. ok
  390. after 100 ->
  391. error(timeout)
  392. end.
  393. peer_ip_no_match(Config) ->
  394. doc("Peer IP is different from the request's, disable tracing."),
  395. Ref = config(ref, Config),
  396. Opts = ranch:get_protocol_options(Ref),
  397. ranch:set_protocol_options(Ref, Opts#{
  398. tracer_callback => do_tracer_callback(self()),
  399. tracer_match_specs => [{peer_ip, {8, 8, 8, 8}}]
  400. }),
  401. do_get("/", Config),
  402. receive
  403. Msg when element(1, Msg) =:= trace_ts ->
  404. error(Msg)
  405. after 100 ->
  406. ok
  407. end.
  408. missing_callback(Config) ->
  409. doc("Ensure the request is still processed if the callback is not provided."),
  410. Ref = config(ref, Config),
  411. Opts0 = ranch:get_protocol_options(Ref),
  412. Opts = maps:remove(tracer_callback, Opts0),
  413. ranch:set_protocol_options(Ref, Opts#{
  414. tracer_match_specs => [{method, <<"GET">>}]
  415. }),
  416. do_get("/", Config),
  417. receive
  418. Msg when element(1, Msg) =:= trace_ts ->
  419. error(Msg)
  420. after 100 ->
  421. ok
  422. end.
  423. missing_match_specs(Config) ->
  424. doc("Ensure the request is still processed if match specs are not provided."),
  425. Ref = config(ref, Config),
  426. Opts0 = ranch:get_protocol_options(Ref),
  427. Opts = maps:remove(tracer_match_specs, Opts0),
  428. ranch:set_protocol_options(Ref, Opts#{
  429. tracer_callback => do_tracer_callback(self())
  430. }),
  431. do_get("/", Config),
  432. receive
  433. Msg when element(1, Msg) =:= trace_ts ->
  434. error(Msg)
  435. after 100 ->
  436. ok
  437. end.
  438. two_matching_requests(Config) ->
  439. doc("Perform two requests that enable tracing on the same connection."),
  440. Ref = config(ref, Config),
  441. Opts = ranch:get_protocol_options(Ref),
  442. ranch:set_protocol_options(Ref, Opts#{
  443. tracer_callback => do_tracer_callback(self()),
  444. tracer_match_specs => [fun(_,_,_) -> true end]
  445. }),
  446. %% Perform a GET request.
  447. ConnPid = gun_open(Config),
  448. Ref1 = gun:get(ConnPid, "/", []),
  449. {response, nofin, 200, _} = gun:await(ConnPid, Ref1),
  450. {ok, _} = gun:await_body(ConnPid, Ref1),
  451. receive
  452. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  453. ok
  454. after 100 ->
  455. error(timeout)
  456. end,
  457. %% Perform a second GET request on the same connection.
  458. Ref2 = gun:get(ConnPid, "/", []),
  459. {response, nofin, 200, _} = gun:await(ConnPid, Ref2),
  460. {ok, _} = gun:await_body(ConnPid, Ref2),
  461. receive
  462. {trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->
  463. ok
  464. after 100 ->
  465. error(timeout)
  466. end,
  467. gun:close(ConnPid).