lib/semantic_logger/appender/open_telemetry.rb



begin
  require "opentelemetry/logs"
rescue LoadError
  raise LoadError,
        'Gem opentelemetry-logs-sdk is required for logging to Open Telemetry. Please add the gem "opentelemetry-logs-sdk" to your Gemfile.'
end

# Open Telemetry Appender
#
#   Writes log messages, and metrics to Open Telemetry.
#
module SemanticLogger
  module Appender
    class OpenTelemetry < SemanticLogger::Subscriber
      attr_reader :name, :version, :logger

      CAPTURE_CONTEXT = ->(log) { log.set_context(:open_telemetry, ::OpenTelemetry::Context.current) }

      # Create a Open Telemetry Logger appender instance.
      #
      # Metric only log events are sent to the Open Telemetry Metrics API instead of the Logs API.
      #   I.e. A metric without a message or an exception.
      #   To disable this default behavior set `metrics: false`
      #
      # Example
      #    SemanticLogger.add_appender(appender: :open_telemetry)
      def initialize(name: "SemanticLogger",
                     version: SemanticLogger::VERSION,
                     formatter: SemanticLogger::Formatters::OpenTelemetry.new,
                     metrics: true,
                     **args,
                     &block)
        @name    = name
        @version = version
        @logger  = ::OpenTelemetry.logger_provider.logger(name: @name, version: @version)

        # Capture the current Open Telemetry context when a log entry is captured.
        # Prevents duplicate subscribers as long as it is from a constant.
        SemanticLogger.on_log(CAPTURE_CONTEXT)

        super(formatter: formatter, metrics: metrics, **args, &block)
      end

      def log(log)
        # return log_metric(log) if metrics && log.metric_only?

        body        = formatter.call(log, self)
        level       = body.delete(:level)
        level_index = body.delete(:level_index)
        time        = body.delete(:time)
        payload     = body.delete(:payload)

        @logger.on_emit(
          severity_text:   level,
          severity_number: level_index,
          timestamp:       time,
          body:            body.transform_keys!(&:to_s),
          attributes:      payload,
          context:         log.context[:open_telemetry] || ::OpenTelemetry::Context.current
        )
        true
      end

      # Flush all pending logs.
      def flush
        @logger.logger_provider.force_flush
      end

      # Flush pending logs and close the appender
      def close
        @logger.logger_provider.shutdown
      end

      # For logging metrics only log events.
      # def log_metric(log)
      #   puts "**** TODO: Metric Only Event ****"
      #   ap formatter.call(log, self)
      #   ap log.payload
      #   true
      # end
    end
  end
end