README.md (7266B)
1 # Telemetry 2 3 [![Codecov](https://codecov.io/gh/beam-telemetry/telemetry/branch/master/graphs/badge.svg)](https://codecov.io/gh/beam-telemetry/telemetry/branch/master/graphs/badge.svg) 4 5 [Documentation](https://hexdocs.pm/telemetry/) 6 7 Telemetry is a lightweight library for dynamic dispatching of events, with a focus on 8 metrics and instrumentation. Any Erlang or Elixir library can use `telemetry` to emit 9 events. Application code and other libraries can then hook into those events and run 10 custom handlers. 11 12 > Note: this library is agnostic to tooling and therefore is not directly related to 13 > OpenTelemetry. For OpenTelemetry in the Erlang VM, see 14 > [opentelemetry-erlang](https://github.com/open-telemetry/opentelemetry-erlang), and check 15 > [opentelemetry_telemetry](https://github.com/opentelemetry-beam/opentelemetry_telemetry) 16 > to connect both libraries. 17 18 ## Usage 19 20 In a nutshell, you register a custom module and function to be invoked for certain events, 21 which are executed whenever there is such an event. The event name is a list of atoms. Each event is 22 composed of a numeric value and can have metadata attached to it. Let's look at an example. 23 24 Imagine that you have a web application and you'd like to log latency and response status for each 25 incoming request. With Telemetry, you can build a module which does exactly that whenever a response 26 is sent. The first step is to execute a measurement. 27 28 In Elixir: 29 30 ```elixir 31 :telemetry.execute( 32 [:web, :request, :done], 33 %{latency: latency}, 34 %{request_path: path, status_code: status} 35 ) 36 ``` 37 38 In Erlang: 39 40 ```erlang 41 telemetry:execute( 42 [web, request, done], 43 #{latency => Latency}, 44 #{request_path => Path, status_code => Status} 45 ) 46 ``` 47 48 Then you can create a module to be invoked whenever the event happens. 49 50 In Elixir: 51 52 ```elixir 53 defmodule LogResponseHandler do 54 require Logger 55 56 def handle_event([:web, :request, :done], measurements, metadata, _config) do 57 Logger.info( 58 "[#{metadata.request_path}] #{metadata.status_code} sent in #{measurements.latency}" 59 ) 60 end 61 end 62 ``` 63 64 In Erlang: 65 66 ```erlang 67 -module(log_response_handler). 68 69 -include_lib("kernel/include/logger.hrl"). 70 71 handle_event([web, request, done], #{latency := Latency}, #{request_path := Path, 72 status_code := Status}, _Config) -> 73 ?LOG_INFO("[~s] ~p sent in ~p", [Path, Status, Latency]). 74 75 ``` 76 77 **Important note:** 78 79 The `handle_event` callback of each handler is invoked synchronously on each `telemetry:execute` call. 80 Therefore, it is extremely important to avoid blocking operations. If you need to perform any action 81 that is not immediate, consider offloading the work to a separate process (or a pool of processes) 82 by sending a message. 83 84 Finally, all you need to do is to attach the module to the executed event. 85 86 In Elixir: 87 88 ```elixir 89 :ok = 90 :telemetry.attach( 91 # unique handler id 92 "log-response-handler", 93 [:web, :request, :done], 94 &LogResponseHandler.handle_event/4, 95 nil 96 ) 97 ``` 98 99 In Erlang: 100 101 ```erlang 102 ok = telemetry:attach( 103 %% unique handler id 104 <<"log-response-handler">>, 105 [web, request, done], 106 fun log_response_handler:handle_event/4, 107 [] 108 ) 109 ``` 110 111 You might think that it isn't very useful, because you could just as well write a log statement 112 instead of calling `telemetry:execute/3` – and you would be right! But now imagine that each Elixir library 113 would publish its own set of events with information useful for introspection. Currently each library 114 rolls their own instrumentation layer – Telemetry aims to provide a single interface for these use 115 cases across the whole ecosystem. 116 117 ### Spans 118 119 In order to provide uniform events that capture the start and end of discrete events, it is recommended 120 that you use the `telemetry:span/3` call. This function will generate a start event and a stop or exception 121 event depending on whether the provided function executed successfully or raised an error. Under the hood, 122 the `telemetry:span/3` function leverages the `telemetry:execute/3` function, so all the same usage patterns 123 apply. If an exception does occur, an `EventPrefix ++ [exception]` event will be emitted and the caught error 124 will be re-raised. 125 126 The measurements for the `EventPrefix ++ [start]` event will contain a key called `system_time` which is 127 derived by calling `erlang:system_time()`. For `EventPrefix ++ [stop]` and `EventPrefix ++ [exception]` 128 events, the measurements will contain a key called `duration` and its value is derived by calling 129 `erlang:monotonic_time() - StartMonotonicTime`. All events include a `monotonic_time` measurement too. 130 All of them represent time as native units. 131 132 To convert the duration from native units you can use: 133 134 ```elixir 135 milliseconds = System.convert_time_unit(duration, :native, :millisecond) 136 ``` 137 138 To create span events you would do something like this: 139 140 In Elixir: 141 142 ```elixir 143 def process_message(message) do 144 start_metadata = %{message: message} 145 146 result = 147 :telemetry.span( 148 [:worker, :processing], 149 start_metadata, 150 fn -> 151 result = ... # Process the message 152 {result, %{metadata: "Information related to the processing of the message"}} 153 end 154 ) 155 end 156 ``` 157 158 In Erlang: 159 160 ```erlang 161 process_message(Message) -> 162 StartMetadata = #{message => Message}, 163 Result = telemetry:span( 164 [worker, processing], 165 StartMetadata, 166 fun() -> 167 Result = % Process the message 168 {Result, #{metadata => "Information related to the processing of the message"}} 169 end 170 ). 171 ``` 172 173 To then attach to the events that `telemetry:span/3` emits you would do the following: 174 175 In Elixir: 176 177 ```elixir 178 :ok = 179 :telemetry.attach_many( 180 "log-response-handler", 181 [ 182 [:worker, :processing, :start], 183 [:worker, :processing, :stop], 184 [:worker, :processing, :exception] 185 ], 186 &LogResponseHandler.handle_event/4, 187 nil 188 ) 189 ``` 190 191 In Erlang: 192 193 ```erlang 194 ok = telemetry:attach_many( 195 <<"log-response-handler">>, 196 [ 197 [worker, processing, start], 198 [worker, processing, stop], 199 [worker, processing, exception] 200 ], 201 fun log_response_handler:handle_event/4, 202 [] 203 ) 204 ``` 205 206 With the following event handler module defined: 207 208 In Elixir: 209 210 ```elixir 211 defmodule LogResponseHandler do 212 require Logger 213 214 def handle_event(event, measurements, metadata, _config) do 215 Logger.info("Event: #{inspect(event)}") 216 Logger.info("Measurements: #{inspect(measurements)}") 217 Logger.info("Metadata: #{inspect(metadata)}") 218 end 219 end 220 ``` 221 222 In Erlang: 223 224 ```erlang 225 -module(log_response_handler). 226 227 -include_lib("kernel/include/logger.hrl"). 228 229 handle_event(Event, Measurements, Metadata, _Config) -> 230 ?LOG_INFO("Event: ~p", [Event]), 231 ?LOG_INFO("Measurements: ~p", [Measurements]), 232 ?LOG_INFO("Metadata: ~p", [Metadata]). 233 ``` 234 235 ## Installation 236 237 Telemetry is available on [Hex](https://hex.pm/packages/telemetry). To install, just add it to 238 your dependencies in `mix.exs`: 239 240 ```elixir 241 defp deps() do 242 [ 243 {:telemetry, "~> 1.0"} 244 ] 245 end 246 ``` 247 248 or `rebar.config`: 249 250 ```erlang 251 {deps, [{telemetry, "~> 1.0"}]}. 252 ``` 253 254 ## Copyright and License 255 256 Copyright (c) 2018 Chris McCord and Erlang Solutions. 257 258 Telemetry's source code is released under the Apache License, Version 2.0. 259 260 See the [LICENSE](LICENSE) and [NOTICE](NOTICE) files for more information.