├── rebar.config ├── .gitignore ├── src ├── cowboy_telemetry.app.src └── cowboy_telemetry_h.erl ├── .github └── workflows │ └── erlang.yml ├── test ├── test_h.erl └── cowboy_telemetry_h_SUITE.erl ├── rebar.lock ├── README.md └── LICENSE /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {project_plugins, [rebar3_hex]}. 3 | {deps, [ 4 | {telemetry, "~> 1.0"}, 5 | {cowboy, "~> 2.7"} 6 | ]}. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | erl_crash.dump 13 | .rebar 14 | logs 15 | _build 16 | .idea 17 | *.iml 18 | rebar3.crashdump 19 | *~ 20 | -------------------------------------------------------------------------------- /src/cowboy_telemetry.app.src: -------------------------------------------------------------------------------- 1 | {application, cowboy_telemetry, 2 | [{description, "Telemetry instrumentation for Cowboy"}, 3 | {vsn, "0.4.0"}, 4 | {registered, []}, 5 | {applications, 6 | [kernel, 7 | stdlib, 8 | telemetry 9 | ]}, 10 | {env,[]}, 11 | {modules, []}, 12 | {licenses, ["Apache 2.0"]}, 13 | {links, [{"Github", "https://github.com/beam-telemetry/cowboy_telemetry"}]} 14 | ]}. 15 | -------------------------------------------------------------------------------- /.github/workflows/erlang.yml: -------------------------------------------------------------------------------- 1 | name: Erlang CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | container: 16 | image: erlang:22.0.7 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Compile 21 | run: rebar3 compile 22 | - name: Run tests 23 | run: rebar3 do eunit, ct 24 | -------------------------------------------------------------------------------- /test/test_h.erl: -------------------------------------------------------------------------------- 1 | -module(test_h). 2 | -behaviour(cowboy_handler). 3 | 4 | -export([init/2]). 5 | 6 | init(_, failure) -> 7 | error(failure); 8 | init(Req, success = Opts) -> 9 | {ok, cowboy_req:reply(200, #{}, <<"Hello world!">>, Req), Opts}; 10 | init(Req, slow = Opts) -> 11 | timer:sleep(200), 12 | {ok, cowboy_req:reply(200, #{}, <<"I'm slow">>, Req), Opts}; 13 | init(Req0, chunked = Opts) -> 14 | Req = cowboy_req:stream_reply(200, Req0), 15 | cowboy_req:stream_body("Hello\r\n", nofin, Req), 16 | cowboy_req:stream_body("World\r\n", fin, Req), 17 | {ok, Req, Opts}; 18 | init(Req0, chunked_slow = Opts) -> 19 | Req = cowboy_req:stream_reply(200, Req0), 20 | cowboy_req:stream_body("Hello\r\n", nofin, Req), 21 | timer:sleep(200), 22 | cowboy_req:stream_body("World\r\n", fin, Req), 23 | {ok, Req, Opts}. 24 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | {"1.2.0", 2 | [{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.8.0">>},0}, 3 | {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.9.1">>},1}, 4 | {<<"ranch">>,{pkg,<<"ranch">>,<<"1.7.1">>},1}, 5 | {<<"telemetry">>,{pkg,<<"telemetry">>,<<"1.0.0">>},0}]}. 6 | [ 7 | {pkg_hash,[ 8 | {<<"cowboy">>, <<"F3DC62E35797ECD9AC1B50DB74611193C29815401E53BAC9A5C0577BD7BC667D">>}, 9 | {<<"cowlib">>, <<"61A6C7C50CF07FDD24B2F45B89500BB93B6686579B069A89F88CB211E1125C78">>}, 10 | {<<"ranch">>, <<"6B1FAB51B49196860B733A49C07604465A47BDB78AA10C1C16A3D199F7F8C881">>}, 11 | {<<"telemetry">>, <<"0F453A102CDF13D506B7C0AB158324C337C41F1CC7548F0BC0E130BBF0AE9452">>}]}, 12 | {pkg_hash_ext,[ 13 | {<<"cowboy">>, <<"4643E4FBA74AC96D4D152C75803DE6FAD0B3FA5DF354C71AFDD6CBEEB15FAC8A">>}, 14 | {<<"cowlib">>, <<"E4175DC240A70D996156160891E1C62238EDE1729E45740BDD38064DAD476170">>}, 15 | {<<"ranch">>, <<"451D8527787DF716D99DC36162FCA05934915DB0B6141BBDAC2EA8D3C7AFC7D7">>}, 16 | {<<"telemetry">>, <<"73BC09FA59B4A0284EFB4624335583C528E07EC9AE76ACA96EA0673850AEC57A">>}]} 17 | ]. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cowboy_telemetry 2 | ===== 3 | 4 | [![Hex.pm Version](https://img.shields.io/hexpm/v/cowboy_telemetry.svg)](https://hex.pm/packages/cowboy_telemetry) 5 | [![Erlang CI](https://github.com/beam-telemetry/cowboy_telemetry/workflows/Erlang%20CI/badge.svg?branch=main)](https://github.com/beam-telemetry/cowboy_telemetry/actions) 6 | 7 | [Telemetry](https://github.com/beam-telemetry/telemetry) instrumentation for the [Cowboy](https://github.com/ninenines/cowboy) HTTP server. 8 | 9 | This package contains a [`cowboy_stream`](https://ninenines.eu/docs/en/cowboy/2.8/manual/cowboy_stream/) handler that will instrument each request and emit `telemetry` events. 10 | 11 | ## Usage 12 | 13 | Configure your cowboy server with the `cowboy_telemetry_h` stream handler first. 14 | 15 | ```erlang 16 | cowboy:start_clear(http, [{port, Port}], #{ 17 | env => #{dispatch => Dispatch}, 18 | stream_handlers => [cowboy_telemetry_h, cowboy_stream_h] 19 | }. 20 | ``` 21 | 22 | ## Telemetry Events 23 | 24 | #### `[cowboy, request, start]` 25 | 26 | A span event emitted at the beginning of a request. 27 | 28 | * `measurements`: `#{system_time => erlang:system_time()}` 29 | * `metadata`: `#{stream_id => cowboy_stream:streamid(), req => cowboy_req:req()}` 30 | 31 | #### `[cowboy, request, stop]` 32 | 33 | A span event emitted at the end of a request. 34 | 35 | * `measurements`: `measurements()` 36 | * `metadata`: `metadata()` 37 | 38 | If the request is terminated early - by the client or by the server - before a response is sent, the metadata will also contain an `error`: 39 | 40 | * `metadata`: `metadata()` + `#{error => cowboy_stream:reason()}` 41 | 42 | #### `[cowboy, request, exception]` 43 | 44 | A span event emitted if the request process exits. 45 | 46 | * `measurements`: `measurements()` 47 | * `metadata`: `metadata()` + `#{kind => exit, stacktrace => list()}` 48 | 49 | #### `[cowboy, request, early_error]` 50 | 51 | A single event emitted when Cowboy itself returns an `early_error` response before executing any handlers. 52 | 53 | * `measurements`: `#{system_time => erlang:system_time(), resp_body_length => non_neg_integer()}` 54 | * `metadata`: `metadata()` without `procs` or `informational` 55 | 56 | ### Types 57 | 58 | * `measurements()`: 59 | * `duration :: req_start - req_end` see [`cowboy_metrics_h`](https://github.com/ninenines/cowboy/blob/master/src/cowboy_metrics_h.erl#L75) 60 | * `req_body_duration :: req_body_start - req_body_end` see [`cowboy_metrics_h`](https://github.com/ninenines/cowboy/blob/master/src/cowboy_metrics_h.erl#L80) 61 | * `resp_duration :: resp_start - resp_end` see [`cowboy_metrics_h`](https://github.com/ninenines/cowboy/blob/master/src/cowboy_metrics_h.erl#L87) 62 | * `req_body_length :: non_neg_integer()` 63 | * `resp_body_length :: non_neg_integer()` 64 | * `metadata()`: 65 | * `pid`, `streamid`, `req`, `resp_headers`, `resp_status`, `ref` and `user_data` from `cowboy_metrics_h:metrics()` 66 | * `cowboy_metrics_h:metrics()`: Defined in [`cowboy_metrics_h`](https://github.com/ninenines/cowboy/blob/master/src/cowboy_metrics_h.erl#L46) 67 | 68 | Note: 69 | 70 | * The `telemetry` handlers are executed from the cowboy connection process, not from the request process. 71 | -------------------------------------------------------------------------------- /src/cowboy_telemetry_h.erl: -------------------------------------------------------------------------------- 1 | -module(cowboy_telemetry_h). 2 | -behavior(cowboy_stream). 3 | 4 | -export([init/3]). 5 | -export([data/4]). 6 | -export([info/3]). 7 | -export([terminate/3]). 8 | -export([early_error/5]). 9 | 10 | init(StreamID, Req, Opts) -> 11 | telemetry:execute( 12 | [cowboy, request, start], 13 | #{system_time => erlang:system_time()}, 14 | #{streamid => StreamID, req => Req}), 15 | cowboy_metrics_h:init(StreamID, Req, add_metrics_callback(Opts)). 16 | 17 | info(StreamID, Info, State) -> 18 | cowboy_metrics_h:info(StreamID, Info, State). 19 | 20 | data(StreamID, IsFin, Data, State) -> 21 | cowboy_metrics_h:data(StreamID, IsFin, Data, State). 22 | 23 | terminate(StreamID, Reason, State) -> 24 | cowboy_metrics_h:terminate(StreamID, Reason, State). 25 | 26 | early_error(StreamID, Reason, PartialReq, Resp, Opts) -> 27 | cowboy_metrics_h:early_error(StreamID, Reason, PartialReq, Resp, add_metrics_callback(Opts)). 28 | 29 | % 30 | 31 | add_metrics_callback(Opts) -> 32 | maps:put(metrics_callback, fun metrics_callback/1, Opts). 33 | 34 | metrics_callback(#{early_error_time := Time} = Metrics) when is_number(Time) -> 35 | {RespBodyLength, Metadata} = maps:take(resp_body_length, Metrics), 36 | telemetry:execute( 37 | [cowboy, request, early_error], 38 | #{system_time => erlang:system_time(), resp_body_length => RespBodyLength}, 39 | Metadata); 40 | metrics_callback(#{reason := {internal_error, {'EXIT', _, {Reason, Stacktrace}}, _}} = Metrics) -> 41 | telemetry:execute( 42 | [cowboy, request, exception], 43 | measurements(Metrics), 44 | (metadata(Metrics))#{kind => exit, reason => Reason, stacktrace => Stacktrace}); 45 | metrics_callback(#{reason := {ErrorType, _, _} = Reason} = Metrics) 46 | when ErrorType == socket_error; 47 | ErrorType == connection_error -> 48 | telemetry:execute( 49 | [cowboy, request, stop], 50 | measurements(Metrics), 51 | (metadata(Metrics))#{error => Reason}); 52 | metrics_callback(Metrics) -> 53 | telemetry:execute( 54 | [cowboy, request, stop], 55 | measurements(Metrics), 56 | metadata(Metrics)). 57 | 58 | measurements(Metrics) -> 59 | #{req_body_length := ReqBodyLength, resp_body_length := RespBodyLength} = Metrics, 60 | 61 | #{ 62 | duration => duration(req_start, req_end, Metrics), 63 | req_body_duration => duration(req_body_start, req_body_end, Metrics), 64 | resp_duration => duration(resp_start, resp_end, Metrics), 65 | req_body_length => ReqBodyLength, 66 | resp_body_length => RespBodyLength 67 | }. 68 | 69 | metadata(Metrics) -> 70 | #{ 71 | pid := Pid, 72 | streamid := Streamid, 73 | req := Req, 74 | resp_headers := RespHeaders, 75 | resp_status := RespStatus, 76 | ref := Ref, 77 | user_data := UserData 78 | } = Metrics, 79 | 80 | #{ 81 | pid => Pid, 82 | streamid => Streamid, 83 | req => Req, 84 | resp_headers => RespHeaders, 85 | resp_status => RespStatus, 86 | ref => Ref, 87 | user_data => UserData 88 | }. 89 | 90 | duration(StartKey, EndKey, Metrics) -> 91 | case Metrics of 92 | #{StartKey := Start, EndKey := End} when is_integer(Start), is_integer(End) -> End - Start; 93 | #{} -> 0 94 | end. 95 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2020, Anonymous . 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | 192 | -------------------------------------------------------------------------------- /test/cowboy_telemetry_h_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(cowboy_telemetry_h_SUITE). 2 | 3 | -compile(export_all). 4 | -compile(nowarn_export_all). 5 | 6 | -include_lib("common_test/include/ct.hrl"). 7 | -include_lib("stdlib/include/assert.hrl"). 8 | 9 | all() -> 10 | [ 11 | successful_request, 12 | failed_request, 13 | chunked_request, 14 | client_timeout_request, 15 | idle_timeout_request, 16 | chunk_timeout_request, 17 | bad_request 18 | ]. 19 | 20 | init_per_suite(Config) -> 21 | application:ensure_all_started(ranch), 22 | application:ensure_all_started(telemetry), 23 | Dispatch = cowboy_router:compile([{"localhost", [ 24 | {"/success", test_h, success}, 25 | {"/chunked", test_h, chunked}, 26 | {"/chunked_slow", test_h, chunked_slow}, 27 | {"/slow", test_h, slow}, 28 | {"/failure", test_h, failure} 29 | ]}]), 30 | {ok, _} = cowboy:start_clear(http, [{port, 8080}], #{ 31 | env => #{dispatch => Dispatch}, 32 | stream_handlers => [cowboy_telemetry_h, cowboy_stream_h], 33 | idle_timeout => 150 34 | } 35 | ), 36 | Config. 37 | 38 | end_per_suite(_Config) -> 39 | application:stop(ranch), 40 | application:stop(telemetry). 41 | 42 | successful_request(_Config) -> 43 | Events = [ 44 | [cowboy, request, start], 45 | [cowboy, request, stop], 46 | [cowboy, request, exception] 47 | ], 48 | telemetry:attach_many(successful_request, Events, fun ?MODULE:echo_event/4, self()), 49 | {ok, {{_Version, 200, _ReasonPhrase}, _Headers, _Body}} = 50 | httpc:request(get, {"http://localhost:8080/success", []}, [], []), 51 | receive 52 | {[cowboy, request, start], StartMeasurements, StartMetadata} -> 53 | ?assertEqual([system_time], maps:keys(StartMeasurements)), 54 | ?assertEqual([req, streamid], maps:keys(StartMetadata)) 55 | after 56 | 1000 -> ct:fail(successful_request_start_event) 57 | end, 58 | receive 59 | {[cowboy, request, stop], StopMeasurements, StopMetadata} -> 60 | ?assert(is_map_key(duration, StopMeasurements)), 61 | ?assert(is_map_key(req_body_duration, StopMeasurements)), 62 | ?assert(is_map_key(req_body_length, StopMeasurements)), 63 | ?assert(is_map_key(resp_duration, StopMeasurements)), 64 | ?assert(is_map_key(resp_body_length, StopMeasurements)), 65 | % 66 | ?assert(is_map_key(streamid, StopMetadata)), 67 | ?assert(is_map_key(req, StopMetadata)), 68 | ?assert(is_map_key(ref, StopMetadata)), 69 | ?assert(is_map_key(pid, StopMetadata)), 70 | ?assert(is_map_key(resp_headers, StopMetadata)), 71 | ?assert(is_map_key(resp_status, StopMetadata)), 72 | ?assert(is_map_key(user_data, StopMetadata)) 73 | after 74 | 1000 -> ct:fail(successful_request_stop_event) 75 | end, 76 | receive 77 | {[cowboy, request, exception], _, _} -> 78 | ct:fail(successful_request_unexpected_exception_event) 79 | after 80 | 100 -> ok 81 | end. 82 | 83 | chunked_request(_Config) -> 84 | Events = [ 85 | [cowboy, request, start], 86 | [cowboy, request, stop], 87 | [cowboy, request, exception] 88 | ], 89 | telemetry:attach_many(chunked_request, Events, fun ?MODULE:echo_event/4, self()), 90 | {ok, {{_Version, 200, _ReasonPhrase}, _Headers, _Body}} = 91 | httpc:request(get, {"http://localhost:8080/chunked", []}, [], []), 92 | receive 93 | {[cowboy, request, start], StartMeasurements, StartMetadata} -> 94 | ?assertEqual([system_time], maps:keys(StartMeasurements)), 95 | ?assertEqual([req, streamid], maps:keys(StartMetadata)) 96 | after 97 | 1000 -> ct:fail(chunked_request_start_event) 98 | end, 99 | receive 100 | {[cowboy, request, stop], StopMeasurements, StopMetadata} -> 101 | ?assert(is_map_key(duration, StopMeasurements)), 102 | ?assert(is_map_key(streamid, StopMetadata)) 103 | after 104 | 1000 -> ct:fail(chunked_request_stop_event) 105 | end, 106 | receive 107 | {[cowboy, request, exception], _, _} -> 108 | ct:fail(chunked_request_unexpected_exception_event) 109 | after 110 | 100 -> ok 111 | end. 112 | 113 | failed_request(_Config) -> 114 | Events = [ 115 | [cowboy, request, start], 116 | [cowboy, request, stop], 117 | [cowboy, request, exception] 118 | ], 119 | telemetry:attach_many(failed_request, Events, fun ?MODULE:echo_event/4, self()), 120 | {ok, {{_Version, 500, _ReasonPhrase}, _Headers, _Body}} = 121 | httpc:request(get, {"http://localhost:8080/failure", []}, [], []), 122 | receive 123 | {[cowboy, request, start], StartMeasurements, StartMetadata} -> 124 | ?assertEqual([system_time], maps:keys(StartMeasurements)), 125 | ?assertEqual([req, streamid], maps:keys(StartMetadata)) 126 | after 127 | 1000 -> ct:fail(failed_request_start_event) 128 | end, 129 | receive 130 | {[cowboy, request, exception], ExceptionMeasurements, ExceptionMetadata} -> 131 | ?assert(is_map_key(duration, ExceptionMeasurements)), 132 | ?assert(is_map_key(streamid, ExceptionMetadata)), 133 | ?assert(is_map_key(kind, ExceptionMetadata)), 134 | ?assert(is_map_key(reason, ExceptionMetadata)), 135 | ?assert(is_map_key(stacktrace, ExceptionMetadata)), 136 | ?assert(is_map_key(user_data, ExceptionMetadata)) 137 | after 138 | 1000 -> ct:fail(failed_request_exception_event) 139 | end, 140 | receive 141 | {[cowboy, request, stop], _, _} -> 142 | ct:fail(failed_request_unexpected_stop_event) 143 | after 144 | 100 -> ok 145 | end. 146 | 147 | client_timeout_request(_Config) -> 148 | Events = [ 149 | [cowboy, request, start], 150 | [cowboy, request, stop], 151 | [cowboy, request, exception] 152 | ], 153 | telemetry:attach_many(client_timeout_request, Events, fun ?MODULE:echo_event/4, self()), 154 | {error, timeout} = 155 | httpc:request(get, {"http://localhost:8080/slow", []}, [{timeout, 50}], []), 156 | receive 157 | {[cowboy, request, start], StartMeasurements, StartMetadata} -> 158 | ?assertEqual([system_time], maps:keys(StartMeasurements)), 159 | ?assertEqual([req, streamid], maps:keys(StartMetadata)) 160 | after 161 | 1000 -> ct:fail(client_timeout_request_start_event) 162 | end, 163 | receive 164 | {[cowboy, request, stop], StopMeasurements, StopMetadata} -> 165 | ?assert(is_map_key(duration, StopMeasurements)), 166 | ?assert(is_map_key(streamid, StopMetadata)), 167 | ?assert(is_map_key(error, StopMetadata)), 168 | ?assert(is_map_key(user_data, StopMetadata)) 169 | after 170 | 1000 -> ct:fail(client_timeout_request_stop_event) 171 | end, 172 | receive 173 | {[cowboy, request, exception], _, _} -> 174 | ct:fail(client_timeout_request_unexpected_exception_event) 175 | after 176 | 100 -> ok 177 | end. 178 | 179 | idle_timeout_request(_Config) -> 180 | Events = [ 181 | [cowboy, request, start], 182 | [cowboy, request, stop], 183 | [cowboy, request, exception] 184 | ], 185 | telemetry:attach_many(idle_timeout_request, Events, fun ?MODULE:echo_event/4, self()), 186 | {error, socket_closed_remotely} = 187 | httpc:request(head, {"http://localhost:8080/slow", []}, [], []), 188 | receive 189 | {[cowboy, request, start], StartMeasurements, StartMetadata} -> 190 | ?assertEqual([system_time], maps:keys(StartMeasurements)), 191 | ?assertEqual([req, streamid], maps:keys(StartMetadata)) 192 | after 193 | 1000 -> ct:fail(idle_timeout_request_start_event) 194 | end, 195 | receive 196 | {[cowboy, request, stop], StopMeasurements, StopMetadata} -> 197 | ?assert(is_map_key(duration, StopMeasurements)), 198 | ?assert(is_map_key(streamid, StopMetadata)), 199 | ?assert(is_map_key(error, StopMetadata)), 200 | ?assert(is_map_key(user_data, StopMetadata)) 201 | after 202 | 1000 -> ct:fail(idle_timeout_request_stop_event) 203 | end, 204 | receive 205 | {[cowboy, request, exception], _, _} -> 206 | ct:fail(idle_timeout_request_unexpected_exception_event) 207 | after 208 | 100 -> ok 209 | end. 210 | 211 | chunk_timeout_request(_Config) -> 212 | Events = [ 213 | [cowboy, request, start], 214 | [cowboy, request, stop], 215 | [cowboy, request, exception] 216 | ], 217 | telemetry:attach_many(chunk_timeout_request, Events, fun ?MODULE:echo_event/4, self()), 218 | httpc:request(head, {"http://localhost:8080/chunked_slow", []}, [], []), 219 | receive 220 | {[cowboy, request, start], StartMeasurements, StartMetadata} -> 221 | ?assertEqual([system_time], maps:keys(StartMeasurements)), 222 | ?assertEqual([req, streamid], maps:keys(StartMetadata)) 223 | after 224 | 1000 -> ct:fail(chunk_timeout_request_start_event) 225 | end, 226 | receive 227 | {[cowboy, request, stop], StopMeasurements, StopMetadata} -> 228 | ?assert(is_map_key(duration, StopMeasurements)), 229 | ?assert(is_map_key(streamid, StopMetadata)), 230 | ?assert(is_map_key(error, StopMetadata)), 231 | ?assert(is_map_key(user_data, StopMetadata)) 232 | after 233 | 1000 -> ct:fail(chunk_timeout_request_stop_event) 234 | end, 235 | receive 236 | {[cowboy, request, exception], _, _} -> 237 | ct:fail(chunk_timeout_request_unexpected_exception_event) 238 | after 239 | 100 -> ok 240 | end. 241 | 242 | bad_request(_Config) -> 243 | Events = [ 244 | [cowboy, request, early_error], 245 | [cowboy, request, start], 246 | [cowboy, request, stop], 247 | [cowboy, request, exception] 248 | ], 249 | telemetry:attach_many(bad_request, Events, fun ?MODULE:echo_event/4, self()), 250 | {ok, {{_Version, 501, _ReasonPhrase}, _Headers, _Body}} = 251 | httpc:request(trace, {"http://localhost:8080/", []}, [], []), 252 | receive 253 | {[cowboy, request, early_error], EarlyErrorMeasurements, EarlyErrorMetadata} -> 254 | ?assert(is_map_key(system_time, EarlyErrorMeasurements)), 255 | ?assert(is_map_key(resp_body_length, EarlyErrorMeasurements)), 256 | ?assert(is_map_key(streamid, EarlyErrorMetadata)) 257 | after 258 | 1000 -> ct:fail(bad_request_start_event) 259 | end, 260 | receive 261 | {[cowboy, request, start], _, _} -> 262 | ct:fail(bad_request_unexpected_start_event) 263 | after 264 | 100 -> ok 265 | end, 266 | receive 267 | {[cowboy, request, stop], _, _} -> 268 | ct:fail(bad_request_unexpected_stop_event) 269 | after 270 | 100 -> ok 271 | end, 272 | receive 273 | {[cowboy, request, exception], _, _} -> 274 | ct:fail(bad_request_unexpected_exception_event) 275 | after 276 | 100 -> ok 277 | end. 278 | 279 | echo_event(Event, Measurements, Metadata, Pid) -> 280 | Pid ! {Event, Measurements, Metadata}. 281 | --------------------------------------------------------------------------------