Current section

Files

Jump to
opencensus_plug lib opencensus plug trace.ex
Raw

lib/opencensus/plug/trace.ex

defmodule Opencensus.Plug.Trace do
@moduledoc """
Template method for creating `Plug` to trace your `Plug` requests.
## Usage
1. Create your own `Plug` module:
```elixir
defmodule MyApp.TracingPlug do
use Opencensus.Plug.Trace
end
```
2. Add it to your pipeline, ex. for Phoenix:
```elixir
defmodule MyAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :my_app
plug MyApp.TracingPlug
end
```
## Configuration
This module creates 2 callback modules, which allows you to configure your
span and also provides a way to add custom attributes assigned to span.
- `c:span_name/1` - defaults to request path
- `c:span_status/1` - defaults to mapping of reponse code to OpenCensus span
value, see `:opencensus.http_status_to_trace_status/1`.
And also you can use `attributes` argument in `use` which must be either list
of attributes which are names of 1-argument functions in current module that
must return string value of the attribute, or map/keyword list of one of:
- `atom` - which is name of the called function
- `{module, function}` - which will call `apply(module, function, [conn])`
- `{module, function, args}` - which will prepend `conn` to the given arguments
and call `apply(module, function, [conn | args])`
Example:
```elixir
defmodule MyAppWeb.TraceWithCustomAttribute do
use Opencensus.Plug.Trace, attributes: [:method]
def method(conn), do: conn.method
end
```
"""
@enforce_keys [:span_name, :tags, :conn_fields]
defstruct @enforce_keys
@doc """
Return name for current span. By defaut returns `"plug"`
"""
@callback span_name(Plug.Conn.t(), options :: term()) :: String.t()
@doc """
Return tuple containing span status and message. By default return value
status assigned by [default mapping](https://opencensus.io/tracing/span/status/)
and empty message.
"""
@callback span_status(Plug.Conn.t(), options :: term()) :: {integer(), String.t()}
defmacro __using__(opts) do
attributes = Keyword.get(opts, :attributes, [])
quote do
@behaviour Plug
@behaviour unquote(__MODULE__)
def init(opts), do: opts
def call(conn, opts) do
parent_span_ctx = :oc_propagation_http_tracecontext.from_headers(conn.req_headers)
:ocp.with_span_ctx(parent_span_ctx)
attributes = Opencensus.Plug.get_tags(conn, __MODULE__, unquote(attributes))
:ocp.with_child_span(span_name(conn, opts), attributes)
span_ctx = :ocp.current_span_ctx()
:ok = unquote(__MODULE__).set_logger_metadata(span_ctx)
conn
|> unquote(__MODULE__).put_ctx_resp_header(span_ctx)
|> Plug.Conn.register_before_send(fn conn ->
{status, msg} = span_status(conn, opts)
:oc_trace.set_status(status, msg, span_ctx)
:oc_trace.finish_span(span_ctx)
:ocp.with_span_ctx(parent_span_ctx)
conn
end)
end
def span_name(conn, _opts), do: conn.request_path
def span_status(conn, _opts),
do: {:opencensus.http_status_to_trace_status(conn.status), ""}
defoverridable span_name: 2, span_status: 2
end
end
## PRIVATE
require Record
Record.defrecordp(
:ctx,
Record.extract(:span_ctx, from_lib: "opencensus/include/opencensus.hrl")
)
@doc false
def set_logger_metadata(span) do
trace_id = List.to_string(:io_lib.format("~32.16.0b", [ctx(span, :trace_id)]))
span_id = List.to_string(:io_lib.format("~16.16.0b", [ctx(span, :span_id)]))
Logger.metadata(
trace_id: trace_id,
span_id: span_id,
trace_options: ctx(span, :trace_options)
)
:ok
end
@doc false
def put_ctx_resp_header(conn, span_ctx) do
headers =
for {k, v} <- :oc_propagation_http_tracecontext.to_headers(span_ctx) do
{k, List.to_string(v)}
end
Plug.Conn.prepend_resp_headers(conn, headers)
end
end