lumberjack_data_dog 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53517e1a4626e2ca86c9a78968008ce667612cb8121ad769a82484056ab7d23c
4
- data.tar.gz: 3ad569bd2691572c29fbb14dfd569cde90ad8c38bd36dda3f0c720ce33b19e88
3
+ metadata.gz: 1ea1beb91821ea1831d491e84701eb98ebc172a8f5cf863a7eb06dead64db440
4
+ data.tar.gz: f5e2eaffefff58790484d5c44ea83decda71b3be3facde69862a51765736066d
5
5
  SHA512:
6
- metadata.gz: b52261c574346d6dad7fc25a2d898c7ae6280a8b8487aeb4341df43ba5d1c5e6a3cb692582dc3d97d7222ab386765fc9c49147adeb1145338a8abbec3e999b22
7
- data.tar.gz: 829c023744c9bc34c1c96f02fe1facde15ccd2eaab80f0e29712679d252bfcccec4d154a9dd54d5c599532fe8c8c8dbc4cd0b6dff0458569b5951e6f07a0a761
6
+ metadata.gz: 5632752f398949fba2646745caa31818eff0beb46c1015feecdb5074fadbeee6effa1fb1f7654d5b6dd7a1c82f156a592230029c5963b9c27bf9a20663b1ed3c
7
+ data.tar.gz: 8966d4801e557934aed6967f84893f13142b54074b02c9dc4adf8a505ff30a73715cbc9cb3a37390b22f938c699fc23618f3ac75f69757404916e8271b96dd98
data/CHANGE_LOG.md CHANGED
@@ -4,15 +4,27 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## 1.1.0
7
+ ## 2.0.0
8
8
 
9
9
  ### Added
10
10
 
11
- - Support for Lumberjack 2.0.
11
+ - Added `Lumberjack::DataDog::Device` as a wrapper for `Lumberjack::JsonDevice` with mapping to Datadog standard attributes.
12
+ - Added `Lumberjack::DataDog::EntryFormatter` to encapsulate entry formatting logic for exceptions and duration. With Lumberjack 2 this can now be merged with other formatters.
13
+ - Added `Lumberjack::DataDog::ExceptionAttributeFormatter` to handle exception attribute extraction and formatting. This formatter can now also handle adding additional attributes from other kinds of exceptions.
12
14
 
13
15
  ### Changed
14
16
 
15
- - This is the last release for the gem under the `lumberjack_data_dog` name. Future updates are being released under the name `lumberjack_datadog`.
17
+ - **Breaking Change** Tags are now called attributes in Lumberjack 2 so some of the method names for setting up attributes have changed.
18
+ - `Lumberjack::DataDog::Config#allow_all_tags` is now `allow_all_attributes`.
19
+ - `Lumberjack::DataDog::Config#tag_mapping` is now `attribute_mapping`.
20
+ - `Lumberjack::DataDog#remap_tags` is now `remap_attributes`.
21
+ - Truncated messages are now suffixed with an ellipsis ("…") character.
22
+
23
+ ## 1.1.0
24
+
25
+ ### Added
26
+
27
+ - Support for Lumberjack 2.0.
16
28
 
17
29
  ### Removed
18
30
 
data/README.md CHANGED
@@ -1,137 +1,147 @@
1
- # Lumberjack DataDog
1
+ # Lumberjack Datadog
2
2
 
