Current section

Files

Jump to
tapper lib tapper.ex
Raw

lib/tapper.ex

defmodule Tapper do
@moduledoc """
The high-level client API for Tapper.
## Example
```
# start new trace (and span)
id = Tapper.start(name: "main", type: :client, debug: true, annotations: Tapper.tag("a", "b"))
# or join an existing one
# id = Trapper.join(trace_id, span_id, parent_id, sample, debug, name: "main")
# start child span
id = Tapper.start_span(id, name: "call-out", annotations: [
Tapper.wire_send(),
Tapper.http_path("/resource/1234")
])
# do something
...
# tag current span with some additional metadata, e.g. when available
Tapper.update_span(id, [
:wire_recv,
Tapper.tag("userId", userId),
])
...
# end child span
id = Tapper.finish_span(child_id, annotations: Tapper.http_status(200))
Tapper.finish(span_id) # end trace
```
## Annotations and Updates
Annotations are either *binary* annotations, which have a `type`, `key` and `value`, and an optional endpoint,
or *event* annotations which have a key-like value, and an optional endpoint. Only event annotations have
an associated timestamp (normally automatically generated by the API, or alternatively supplied to `update_span/3`).
The helper functions in this module produce either a binary annotation or an event annotation, and can be
passed through the `annotations` option on any of `start/1`, `join/6`, `start_span/2`, `finish_span/2`
and `finish/2`, or as the second parameter of `update_span/3`. Note that for convenience, all these functions
accept either a list of annotations, or a single annotation.
For event annotations, instead of using the helper function, you can pass an atom, which results in an
event annotation, with the default endpoint. i.e. the following are all equivalent in terms of the annotations
they produce (but not, of course in other ways):
```
Tapper.start_span(id, annotations: [Tapper.server_receive()])
Tapper.start_span(id, annotations: Tapper.server_receive())
Tapper.start_span(id, annotations: [:sr])
Tapper.start_span(id, annotations: :sr)
Tapper.update_span(id, [Tapper.server_receive()])
Tapper.update_span(id, Tapper.server_receive())
Tapper.update_span(id, :sr)
Tapper.update_span(id, [:sr])
```
| Helper | Kind | Value/Key | Short-code(s) |
| ------ | ---- | ---------- | ---- |
| `server_receive/0` | event | `sr` | `:sr`, `:server_recv` |
| `server_send/0` | event | `ss` | `:ss`, `:server_send` |
| `client_receive/0` | event | `cr` | `:cr`, `:client_recv` |
| `client_send/0` | event | `cs` | `:cs`, `:server_send` |
| `wire_send/0` | event | `ws` | `:ws`, `:wire_send` |
| `wire_receive/0` | event | `wr` | `:wr`, `:wire_recv` |
| `error/0` | event | `error` | `:error` |
| `async/0` | event | `async` | N/A |
| `annotation/2` | event | given value | N/A |
| `client_address/1` | binary | `ca` | N/A |
| `server_address/1` | binary | `sa` | N/A |
| `http_host/1` | binary | `http.host` | N/A |
| `http_method/1` | binary | `http.method` | N/A |
| `http_path/1` | binary | `http.path` | N/A |
| `http_url/1` | binary | `http.url` | N/A |
| `http_status_code/1` | binary | `http.status_code` | N/A |
| `http_request_size/1` | binary | `http.request_size` | N/A |
| `http_response_size/1` | binary | `http.response_size` | N/A |
| `sql_query/1` | binary | `sql.query` | N/A |
| `error_message/1` | binary | `error` | N/A |
| `tag/3` | binary | given key | N/A |
| `binary_annotation/4` | binary | given key | N/A |
The general functions, `annotation/2` and `binary_annotation/4` can be used to create any other type
of annotation, and also allow a `Tapper.Endpoint` struct to be specified.
### Other update types with special meanings
* `name/1` does not add an annotation, it sets the name of the span.
* `async/0` adds an `async` event annotation, but also modifies the behaviour of Tapper to support
asynchronous termination of spans. See `Tapper.Tracer.Timeout` for details.
"""
@behaviour Tapper.Tracer.Api
@binary_annotation_types [:string, :bool, :i16, :i32, :i64, :double, :bytes]
alias Tapper.Tracer
alias Tapper.Tracer.Api
@spec start(opts :: Keyword.t) :: Tapper.Id.t
defdelegate start(opts \\ []), to: Tracer
@spec join(trace_id :: Tapper.TraceId.t,
span_id :: Tapper.SpanId.t,
parent_id :: Tapper.SpanId.t | :root,
sample :: boolean(), debug :: boolean(),
opts :: Keyword.t) :: Tapper.Id.t
defdelegate join(trace_id, span_id, parent_id, sample, debug, opts \\ []), to: Tracer
@spec finish(tapper_id :: Tapper.Id.t, opts :: Keyword.t) :: :ok
defdelegate finish(id, opts \\ []), to: Tracer
@spec start_span(tapper_id :: Tapper.Id.t, opts :: Keyword.t) :: Tapper.Id.t
defdelegate start_span(id, opts \\ []), to: Tracer
@spec finish_span(tapper_id :: Tapper.Id.t, opts :: Keyword.t) :: Tapper.Id.t
defdelegate finish_span(id, opts \\ []), to: Tracer
@spec update_span(tapper_id :: Tapper.Id.t, deltas :: Api.delta | [Api.delta], opts :: Keyword.t) :: Tapper.Id.t
defdelegate update_span(id, deltas, opts \\ []), to: Tracer
@doc "Annotation helper: name (or rename) the current span."
@spec name(name :: String.t | atom) :: Api.name_delta
def name(name), do: Tracer.name_delta(name)
@doc """
Annotation helper: mark the span as asynchronous, and add an `async` event annotation.
Adding this annotation is semantically equivalent to calling `finish/2` with the `async` option, and
instigates the same time-out behaviour.
You would probably add one of these to every asynchronous span, so we know which spans were async.
Ensure that child spans, and the whole trace, are finished as normal.
## Example
```
# start a trace
id = Tapper.start(name: "main")
# spawn a task
ref = Task.start(fn ->
# start a child span in the task
child_id = Tapper.start_span(id, "fetch", annotations: Tapper.async())
res = do_something()
{res, child_id}
end)
# finish the trace, this won't send the spans; note we don't need async: true
# if Task has already called start_span(), but it might not have, so safer to do so!
Tapper.finish(id, async: true)
...
# await the result from our task
{res, span_id} = Task.await(ref)
send_result_to_queue_or_something(res)
# finish the child span; will complete trace and send spans
Tapper.finish_span(span_id)
```
## See also
* `Tapper.Tracer.Timeout`
* `Tapper.finish/2`
"""
@spec async() :: Api.async_delta
def async, do: Tracer.async_delta()
@doc "Annotation helper: create a server_receive event (`sr` event annotation); see also `:server` option on `Tapper.start/1`."
@spec server_receive() :: Api.annotation_delta
def server_receive, do: Tracer.annotation_delta(:sr)
@doc "Annotation helper: create a server_send event (`ss` event annotation)."
@spec server_send() :: Api.annotation_delta
def server_send, do: Tracer.annotation_delta(:ss)
@doc "Annotation helper: create a client_send event (`cs` event annotation); see also `:client` option on `Tapper.start/1`."
@spec client_send() :: Api.annotation_delta
def client_send, do: Tracer.annotation_delta(:cs)
@doc "Annotation helper: create a client_receive event (`cr` event annotation)."
@spec client_receive() :: Api.annotation_delta
def client_receive, do: Tracer.annotation_delta(:cr)
@doc "Annotation helper: create a send event (`ws` event annotation)."
@spec wire_send() :: Api.annotation_delta
def wire_send, do: Tracer.annotation_delta(:ws)
@doc "Annotation helper: create a receive event (`wr` event annotation)."
@spec wire_receive() :: Api.annotation_delta
def wire_receive, do: Tracer.annotation_delta(:wr)
@doc "Annotation helper: create an error event (`error` event annotation)."
@spec error() :: Api.annotation_delta
def error, do: Tracer.annotation_delta(:error)
@doc "Annotation helper: tag the client's address (`ca` binary annotation)."
@spec client_address(endpoint :: Tapper.Endpoint.t) :: Api.binary_annotation_delta
def client_address(endpoint), do: Tracer.binary_annotation_delta(:bool, :ca, true, endpoint)
@doc "Annotation helper: tag with the server's address (`sa` binary annotation)."
@spec server_address(endpoint :: Tapper.Endpoint.t) :: Api.binary_annotation_delta
def server_address(endpoint), do: Tracer.binary_annotation_delta(:bool, :sa, true, endpoint)
@doc "Annotation helper: tag with HTTP host information (`http.host` binary annotation)."
@spec http_host(hostname :: String.t) :: Api.binary_annotation_delta
def http_host(hostname) when is_binary(hostname), do: Tracer.binary_annotation_delta(:string, "http.host", hostname)
@doc "Annotation helper: tag with HTTP method information (`http.method` binary annotation)."
@spec http_method(method :: String.t | atom) :: Api.binary_annotation_delta
def http_method(method) when is_binary(method) or is_atom(method), do: Tracer.binary_annotation_delta(:string, "http.method", method)
@doc "Annotation helper: tag with HTTP path information: should be without query parameters (`http.path` binary annotation)"
@spec http_path(path :: String.t) :: Api.binary_annotation_delta
def http_path(path) when is_binary(path), do: Tracer.binary_annotation_delta(:string, "http.path", path)
@doc "Annotation helper: tag with full HTTP URL information (`http.url` binary annotation)"
@spec http_url(url :: String.t) :: Api.binary_annotation_delta
def http_url(url) when is_binary(url), do: Tracer.binary_annotation_delta(:string, "http.url", url)
@doc "Annotation helper: tag with an HTTP status code (`http.status_code` binary annotation)"
@spec http_status_code(code :: integer()) :: Api.binary_annotation_delta
def http_status_code(code) when is_integer(code), do: Tracer.binary_annotation_delta(:i16, "http.status_code", code)
@doc "Annotation helper: tag with an HTTP request size (`http.request.size` binary annotation)"
@spec http_request_size(size :: integer()) :: Api.binary_annotation_delta
def http_request_size(size) when is_integer(size), do: Tracer.binary_annotation_delta(:i64, "http.request.size", size)
@doc "Annotation helper: tag with an HTTP reponse size (`http.response.size` binary annotation)"
@spec http_response_size(size :: integer()) :: Api.binary_annotation_delta
def http_response_size(size) when is_integer(size), do: Tracer.binary_annotation_delta(:i64, "http.response.size", size)
@doc "Annotation helper: tag with a database query (`sql.query` binary annotation)"
@spec sql_query(query :: String.t) :: Api.binary_annotation_delta
def sql_query(query) when is_binary(query), do: Tracer.binary_annotation_delta(:string, "sql.query", query)
@doc "Annotation helper: tag with an error message (`error` binary annotation)"
@spec error_message(message :: String.t) :: Api.binary_annotation_delta
def error_message(message) when is_binary(message), do: Tracer.binary_annotation_delta(:string, :error, message)
@doc "Annotation helper: tag with a general (key,value,host) binary annotation, determining type of annotation automatically"
@spec tag(key :: Api.binary_annotation_key, value :: Api.binary_annotation_value, endpoint :: Api.maybe_endpoint) :: Api.binary_annotation_delta
def tag(key, value, endpoint \\ nil)
def tag(key, value, endpoint) when (is_binary(key) or is_atom(key)) and is_binary(value), do: Tracer.binary_annotation_delta(:string, key, value, endpoint)
def tag(key, value, endpoint) when (is_binary(key) or is_atom(key)) and is_boolean(value), do: Tracer.binary_annotation_delta(:bool, key, value, endpoint)
def tag(key, value, endpoint) when (is_binary(key) or is_atom(key)) and is_integer(value), do: Tracer.binary_annotation_delta(:i64, key, value, endpoint)
def tag(key, value, endpoint) when (is_binary(key) or is_atom(key)) and is_float(value), do: Tracer.binary_annotation_delta(:double, key, value, endpoint)
def tag(key, value, endpoint) when (is_binary(key) or is_atom(key)), do: Tracer.binary_annotation_delta(:string, key, inspect(value), endpoint)
@doc "Annotation helper: create an event annotation, general interface."
@spec annotation(value :: Api.annotation_value, endpoint :: Api.maybe_endpoint) :: Api.annotation_delta
def annotation(value, endpoint \\ nil) when is_atom(value) or is_binary(value), do: Tracer.annotation_delta(value, endpoint)
@doc """
Annotation helper: create a general binary annotation.
`type` is one of: `:string`, `:bool`, `:i16`, `:i32`, `:i64`, `:double`, `:bytes`
## Example
```
binary_annotation(id, :i16, "tab", 4)
```
"""
@spec binary_annotation(type :: Api.binary_annotation_type, key :: Api.binary_annotation_key, value :: Api.binary_annotation_value, endpoint :: Api.maybe_endpoint) :: Api.binary_annotation_delta
def binary_annotation(type, key, value, endpoint \\ nil) when type in @binary_annotation_types do
Tracer.binary_annotation_delta(type, key, value, endpoint)
end
end