Current section

Files

Jump to
otel_metric_exporter lib otel_metric_exporter protocol.ex
Raw

lib/otel_metric_exporter/protocol.ex

defmodule OtelMetricExporter.Protocol do
@moduledoc false
alias OtelMetricExporter.Opentelemetry.Proto.Collector.Metrics.V1.ExportMetricsServiceRequest
alias OtelMetricExporter.Opentelemetry.Proto.Metrics.V1.ResourceMetrics
alias OtelMetricExporter.Opentelemetry.Proto.Metrics.V1.ScopeMetrics
alias OtelMetricExporter.Opentelemetry.Proto.Collector.Logs.V1.ExportLogsServiceRequest
alias OtelMetricExporter.Opentelemetry.Proto.Common.V1.AnyValue
alias OtelMetricExporter.Opentelemetry.Proto.Common.V1.InstrumentationScope
alias OtelMetricExporter.Opentelemetry.Proto.Common.V1.KeyValueList
alias OtelMetricExporter.Opentelemetry.Proto.Logs.V1.LogRecord
alias OtelMetricExporter.Opentelemetry.Proto.Logs.V1.ResourceLogs
alias OtelMetricExporter.Opentelemetry.Proto.Logs.V1.ScopeLogs
alias OtelMetricExporter.Opentelemetry.Proto.Logs.V1.SeverityNumber
alias OtelMetricExporter.Opentelemetry.Proto.Resource.V1.Resource
alias OtelMetricExporter.OtlpUtils
@doc """
Convert a logger event to a struct ready for protobuf encoding
"""
@spec prepare_log_event(:logger.log_event(), term()) :: struct()
def prepare_log_event(%{level: level, msg: msg, meta: metadata}, config) do
time = metadata[:time] * 1000
# {trace_id, span_id} = extract_trace_context(metadata)
%LogRecord{
time_unix_nano: time,
observed_time_unix_nano: time,
severity_number: severity_number(level),
severity_text: Atom.to_string(level),
body: encode_body(msg),
attributes: metadata |> prepare_attributes(config) |> OtlpUtils.build_kv(),
dropped_attributes_count: 0,
flags: 0,
# Official OTel tracing library adds these as charlists, so we need to convert them to binaries
trace_id: Map.get(metadata, :otel_trace_id, nil) |> to_string(),
span_id: Map.get(metadata, :otel_span_id, nil) |> to_string(),
event_name: Map.get(metadata, :event_name, nil)
}
end
defp encode_body({:string, chardata}) do
%AnyValue{value: {:string_value, IO.chardata_to_string(chardata)}}
end
defp encode_body({:report, report}) do
%AnyValue{value: {:kvlist_value, %KeyValueList{values: OtlpUtils.build_kv(report)}}}
end
defp encode_body({io_format, args}) do
%AnyValue{value: {:string_value, :io_lib.format(io_format, args) |> IO.chardata_to_string()}}
end
defp severity_number(:debug), do: SeverityNumber.value(:SEVERITY_NUMBER_DEBUG)
defp severity_number(:info), do: SeverityNumber.value(:SEVERITY_NUMBER_INFO)
defp severity_number(:notice), do: SeverityNumber.value(:SEVERITY_NUMBER_INFO2)
defp severity_number(:warning), do: SeverityNumber.value(:SEVERITY_NUMBER_WARN)
defp severity_number(:error), do: SeverityNumber.value(:SEVERITY_NUMBER_ERROR)
defp severity_number(:critical), do: SeverityNumber.value(:SEVERITY_NUMBER_ERROR2)
defp severity_number(:alert), do: SeverityNumber.value(:SEVERITY_NUMBER_FATAL)
defp severity_number(:emergency), do: SeverityNumber.value(:SEVERITY_NUMBER_FATAL4)
defp prepare_attributes(%{crash_reason: {reason, stacktrace}} = metadata, config) do
message = if is_exception(reason), do: Exception.message(reason), else: inspect(reason)
type =
case reason do
{:nocatch, _} -> "Uncaught throw"
{:timeout, _} -> "EXIT: time out"
:timeout -> "EXIT: time out"
reason when is_exception(reason) -> to_string(reason.__struct__)
_ -> "EXIT: #{inspect(reason)}"
end
metadata
|> Map.drop([:crash_reason])
|> Map.put("exception.message", message)
|> Map.put("exception.type", type)
|> Map.put("exception.stacktrace", Exception.format_stacktrace(stacktrace))
|> prepare_attributes(config)
end
defp prepare_attributes(metadata, %{metadata: metadata_to_keep, metadata_map: metadata_map}) do
Enum.flat_map(metadata, fn
{k, v} when is_binary(k) ->
[{k, v}]
{k, v} ->
case Map.fetch(metadata_map, k) do
{:ok, remapped_key} ->
[{remapped_key, v}]
:error ->
if k in metadata_to_keep, do: [{k, v}], else: []
end
end)
end
def build_log_service_request(events, resource \\ %{}) do
%ExportLogsServiceRequest{
resource_logs: [
%ResourceLogs{
resource: %Resource{
attributes: OtlpUtils.build_kv(resource)
},
scope_logs: [
%ScopeLogs{
scope: %InstrumentationScope{
name: "otel_metric_exporter"
},
log_records: events
}
]
}
]
}
end
def build_metric_service_request(metrics, resource \\ %{}) do
%ExportMetricsServiceRequest{
resource_metrics: [
%ResourceMetrics{
resource: %Resource{attributes: OtlpUtils.build_kv(resource)},
scope_metrics: [
%ScopeMetrics{
scope: %InstrumentationScope{name: "otel_metric_exporter"},
metrics: metrics
}
]
}
]
}
end
end