3
- [![Continuous Integration](https://github.com/bdurand/lumberjack_data_dog/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/bdurand/lumberjack_data_dog/actions/workflows/continuous_integration.yml)
3
+ [![Continuous Integration](https://github.com/bdurand/lumberjack_datadog/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/bdurand/lumberjack_datadog/actions/workflows/continuous_integration.yml)
4
4
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
5
- [![Gem Version](https://badge.fury.io/rb/lumberjack_data_dog.svg)](https://badge.fury.io/rb/lumberjack_data_dog)
5
+ [![Gem Version](https://badge.fury.io/rb/lumberjack_datadog.svg)](https://badge.fury.io/rb/lumberjack_datadog)
6
6
 
7
- > [!IMPORTANT]
8
- > This gem has been renamed to [`lumberjack_datadog`](https://github.com/bdurand/lumberjack_datadog). For new versions switch using that gem.
9
-
10
- This gem provides a logging setup for using the [lumberjack](https://github.com/bdurand/lumberjack) gem with DataDog. It sets up JSON logging and maps values to DataDog's [standard attributes](https://docs.datadoghq.com/logs/processing/attributes_naming_convention/).
7
+ This gem provides a logging setup for using the [lumberjack](https://github.com/bdurand/lumberjack) gem with Datadog. It sets up JSON logging and maps values to Datadog's [standard attributes](https://docs.datadoghq.com/logs/processing/attributes_naming_convention/).
11
8
 
12
9
  ## Features
13
10
 
14
- - **DataDog Standard Attribute Mapping**: Automatically maps Lumberjack log fields to DataDog's standard attributes:
11
+ - **Datadog Standard Attribute Mapping**: Automatically maps Lumberjack log fields to Datadog's standard attributes:
15
12
  - `time` → `timestamp`
16
13
  - `severity` → `status`
17
14
  - `progname` → `logger.name`
18
15
  - `pid` → `pid`
19
16
  - **Exception Handling**: Structured exception logging with `kind`, `message`, and `stack` fields
20
- - **Duration Logging**: Automatic conversion of duration values to nanoseconds for DataDog
17
+ - **Duration Logging**: Automatic conversion of duration values to nanoseconds for Datadog
21
18
  - **Configurable Message Truncation**: Limit message length to prevent oversized logs
22
- - **Thread Information**: Optional thread name logging
23
- - **attribute Remapping**: Flexible attribute transformation and remapping
24
- - **Pretty JSON**: Optional pretty-printed JSON output for development
19
+ - **Attribute Remapping**: Flexible attribute transformation and remapping
25
20
 
26
21
  ## Usage
27
22
 
28
23
  ### Basic Setup
29
24
 
30
25
  ```ruby
31
- require 'lumberjack_data_dog'
32
-
33
26
  # Create a logger that outputs to STDOUT
34
- logger = Lumberjack::DataDog.setup
27
+ logger = Lumberjack::Logger.new(:datadog)
35
28
 
36
- # Log messages
37
- logger.info("Application started")
38
- logger.warn("This is a warning", user_id: 123)
39
- logger.error("Something went wrong", request_id: "abc-123")
29
+ # Create a logger that outputs to a file
30
+ logger = Lumberjack::Logger.new(:datadog, output: "/var/log/app.log")
40
31
  ```
41
32
 
42
33
  ### Advanced Configuration
43
34
 
35
+ You can pass options to the logger during initialization to further customize how entries are formatted:
36
+
44
37
  ```ruby
45
- # The output device and logger options can be passed in the setup method.
46
- # These are passed through to Lumberjack::Logger.new.
47
- logger = Lumberjack::DataDog.setup(log_device, level: :info) do |config|
48
- # Truncate messages longer than 1000 characters
49
- config.max_message_length = 1000
38
+ logger = Lumberjack::Logger.new(:datadog,
39
+ output: "/var/log/app.log",
40
+ max_message_length: 1000, # Truncate messages longer than 1000 characters
41
+ allow_all_attributes: false, # Only include explicitly mapped attributes
42
+ attribute_mapping: { # Custom attribute mapping
43
+ request_id: "trace_id", # Map request_id to trace_id
44
+ user_id: "usr.id" # Map user_id to nested usr.id
45
+ }
46
+ )
47
+ ```
50
48
 
51
- # Include thread information
52
- config.thread_name = true # or :global for global thread ID
49
+ #### Configuration Options
53
50
 
54
- # Disable PID logging
55
- config.pid = false
51
+ | Option | Type | Default | Description |
52
+ |--------|------|---------|-------------|
53
+ | `max_message_length` | Integer or nil | `nil` | Maximum length for log messages. Messages longer than this will be truncated with an ellipsis. |
54
+ | `allow_all_attributes` | Boolean | `true` | Whether to include all log entry attributes at the root level of the JSON output. When false, only explicitly mapped attributes are included. |
55
+ | `attribute_mapping` | Hash | `{}` | Custom mapping of attribute names. Keys are original names (symbols), values can be strings, arrays (for nested attributes), or procs for custom formatting. |
56
+ | `pid` | Boolean or Symbol | `true` | Process ID handling: `true` (include current PID), `false` (exclude PID), or `:global` (globally unique PID with hostname). |
57
+ | `backtrace_cleaner` | Object or nil | `nil` | Optional backtrace cleaner that responds to `#clean` method for cleaning exception stack traces. |
56
58
 
57
- # Remap custom attributes to DataDog attributes
59
+ > [!TIP]
60
+ > You can also pass `pretty: true` in development mode to have more human readable logs if you aren't sending them to Datadog.
61
+
62
+ #### Configuration Block
63
+
64
+ Alternatively, you can use the `setup` method with a configuration block for complex setups. Note that this approach is maintained for compatibility, but the direct constructor approach shown above is generally preferred for new code.
65
+
66
+ ```ruby
67
+ logger = Lumberjack::DataDog.setup($stdout, level: :info) do |config|
68
+ # Message truncation
69
+ config.max_message_length = 500
70
+
71
+ # Process ID options
72
+ config.pid = true # Include current process ID (default)
73
+ config.pid = false # Don't include process ID
74
+ config.pid = :global # Use a globally unique process ID (includes hostname)
75
+
76
+ # Attribute handling
77
+ config.allow_all_attributes = true # Include all log attributes at root level (default)
78
+ config.allow_all_attributes = false # Only include explicitly mapped attributes
79
+
80
+ # Custom attribute mapping and formatting
58
81
  config.remap_attributes(
59
- user_id: "usr.id",
60
- request_id: "http.request_id"
82
+ request_id: "trace_id", # Simple remapping
83
+ user_id: "usr.id" # Nested attribute mapping
61
84
  )
62
85
 
63
- # Add a backtrace cleaner to remove unnecessary noise when logging exceptions.
64
- # The cleaner object must respond to `clean` method.
65
- config.backtrace_cleaner = Rails.backtrace_cleaner
66
-
67
- # Enable pretty JSON for development
86
+ # Pretty print JSON (useful for development)
68
87
  config.pretty = true
88
+
89
+ # Custom backtrace cleaner for exceptions
90
+ config.backtrace_cleaner = MyCustomBacktraceCleaner.new
69
91
  end
70
92
  ```
71
93
 
72
- ### Logging to a File
94
+ ### Entry Formatter
73
95
 
74
- ```ruby
75
- # Log to a file instead of STDOUT
76
- logger = Lumberjack::DataDog.setup("/var/log/app.log")
77
- logger.info("Logged to file")
78
- ```
96
+ The Datadog device automatically includes the entry formatter to add helpers for logging exceptions and durations. You don't need to specify it explicitly.
79
97
 
80
98
  ### Exception Logging
81
99
 
82
- Exceptions are automatically structured with DataDog's error attributes:
100
+ Exceptions are automatically structured with Datadog's standard error attributes.
83
101
 
84
102
  ```ruby
85
103
  begin
86
- raise StandardError, "Something went wrong"
104
+ do_something
87
105
  rescue => e
88
- # Results in structured error with error.kind, error.message, and error.stack fields
106
+ # Results in logging `e.inspect` as the log message along with @error.kind, @error.message, and @error.stack
89
107
  logger.error(e)
108
+
109
+ # You can also log a different message and put the error in the attributes. The standard attributes
110
+ # will still be parsed out of the error object.
111
+ logger.error("Something went wrong", error: e)
90
112
  end
91
113
  ```
92
114
 
115
+ You can pass in a custom backtrace cleaner for exceptions. This can be any object that responds to the `clean` method that takes an array of strings and returns an array of strings. If you are in a Rails environment, you can pass in `Rails.backtrace_cleaner`.
116
+
117
+ ```ruby
118
+ logger = Lumberjack::Logger.new(:datadog, backtrace_cleaner: Rails.backtrace_cleaner)
119
+ ```
120
+
93
121
  ### Duration Logging
94
122
 
95
- Duration values are automatically converted to nanoseconds:
123
+ Duration values are automatically converted to nanoseconds in the Datadog standard `duration` attribute from the `duration` attribute. This keeps the Ruby code clean since Ruby measures time in seconds. There are helpers for logging durations in milliseconds, microseconds, and nanoseconds.
96
124
 
97
125
  ```ruby
98
- # Log execution time
99
- start_time = Time.now
100
- # ... do some work ...
101
- duration = Time.now - start_time
126
+ duration = Benchmark.realtime do
127
+ do_something
128
+ end
102
129
 
130
+ # duration is automatically converted to nanoseconds in the logs.
103
131
  logger.info("Operation completed", duration: duration)
104
- # duration is automatically converted to nanoseconds
105
132
 
106
- # You can also use specific duration units
133
+ # You can also use specific duration units.
107
134
  logger.info("DB query", duration_ms: 150.5) # milliseconds
108
135
  logger.info("API call", duration_micros: 1500) # microseconds
109
136
  logger.info("Function", duration_ns: 1500000) # nanoseconds
110
137
  ```
111
138
 
112
- ### Custom attribute Transformation
113
-
114
- ```ruby
115
- logger = Lumberjack::DataDog.setup do |config|
116
- config.remap_attributes(
117
- # Simple remapping
118
- correlation_id: "trace.correlation_id",
119
-
120
- # Transform with a lambda
121
- user_email: ->(email) { {"usr.email" => email.downcase} }
122
- )
123
- end
124
-
125
- logger.info("User logged in", user_email: "USER@EXAMPLE.COM")
126
- # Results in usr.email: "user@example.com"
127
- ```
128
-
129
139
  ## Installation
130
140
 
131
141
  Add this line to your application's Gemfile:
132
142
 
133
143
  ```ruby
134
- gem "lumberjack_data_dog"
144
+ gem "lumberjack_datadog"
135
145
  ```
136
146
 
137
147
  And then execute:
@@ -141,7 +151,7 @@ $ bundle install
141
151
 
142
152
  Or install it yourself as:
143
153
  ```bash
144
- $ gem install lumberjack_data_dog
154
+ $ gem install lumberjack_datadog
145
155
  ```
146
156
 
147
157
  ## Contributing
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0
1
+ 2.0.0
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Configuration for Datadog logging
5
+ # @!parse
6
+ # class Lumberjack::DataDog::Config; end
7
+ module Lumberjack::DataDog
8
+ # Configuration options for Datadog logger setup.
9
+ #
10
+ # This class provides a configuration object for customizing the behavior
11
+ # of Datadog loggers when using the {Lumberjack::DataDog.setup} method.
12
+ #
13
+ # @!attribute [rw] max_message_length
14
+ # @return [Integer, nil] Maximum length for log messages. Messages longer than this
15
+ # will be truncated with an ellipsis. Default is nil (no truncation).
16
+ # @!attribute [rw] backtrace_cleaner
17
+ # @return [Object, nil] Optional backtrace cleaner that responds to #clean method.
18
+ # Used for cleaning exception stack traces. Default is nil.
19
+ # @!attribute [rw] pid
20
+ # @return [Boolean, Symbol] Process ID inclusion option. Can be true (include current PID),
21
+ # false (exclude PID), or :global (use globally unique PID). Default is true.
22
+ # @!attribute [rw] allow_all_attributes
23
+ # @return [Boolean] Whether to include all log entry attributes at the root level
24
+ # of the JSON output. Default is true.
25
+ # @!attribute [r] attribute_mapping
26
+ # @return [Hash] Custom mapping of attribute names. Use {#remap_attributes} to modify.
27
+ # @!attribute [rw] pretty
28
+ # @return [Boolean] Whether to pretty-print JSON output. Useful for development.
29
+ # Default is false.
30
+ class Config
31
+ attr_accessor :max_message_length
32
+ attr_accessor :backtrace_cleaner
33
+ attr_accessor :pid
34
+ attr_accessor :allow_all_attributes
35
+ attr_reader :attribute_mapping
36
+ attr_accessor :pretty
37
+
38
+ def initialize
39
+ @max_message_length = nil
40
+ @backtrace_cleaner = nil
41
+ @thread_name = false
42
+ @pid = true
43
+ @allow_all_attributes = true
44
+ @attribute_mapping = {}
45
+ @pretty = false
46
+ end
47
+
48
+ # Add the thread name to the `logger.thread_name` attribute. This setting is deprecated.
49
+ # Call logger.tag!("logger.thread_name" => -> { Lumberjack::Utils.global_thread_id }) instead.
50
+ #
51
+ # @param value [Boolean, Symbol] Thread name option. Pass :global to use the global thread ID.
52
+ # @return [void]
53
+ # @deprecated
54
+ def thread_name=(value)
55
+ message = "Setting @logger.thread_name through the logger setup is deprecated and will be removed in version 2.1.; call logger.tag!(\"logger.thread_name\" => -> { Lumberjack::Utils.global_thread_id }) instead."
56
+ Lumberjack::Utils.deprecated(:thread_name=, message) do
57
+ @thread_name = value
58
+ end
59
+ end
60
+
61
+ # @deprecated
62
+ attr_reader :thread_name
63
+
64
+ # Remap log attributes by merging additional attribute mappings.
65
+ #
66
+ # @param attribute_mapping [Hash] Additional attribute mappings to merge.
67
+ # Keys are the original attribute names (symbols), values are the target
68
+ # field names (strings, arrays for nested fields, or procs for custom formatting).
69
+ # @return [Hash] The updated attribute mapping hash
70
+ # @example Simple attribute remapping
71
+ # config.remap_attributes(request_id: "trace_id", user_id: "usr.id")
72
+ def remap_attributes(attribute_mapping)
73
+ @attribute_mapping = @attribute_mapping.merge(attribute_mapping)
74
+ end
75
+
76
+ # Validate configuration options to ensure they are properly set.
77
+ #
78
+ # Checks that:
79
+ # - max_message_length is nil or a positive integer
80
+ # - backtrace_cleaner is nil or responds to #clean method
81
+ #
82
+ # @raise [ArgumentError] if max_message_length is not nil or a positive integer
83
+ # @raise [ArgumentError] if backtrace_cleaner doesn't respond to #clean
84
+ # @return [void]
85
+ def validate!
86
+ if !max_message_length.nil? && (!max_message_length.is_a?(Integer) || max_message_length <= 0)
87
+ raise ArgumentError, "max_message_length must be a positive integer"
88
+ end
89
+
90
+ unless backtrace_cleaner.nil? || backtrace_cleaner.respond_to?(:clean)
91
+ raise ArgumentError, "backtrace_cleaner must respond to #clean"
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Datadog device for Lumberjack logging
5
+ # @!parse
6
+ # class Lumberjack::DataDog::Device < Lumberjack::JsonDevice; end
7
+ module Lumberjack::DataDog
8
+ # Device for sending logs to Datadog with automatic attribute mapping and formatting.
9
+ #
10
+ # This device extends Lumberjack::JsonDevice to provide Datadog-specific functionality
11
+ # including standard attribute mapping, exception formatting, and duration conversion.
12
+ # It automatically registers itself as the :datadog device type.
13
+ #
14
+ # @example Basic usage
15
+ # logger = Lumberjack::Logger.new(:datadog, output: $stdout)
16
+ #
17
+ # @example With custom options
18
+ # logger = Lumberjack::Logger.new(:datadog,
19
+ # output: "/var/log/app.log",
20
+ # max_message_length: 1000,
21
+ # allow_all_attributes: false
22
+ # )
23
+ class Device < Lumberjack::JsonDevice
24
+ Lumberjack::DeviceRegistry.add(:datadog, self)
25
+
26
+ # Initialize the Datadog device with Datadog-specific formatting.
27
+ #
28
+ # @param options [Hash] Device options including Datadog-specific configuration
29
+ # @option options [Boolean, Symbol] :pid Include process ID in logs
30
+ # @option options [Hash] :attribute_mapping Custom attribute name mapping
31
+ # @option options [Boolean] :allow_all_attributes Include all log entry attributes
32
+ # @option options [Integer, nil] :max_message_length Maximum message length
33
+ # @option options [Object, nil] :backtrace_cleaner Backtrace cleaner for exceptions
34
+ def initialize(options = {})
35
+ datadog_options = options.dup
36
+
37
+ mapping_options = {}
38
+ mapping_options[:pid] = datadog_options.delete(:pid)
39
+ mapping_options[:attribute_mapping] = datadog_options.delete(:attribute_mapping)
40
+ mapping_options[:allow_all_attributes] = datadog_options.delete(:allow_all_attributes)
41
+ mapping_options[:max_message_length] = datadog_options.delete(:max_message_length)
42
+ datadog_options[:mapping] ||= Lumberjack::DataDog.json_mapping(**mapping_options.compact)
43
+
44
+ backtrace_cleaner = datadog_options.delete(:backtrace_cleaner)
45
+ @entry_formatter = Lumberjack::DataDog.entry_formatter(backtrace_cleaner: backtrace_cleaner)
46
+
47
+ super(datadog_options)
48
+ end
49
+
50
+ # Write a log entry with Datadog-specific formatting applied.
51
+ #
52
+ # This method applies the configured entry formatter to transform
53
+ # the log entry's message and attributes before passing it to the
54
+ # parent JSON device for output.
55
+ #
56
+ # @param entry [Lumberjack::LogEntry] The log entry to write
57
+ # @return [void]
58
+ def write(entry)
59
+ message, attributes = @entry_formatter.format(entry.message, entry.attributes)
60
+ entry.message = message
61
+ entry.attributes = attributes
62
+ super
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Datadog integration for exception attribute formatting
5
+ # @!parse
6
+ # module Lumberjack::DataDog; end
7
+ module Lumberjack::DataDog
8
+ # Formats exception attributes for Datadog logs.
9
+ #
10
+ # This formatter converts Exception objects into structured attributes
11
+ # that follow Datadog's error tracking conventions.
12
+ class ExceptionAttributeFormatter
13
+ # Initialize the exception attribute formatter.
14
+ #
15
+ # @param backtrace_cleaner [Object, nil] Optional backtrace cleaner that responds to #clean
16
+ # @param additional_attributes [Hash] Additional attributes to extract from exception
17
+ def initialize(backtrace_cleaner: nil, additional_attributes: {})
18
+ @backtrace_cleaner = backtrace_cleaner
19
+ @additional_attributes = additional_attributes
20
+ end
21
+
22
+ # Format exception attributes for logging.
23
+ #
24
+ # Converts an Exception object into a hash of attributes suitable for Datadog
25
+ # error tracking, including the exception class name, message, and stack trace.
26
+ #
27
+ # @param error [Exception] The exception to format
28
+ # @return [Hash] Formatted exception attributes with the following keys:
29
+ # - 'kind': Exception class name
30
+ # - 'message': Exception message
31
+ # - 'stack': Array of stack trace strings (if backtrace available)
32
+ # - Additional keys from @additional_attributes if specified
33
+ def call(error)
34
+ error_attributes = {"kind" => error.class.name, "message" => error.message}
35
+
36
+ backtrace = error.backtrace
37
+ if backtrace
38
+ backtrace = @backtrace_cleaner.clean(backtrace) if @backtrace_cleaner
39
+ error_attributes["stack"] = backtrace
40
+ end
41
+
42
+ @additional_attributes.each do |key, method|
43
+ next unless error.respond_to?(method)
44
+
45
+ error_attributes[key.to_s] = error.public_send(method)
46
+ end
47
+
48
+ error_attributes
49
+ end
50
+ end
51
+ end
@@ -2,7 +2,40 @@
2
2
 
3
3
  require "lumberjack_json_device"
4
4
 
5
+ # Datadog integration for Lumberjack logging.
6
+ #
7
+ # This module provides JSON logging functionality specifically designed for Datadog,
8
+ # automatically mapping standard log attributes to Datadog's naming conventions
9
+ # and providing structured exception and duration logging.
10
+ #
11
+ # @example Basic usage
12
+ # logger = Lumberjack::Logger.new(:datadog)
13
+ # logger.info("Application started")
14
+ #
15
+ # @example Advanced configuration
16
+ # logger = Lumberjack::Logger.new(:datadog,
17
+ # max_message_length: 1000,
18
+ # allow_all_attributes: false,
19
+ # attribute_mapping: { request_id: "trace_id" }
20
+ # )
21
+ #
22
+ # @see Device
23
+ # @see Config
5
24
  module Lumberjack::DataDog
25
+ # Version of the lumberjack_datadog gem
26
+ # @return [String] The current version string
27
+ VERSION = ::File.read(::File.join(__dir__, "..", "..", "VERSION")).strip.freeze
28
+
29
+ # Standard mapping of log attributes to Datadog fields.
30
+ #
31
+ # This constant defines the default attribute mapping used to transform
32
+ # Lumberjack log entries into Datadog-compatible format.
33
+ #
34
+ # @return [Hash] Mapping hash with the following transformations:
35
+ # - :time → "timestamp"
36
+ # - :severity → "status"
37
+ # - :progname → ["logger", "name"] (nested attribute)
38
+ # - :pid → "pid"
6
39
  STANDARD_ATTRIBUTE_MAPPING = {
7
40
  time: "timestamp",
8
41
  severity: "status",
@@ -10,135 +43,147 @@ module Lumberjack::DataDog
10
43
  pid: "pid"
11
44
  }.freeze
12
45
 
13
- class Config
14
- attr_accessor :max_message_length
15
- attr_accessor :backtrace_cleaner
16
- attr_accessor :thread_name
17
- attr_accessor :pid
18
- attr_accessor :allow_all_attributes
19
- attr_reader :attribute_mapping
20
- attr_accessor :pretty
21
-
22
- def initialize
23
- @max_message_length = nil
24
- @backtrace_cleaner = nil
25
- @thread_name = false
26
- @pid = true
27
- @allow_all_attributes = true
28
- @attribute_mapping = {}
29
- @pretty = false
30
- end
46
+ class << self
47
+ # Returns a mapping of log attributes to JSON fields for Datadog.
48
+ #
49
+ # This method creates the attribute mapping configuration used by the JSON device
50
+ # to transform log entries into Datadog-compatible format.
51
+ #
52
+ # @param pid [Boolean, Symbol] Include process ID in logs. Options:
53
+ # - true: Include current process ID (default)
54
+ # - false: Exclude process ID
55
+ # - :global: Use globally unique process ID
56
+ # @param attribute_mapping [Hash] Custom attribute name mapping. Keys are original
57
+ # attribute names (symbols), values can be strings, arrays (for nested attributes),
58
+ # or procs for custom formatting.
59
+ # @param allow_all_attributes [Boolean] Whether to include all log entry attributes
60
+ # at the root level of JSON output. Default is true.
61
+ # @param max_message_length [Integer, nil] Maximum message length. Messages longer
62
+ # than this will be truncated. Default is nil (no truncation).
63
+ # @return [Hash] Complete mapping configuration for the JSON device
64
+ def json_mapping(pid: true, attribute_mapping: {}, allow_all_attributes: true, max_message_length: nil)
65
+ mapping = STANDARD_ATTRIBUTE_MAPPING.dup
66
+
67
+ if pid == :global
68
+ mapping[:pid] = ->(pid) { {"pid" => Lumberjack::Utils.global_pid(pid)} }
69
+ else
70
+ mapping.delete(:pid) unless pid
71
+ end
31
72
 
32
- def remap_attributes(attribute_mapping)
33
- @attribute_mapping = @attribute_mapping.merge(attribute_mapping)
34
- end
73
+ mapping.merge!(attribute_mapping.transform_keys(&:to_sym))
35
74
 
36
- def validate!
37
- if !max_message_length.nil? && (!max_message_length.is_a?(Integer) || max_message_length <= 0)
38
- raise ArgumentError, "max_message_length must be a positive integer"
39
- end
75
+ mapping[:attributes] = "*" if allow_all_attributes
40
76
 
41
- unless backtrace_cleaner.nil? || backtrace_cleaner.respond_to?(:clean)
42
- raise ArgumentError, "backtrace_cleaner must respond to #clean"
77
+ mapping[:message] = if max_message_length
78
+ truncate_message_transformer(max_message_length)
79
+ else
80
+ true
43
81
  end
44
- end
45
- end
46
-
47
- class << self
48
- def setup(stream = $stdout, options = {}, &block)
49
- config = Config.new
50
- yield(config) if block_given?
51
- config.validate!
52
82
 
53
- new_logger(stream, options, config)
83
+ mapping.transform_keys!(&:to_s)
54
84
  end
55
85
 
56
- private
86
+ def entry_formatter(backtrace_cleaner: nil)
87
+ Lumberjack::EntryFormatter.build do |formatter|
88
+ formatter.format_class(Exception) do |error|
89
+ Lumberjack::MessageAttributes.new(error.inspect, error: error)
90
+ end
57
91
 
58
- def new_logger(stream, options, config)
59
- logger = Lumberjack::Logger.new(json_device(stream, config), **options)
92
+ formatter.format_attributes(Exception, Lumberjack::DataDog::ExceptionAttributeFormatter.new(backtrace_cleaner: backtrace_cleaner))
60
93
 
61
- # Add the error to the error attribute if an exception is logged as the message.
62
- logger.message_formatter.add(Exception, message_exception_formatter)
94
+ formatter.format_attribute_name(:duration) do |seconds|
95
+ (seconds.to_f * 1_000_000_000).round
96
+ end
63
97
 
64
- # Split the error attribute up into standard attributes if it is an exception.
65
- logger.attribute_formatter.add(Exception, exception_attribute_formatter(config))
98
+ formatter.format_attribute_name(:duration_ms) do |millis|
99
+ nanoseconds = (millis.to_f * 1_000_000).round
100
+ Lumberjack::RemapAttribute.new("duration" => nanoseconds)
101
+ end
66
102
 
67
- if config.thread_name
68
- if config.thread_name == :global
69
- logger.tag!("logger.thread_name" => -> { Lumberjack::Utils.global_thread_id })
70
- else
71
- logger.tag!("logger.thread_name" => -> { Lumberjack::Utils.thread_name })
103
+ formatter.format_attribute_name(:duration_micros) do |micros|
104
+ nanoseconds = (micros.to_f * 1_000).round
105
+ Lumberjack::RemapAttribute.new("duration" => nanoseconds)
72
106
  end
73
- end
74
107
 
75
- if config.pid == :global
76
- logger.tag!("pid" => -> { Lumberjack::Utils.global_pid })
108
+ formatter.format_attribute_name(:duration_ns) do |ns|
109
+ Lumberjack::RemapAttribute.new("duration" => ns.to_i)
110
+ end
77
111
  end
78
-
79
- logger
80
112
  end
81
113
 
82
- def json_device(stream, config)
83
- Lumberjack::JsonDevice.new(output: stream, mapping: json_mapping(config), pretty: config.pretty)
84
- end
85
-
86
- def json_mapping(config)
87
- mapping = config.attribute_mapping.transform_keys(&:to_sym)
88
- mapping = mapping.merge(STANDARD_ATTRIBUTE_MAPPING)
89
-
90
- mapping.delete(:pid) if !config.pid || config.pid == :global
91
-
92
- mapping[:attributes] = "*" if config.allow_all_attributes
93
-
94
- mapping[:message] = if config.max_message_length
95
- truncate_message_transformer(config.max_message_length)
96
- else
97
- "message"
98
- end
99
-
100
- mapping[:duration] = duration_nanosecond_transformer(1_000_000_000)
101
- mapping[:duration_ms] = duration_nanosecond_transformer(1_000_000)
102
- mapping[:duration_micros] = duration_nanosecond_transformer(1_000)
103
- mapping[:duration_ns] = duration_nanosecond_transformer(1)
114
+ # Convenience method for setting up a Datadog logger with a block configuration.
115
+ #
116
+ # This method provides a block-based configuration approach that was more useful
117
+ # in earlier versions of Lumberjack. With Lumberjack 2+, you can achieve the same
118
+ # results more directly using {Lumberjack::Logger.new} with the :datadog device.
119
+ #
120
+ # @param stream [IO, String, Pathname] Output stream or path for log output.
121
+ # Default is $stdout.
122
+ # @param options [Hash] Additional logger options passed to Lumberjack::Logger.new
123
+ # @yield [Lumberjack::DataDog::Config] Block for configuring the logger behavior
124
+ # @return [Lumberjack::Logger] Configured logger instance
125
+ #
126
+ # @example Block configuration
127
+ # logger = Lumberjack::DataDog.setup($stdout, level: :info) do |config|
128
+ # config.max_message_length = 500
129
+ # config.pretty = true
130
+ # end
131
+ #
132
+ # @see Config
133
+ # @see Device
134
+ def setup(stream = $stdout, options = {}, &block)
135
+ config = Config.new
136
+ yield(config) if block_given?
137
+ config.validate!
104
138
 
105
- mapping.transform_keys!(&:to_s)
139
+ new_logger(stream, options, config)
106
140
  end
107
141
 
142
+ private
143
+
144
+ # Creates a transformer lambda that truncates messages to a maximum length.
145
+ #
146
+ # @param max_length [Integer] Maximum message length
147
+ # @return [Proc] Message truncation transformer
108
148
  def truncate_message_transformer(max_length)
109
149
  lambda do |msg|
110
150
  msg = msg.inspect unless msg.is_a?(String)
111
- msg = msg[0, max_length] if msg.is_a?(String) && msg.length > max_length
151
+ msg = "#{msg[0, max_length - 1]}…" if msg.length > max_length
112
152
  {"message" => msg}
113
153
  end
114
154
  end
115
155
 
116
- def duration_nanosecond_transformer(multiplier)
117
- lambda do |duration|
118
- if duration.is_a?(Numeric)
119
- {"duration" => (duration * multiplier).round}
120
- else
121
- {"duration" => nil}
122
- end
123
- end
124
- end
156
+ # Creates a new logger instance with the provided configuration.
157
+ #
158
+ # @param stream [IO, String, Pathname] Output stream or path
159
+ # @param options [Hash] Logger options to pass to Lumberjack::Logger.new
160
+ # @param config [Lumberjack::DataDog::Config] Configuration object with settings
161
+ # @return [Lumberjack::Logger] Configured logger instance
162
+ def new_logger(stream, options, config)
163
+ mapping = json_mapping(
164
+ pid: config.pid,
165
+ attribute_mapping: config.attribute_mapping,
166
+ allow_all_attributes: config.allow_all_attributes,
167
+ max_message_length: config.max_message_length
168
+ )
125
169
 
126
- def message_exception_formatter
127
- lambda do |error|
128
- Lumberjack::Formatter::TaggedMessage.new(error.inspect, error: error)
129
- end
130
- end
170
+ options = options.merge(output: stream, mapping: mapping, pretty: config.pretty)
171
+ logger = Lumberjack::Logger.new(:datadog, **options)
131
172
 
132
- def exception_attribute_formatter(config)
133
- lambda do |error|
134
- error_attributes = {"kind" => error.class.name, "message" => error.message}
135
- trace = error.backtrace
136
- if trace
137
- trace = config.backtrace_cleaner.clean(trace) if config.backtrace_cleaner
138
- error_attributes["stack"] = trace
173
+ # Deprecated behavior
174
+ if config.thread_name
175
+ if config.thread_name == :global
176
+ logger.tag!("logger.thread_name" => -> { Lumberjack::Utils.global_thread_id })
177
+ else
178
+ logger.tag!("logger.thread_name" => -> { Lumberjack::Utils.thread_name })
139
179
  end
140
- error_attributes
141
180
  end
181
+
182
+ logger
142
183
  end
143
184
  end
144
185
  end
186
+
187
+ require_relative "data_dog/config"
188
+ require_relative "data_dog/device"
189
+ require_relative "data_dog/exception_attribute_formatter"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lumberjack_data_dog
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
@@ -37,6 +37,9 @@ files:
37
37
  - README.md
38
38
  - VERSION
39
39
  - lib/lumberjack/data_dog.rb
40
+ - lib/lumberjack/data_dog/config.rb
41
+ - lib/lumberjack/data_dog/device.rb
42
+ - lib/lumberjack/data_dog/exception_attribute_formatter.rb
40
43
  - lib/lumberjack_data_dog.rb
41
44
  - lumberjack_data_dog.gemspec
42
45
  homepage: https://github.com/bdurand/lumberjack_data_dog