defmodule OpentelemetryBreathalyzer do @moduledoc """ Breathalyzer is an OpenTelemetry tracer for Absinthe. * Add it to your dependencies: ``` defp deps do [ ..., {:opentelemetry_breathalyzer, "~> 0.1.0"} ] end ``` * Configure it: ``` config :opentelemetry_breathalyzer, :trace, execute_operation: [ request_document: true, request_schema: true, request_selections: false, request_variables: true, request_complexity: false, response_errors: true, response_result: false, request_context: [[:current_user, :id]] ] ``` * And setup it just before your application supervisor starts: ``` defmodule MyApplication do def start(_type, args) do opts = [strategy: :one_for_one, name: MySupervisor] OpentelemetryBreathalyzer.setup() Supervisor.start_link(children(args), opts) end ``` * You may also choose to start only a couple of handlers: ``` defmodule MyApplication do def start(_type, args) do opts = [strategy: :one_for_one, name: MySupervisor] OpentelemetryBreathalyzer.setup(only: [:execute_operation, :execute_middleware]]) Supervisor.start_link(children(args), opts) end ``` * There you go. You should start seeing traces popping into your OpenTelemetry store. """ # Absinthe Telemetry documentation: # https://hexdocs.pm/absinthe/telemetry.html require Logger alias __MODULE__.{ ExecuteOperation, ResolveField, ExecuteMiddleware } @default_config [ execute_operation: [ request_document: true, request_schema: true, request_selections: false, request_variables: true, request_complexity: false, response_errors: true, response_result: false, request_context: [] ] ] @default_handlers [ :execute_operation, :resolve_field, :execute_middleware ] def setup(instrumentation_opts \\ []) do config = Application.get_env(:opentelemetry_breathalyzer, :trace, @default_config) Keyword.get(instrumentation_opts, :only, @default_handlers) |> Enum.each(fn atom when is_atom(atom) -> attach_handler(atom, config[atom]) _ -> raise "Unsupported :only configuration. Supported values are: :execute_operation, :resolve_field, :execute_middleware" end) end def teardown do detach_handler(:execute_operation) detach_handler(:resolve_field) detach_handler(:execute_middleware) end def attach_handler(type, config \\ %{}) def attach_handler(:execute_operation, config) do with :ok <- :telemetry.attach( {ExecuteOperation, :start}, [:absinthe, :execute, :operation, :start], &ExecuteOperation.handle_start/4, config ), :ok <- :telemetry.attach( {ExecuteOperation, :stop}, [:absinthe, :execute, :operation, :stop], &ExecuteOperation.handle_stop/4, config ) do :ok end end def attach_handler(:resolve_field, config) do with :ok <- :telemetry.attach( {ResolveField, :start}, [:absinthe, :resolve, :field, :start], &ResolveField.handle_start/4, config ), :ok <- :telemetry.attach( {ResolveField, :stop}, [:absinthe, :resolve, :field, :stop], &ResolveField.handle_stop/4, config ) do :ok end end def attach_handler(:execute_middleware, config) do with :ok <- :telemetry.attach( {ExecuteMiddleware, :start}, [:absinthe, :middleware, :batch, :start], &ExecuteMiddleware.handle_start/4, config ), :ok <- :telemetry.attach( {ExecuteMiddleware, :stop}, [:absinthe, :middleware, :batch, :stop], &ExecuteMiddleware.handle_stop/4, config ) do :ok end end def detach_handler(:execute_operation) do :telemetry.detach({ExecuteOperation, :start}) :telemetry.detach({ExecuteOperation, :stop}) end def detach_handler(:resolve_field) do :telemetry.detach({ResolveField, :start}) :telemetry.detach({ResolveField, :stop}) end def detach_handler(:execute_middleware) do :telemetry.detach({ExecuteMiddleware, :start}) :telemetry.detach({ExecuteMiddleware, :stop}) end end