Current section
Files
Jump to
Current section
Files
lib/opentelemetry_mongodb_driver.ex
defmodule OpentelemetryMongodbDriver do
@moduledoc """
OpentelemetryMongodbDriver uses [telemetry](https://hexdocs.pm/telemetry/) handlers to
and Mongo a event listener create `OpenTelemetry` spans.
## Usage
In your application start:
def start(_type, _args) do
OpentelemetryMongodbDriver.setup()
# ...
end
"""
alias OpenTelemetry.SemanticConventions
alias OpentelemetryMongodbDriver.Command
require OpenTelemetry.SemanticConventions.Trace
require OpenTelemetry.Tracer
@execution_start [:mongodb_driver, :start]
@options_schema NimbleOptions.new!(
db_statement: [
type: {:in, [:disabled, :plain, :obfuscated, {:fun, 1}]},
default: :disabled,
doc: ~s"""
Whether or not incllude db statements. Could be any of:
* `:disabled` - omit db statements
* `:plain` - include the entire statement without sanitization
* `:obfuscated` - include the entire statement with default obsfuscation
Optionally a function can be provided that takes a `keyword()` representing
the command and returns a sanitized version of it as string.
"""
]
)
@typedoc "Setup options"
@type opts() :: unquote(NimbleOptions.option_typespec(@options_schema))
@doc """
Initializes and configures the telemetry handlers.
## Options
#{NimbleOptions.docs(@options_schema)}
"""
@spec setup(opts()) :: :ok
def setup(opts \\ []) do
config = Map.new(NimbleOptions.validate!(opts, @options_schema))
:telemetry.attach(
{__MODULE__, :execution},
@execution_start,
&__MODULE__.handle_event/4,
config
)
:ok
end
# private use for test
@doc false
def detach do
:telemetry.detach({__MODULE__, :execution})
end
@doc false
def handle_event(event, measurements, metadata, config)
def handle_event(@execution_start, _measurements, metadata, config) do
collection = extract_collection(metadata.command)
span_name =
if collection do
"#{collection}.#{metadata.command_name}"
else
metadata.command_name
end
# metadata.command |> IO.inspect(label: "\nCMD")
attributes =
%{
SemanticConventions.Trace.db_system() => :mongodb,
SemanticConventions.Trace.db_name() => metadata.database_name,
SemanticConventions.Trace.db_operation() => metadata.command_name
# FIXME not available
# SemanticConventions.Trace.db_connection_string() => "...",
# TODO need connection information to complete the required attributes
# net.peer.name or net.peer.ip and net.peer.port
# SemanticConventions.Trace.net_peer_name() => "...",
# SemanticConventions.Trace.net_peer_port() => "...",
}
|> maybe_put(SemanticConventions.Trace.db_mongodb_collection(), collection)
|> maybe_put(SemanticConventions.Trace.db_statement(), sanitize(metadata.command, config))
s =
OpenTelemetry.Tracer.start_span(span_name, %{
kind: :client,
attributes: attributes
})
:ok =
OpentelemetryMongodbDriver.EventMonitor.register_start_span(
metadata.connection_id,
metadata.request_id,
s
)
end
defp extract_collection([{_, collection} | _]) when is_binary(collection), do: collection
defp extract_collection(_command), do: nil
defp maybe_put(map, _key, nil), do: map
defp maybe_put(map, key, val), do: Map.put(map, key, val)
defp sanitize(_command, %{db_statement: :disabled}), do: nil
defp sanitize(command, %{db_statement: db_statement}) when is_function(db_statement),
do: db_statement.(command)
defp sanitize(command, %{db_statement: db_statement}),
do: Command.sanitize(command, db_statement == :obfuscated)
end