From 0c4de35187f3bf3c37b107d6f763270a57326e0a Mon Sep 17 00:00:00 2001 From: Viktor Schmidt Date: Tue, 29 Jul 2025 12:00:16 +0200 Subject: [PATCH] Fix the experimental formatter so its payload method coerces values. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ensures every attribute value is OTLP‑compatible. Hashes become a JSON string, nil is dropped, arrays become arrays of strings/numbers/bools. --- CHANGELOG.md | 3 ++ .../formatters/open_telemetry.rb | 30 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc9a74d..dafcbf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +- Fix the experimental OpenTelemetry formatter so its payload method coerces values. + Ensures every attribute value is OTLP‑compatible. + ## [4.17.0] - Correct `source_code_uri` URL diff --git a/lib/semantic_logger/formatters/open_telemetry.rb b/lib/semantic_logger/formatters/open_telemetry.rb index 1914994..6d19142 100644 --- a/lib/semantic_logger/formatters/open_telemetry.rb +++ b/lib/semantic_logger/formatters/open_telemetry.rb @@ -2,6 +2,9 @@ module SemanticLogger module Formatters class OpenTelemetry < Raw + # primitives allowed by OTLP logs in Ruby: String, Integer, Float, TrueClass, FalseClass + PRIMS = [String, Integer, Float, TrueClass, FalseClass].freeze + # Log level def level hash[:level] = log.level.to_s @@ -12,11 +15,36 @@ def level def payload return unless log.payload.respond_to?(:empty?) && !log.payload.empty? - hash[:payload] = log.payload.transform_keys!(&:to_s) + hash[:payload] = coerce_map(log.payload) end private + def coerce_value(v) + case v + when *PRIMS then v + when Array then v.map { |e| coerce_value(e) }.compact # arrays of scalars only. + when NilClass then nil # drop nils by caller. + else v.to_s # stringify objects / hashes. + end + end + + def coerce_map(h) + h.each_with_object({}) do |(k, v), out| + next if v.nil? + + out[k.to_s] = + if v.is_a?(Hash) + # Stringify whole hash. + v.transform_values { |vv| coerce_value(vv) }. + transform_keys!(&:to_s). + to_json + else + coerce_value(v) + end + end + end + def severity_number(severity) case severity when :trace