Class: RailsSemanticLogging::Formatters::Datadog
- Inherits:
-
SemanticLogger::Formatters::Raw
- Object
- SemanticLogger::Formatters::Raw
- RailsSemanticLogging::Formatters::Datadog
- Defined in:
- lib/rails_semantic_logging/formatters/datadog.rb
Overview
Datadog-optimized JSON formatter that maps log fields to Datadog Standard Attributes (docs.datadoghq.com/standard-attributes/).
Key mappings:
name -> logger.name
level -> status
duration -> duration (nanoseconds) + duration_human (Rails format)
exception -> error: { kind, message, stack }
payload -> http: { status_code, method, url, ... } (controller requests)
named_tags.dd -> dd (top-level, for trace linking)
named_tags.user_* -> usr.{id, email, name, role}
Constant Summary collapse
- NANOSECONDS_PER_MILLISECOND =
1_000_000- HTTP_PAYLOAD_MAP =
Mapping of Rails payload keys to Datadog http standard attribute names
{ status: :status_code, method: :method, host: :host, user_agent: :useragent, referer: :referer }.freeze
- USER_NAMED_TAGS_MAP =
Mapping of user-related named_tags to usr.* standard attributes
{ user_id: :id, user_email: :email, user_name: :name, user_role: :role }.freeze
- ROUTING_ERROR_MESSAGE =
Matches ActionController::RoutingError messages like:
No route matches [GET] "/some/path"Used to extract http.method and http.url_details.path from the exception.
/\ANo route matches \[(?<method>\w+)\]\s+"(?<path>[^"]+)"/
Instance Method Summary collapse
- #call(log, logger) ⇒ Object
- #duration ⇒ Object
- #exception ⇒ Object
-
#initialize(time_format: :iso_8601, time_key: :timestamp, **args) ⇒ Datadog
constructor
rubocop:disable Naming/VariableNumber.
- #level ⇒ Object
-
#message ⇒ Object
Fall back to the exception message when the log carries no message of its own.
-
#metric ⇒ Object
SemanticLogger’s :dimensions log attribute carries metric tags (e.g. ‘logger.info(’Processed feed’, metric: ‘feed_size’, metric_amount: 12, dimensions: { feed: ‘amazon’ })‘).
- #name ⇒ Object
- #thread_name ⇒ Object
Constructor Details
#initialize(time_format: :iso_8601, time_key: :timestamp, **args) ⇒ Datadog
rubocop:disable Naming/VariableNumber
41 42 43 |
# File 'lib/rails_semantic_logging/formatters/datadog.rb', line 41 def initialize(time_format: :iso_8601, time_key: :timestamp, **args) # rubocop:disable Naming/VariableNumber super(time_format:, time_key:, log_application: false, log_host: true, log_environment: false, **args) end |
Instance Method Details
#call(log, logger) ⇒ Object
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/rails_semantic_logging/formatters/datadog.rb', line 45 def call(log, logger) super # SemanticLogger::Formatters::Raw assigns log.payload and log.named_tags # to hash[:payload] / hash[:named_tags] BY REFERENCE. We mutate those # below (remap_http_payload deletes :host/:method/etc., remap_named_tags # deletes :request_id/:client_ip/:dd/etc., deep_compact_blank! drops # every blank value). Without these dups the formatter strips keys off # the underlying log event, which breaks any consumer that reads # log.payload / log.named_tags after formatting (other appenders, # RSpec matchers that re-inspect the captured event). hash[:payload] = hash[:payload].dup if hash[:payload].is_a?(Hash) hash[:named_tags] = hash[:named_tags].dup if hash[:named_tags].is_a?(Hash) remap_http_payload parse_url_details deep_compact_blank!(hash) hash.to_json end |
#duration ⇒ Object
87 88 89 90 91 92 93 94 95 96 |
# File 'lib/rails_semantic_logging/formatters/datadog.rb', line 87 def duration # Propagate duration from payload if not set on log log.duration = log.payload[:duration] if log.duration.nil? && log.payload&.dig(:duration) return unless log.duration # Datadog standard: duration in nanoseconds hash[:duration] = (log.duration * NANOSECONDS_PER_MILLISECOND).to_i # Human-readable duration for readability (Rails format) hash[:duration_human] = "#{log.duration.round(2)}ms" end |
#exception ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/rails_semantic_logging/formatters/datadog.rb', line 98 def exception return unless log.exception hash[:error] = { kind: log.exception.class.name, message: log.exception., stack: log.exception.backtrace&.join("\n") } parse_routing_error end |
#level ⇒ Object
73 74 75 |
# File 'lib/rails_semantic_logging/formatters/datadog.rb', line 73 def level hash[:status] = log.level end |
#message ⇒ Object
Fall back to the exception message when the log carries no message of its own. rails_semantic_logger logs unmatched routes (and other rescued exceptions) via ActionDispatch::DebugExceptions by passing only the exception object, so without this the Datadog ‘message` field would be empty for those events.
82 83 84 85 |
# File 'lib/rails_semantic_logging/formatters/datadog.rb', line 82 def super hash[:message] ||= log.exception&. end |
#metric ⇒ Object
SemanticLogger’s :dimensions log attribute carries metric tags (e.g. ‘logger.info(’Processed feed’, metric: ‘feed_size’, metric_amount: 12, dimensions: { feed: ‘amazon’ })‘). The Raw formatter sets :metric and :metric_amount but not :dimensions — surface it at the top level so the Datadog UI can filter on those tags directly without diving into named_tags.
115 116 117 118 |
# File 'lib/rails_semantic_logging/formatters/datadog.rb', line 115 def metric super hash[:dimensions] = log.dimensions if log.dimensions.respond_to?(:any?) && log.dimensions.any? end |
#name ⇒ Object
69 70 71 |
# File 'lib/rails_semantic_logging/formatters/datadog.rb', line 69 def name hash[:'logger.name'] = log.name if log.name end |
#thread_name ⇒ Object
65 66 67 |
# File 'lib/rails_semantic_logging/formatters/datadog.rb', line 65 def thread_name # Exclude thread_name from output end |