Current section
Files
Jump to
Current section
Files
lib/clef_event.ex
defmodule ExSeq.CLEFEvent do
@moduledoc """
Represents a Compact Log Event Format (CLEF) log event.
"""
alias ExSeq.CLEFLevel
@enforce_keys [:level]
defstruct [
:timestamp,
:message,
:exception,
:level,
:trace_id,
:span_id,
:span_start,
:span_kind,
:resource_attributes,
:parent_span_id,
properties: %{}
]
@typedoc """
CLEF event
* `timestamp` – a `DateTime` (or `NaiveDateTime`) for the log time.
* `message` – the main log message string.
* `exception` – an exception stacktrace or error details.
* `level` – one of the `ExSeq.CLEFLevel` variants.
* `trace_id` – a string trace ID for distributed tracing.
* `span_id` – a string span ID for distributed tracing.
* `span_start` – a `DateTime` marking when the span began.
* `span_kind` – a string describing the kind of span (client, server, etc.).
* `resource_attributes` – a map of key/value pairs describing the resource.
* `parent_span_id` – a span ID for the parent span (if any).
* `properties` – additional arbitrary key/value pairs.
"""
@type t :: %__MODULE__{
timestamp: DateTime.t() | NaiveDateTime.t() | nil,
message: String.t() | nil,
exception: String.t() | nil,
level: CLEFLevel.t(),
trace_id: String.t() | nil,
span_id: String.t() | nil,
span_start: DateTime.t() | NaiveDateTime.t() | nil,
span_kind: String.t() | nil,
resource_attributes: map() | nil,
parent_span_id: String.t() | nil,
properties: map()
}
end
defimpl Jason.Encoder, for: ExSeq.CLEFEvent do
alias ExSeq.CLEFLevel
defp sanitize_metadata(value) when is_map(value) and not is_struct(value) do
Enum.into(value, %{}, fn {k, v} -> {k, sanitize_metadata(v)} end)
end
defp sanitize_metadata(value) when is_struct(value) do
# Dirty fallback hack
try do
Jason.encode!(value)
value
rescue
_ -> inspect(value)
end
end
defp sanitize_metadata(value) when is_list(value) do
Enum.map(value, &sanitize_metadata/1)
end
defp sanitize_metadata(value) when is_tuple(value) do
inspect(value)
end
defp sanitize_metadata(value) when is_pid(value) do
inspect(value)
end
defp sanitize_metadata(value) when is_atom(value) do
Atom.to_string(value)
end
defp sanitize_metadata(value) when is_binary(value), do: value
defp sanitize_metadata(value), do: inspect(value)
def encode(%ExSeq.CLEFEvent{} = event, opts) do
# Transform the struct into a map with the CLEF fields:
clef_map = %{
"@t" => event.timestamp,
"@m" => event.message,
"@x" => event.exception,
"@l" => CLEFLevel.to_string(event.level),
"@tr" => event.trace_id,
"@sp" => event.span_id,
"@st" => event.span_start,
"@sk" => event.span_kind,
"@ra" => event.resource_attributes,
"@ps" => event.parent_span_id
}
# Add user-defined properties into the top-level map:
props = event.properties |> Enum.into(%{})
merged = Map.merge(clef_map, props || %{})
cleaned =
merged
|> Enum.reject(fn {_key, value} ->
is_nil(value) or value == ""
end)
|> Enum.into(%{})
|> sanitize_metadata()
Jason.Encode.map(cleaned, opts)
end
end