lumberjack_data_dog 1.0.1 → 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: 1033352bd0532183f9ca57cc3ce6c6543755dbb76cae5efa1259573e42c95ae4
4
- data.tar.gz: cd54d5db4c1d8599e2980331e7c6f8e9885872747d409ca8b833504ef1152536
3
+ metadata.gz: 1ea1beb91821ea1831d491e84701eb98ebc172a8f5cf863a7eb06dead64db440
4
+ data.tar.gz: f5e2eaffefff58790484d5c44ea83decda71b3be3facde69862a51765736066d
5
5
  SHA512:
6
- metadata.gz: e654fe142fbf46f7565a9007049fcc897ba43d741f9759c725c3862398b2e32e2636f2ea6dfc37de04f6507f93b379353cef3fe3563df3b7a5d9d872479a2376
7
- data.tar.gz: ec7d80339a8cadefa306e7074ed1e8eaf9261fcb39a08f37355268706d2ae500c46c2bff57afdd560d6416d7f28683bce779db439b68250bd765c46f1bc6c90f
6
+ metadata.gz: 5632752f398949fba2646745caa31818eff0beb46c1015feecdb5074fadbeee6effa1fb1f7654d5b6dd7a1c82f156a592230029c5963b9c27bf9a20663b1ed3c
7
+ data.tar.gz: 8966d4801e557934aed6967f84893f13142b54074b02c9dc4adf8a505ff30a73715cbc9cb3a37390b22f938c699fc23618f3ac75f69757404916e8271b96dd98
@@ -3,10 +3,8 @@ name: Continuous Integration
3
3
  on:
4
4
  push:
5
5
  branches:
6
- - master
6
+ - main
7
7
  - actions-*
8
- tags:
9
- - v*
10
8
  pull_request:
11
9
  branches-ignore:
12
10
  - actions-*
@@ -29,9 +27,8 @@ jobs:
29
27
  standardrb: true
30
28
  - ruby: "3.0"
31
29
  - ruby: "2.7"
32
- - ruby: "2.5"
33
30
  steps:
34
- - uses: actions/checkout@v2
31
+ - uses: actions/checkout@v4
35
32
  - name: Set up Ruby
36
33
  uses: ruby/setup-ruby@v1
37
34
  with:
data/.standard.yml CHANGED
@@ -1,4 +1,4 @@
1
- ruby_version: 2.5
1
+ ruby_version: 2.7
2
2
 
3
3
  format: progress
4
4
 
data/CHANGE_LOG.md CHANGED
@@ -4,6 +4,32 @@ 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
+ ## 2.0.0
8
+
9
+ ### Added
10
+
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.
14
+
15
+ ### Changed
16
+
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.
28
+
29
+ ### Removed
30
+
31
+ - Support for Ruby < 2.7
32
+
7
33
  ## 1.0.1
8
34
 
9
35
  ### Added
data/README.md CHANGED
@@ -1,136 +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
- 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/).
8
8
 
9
9
  ## Features
10
10
 
11
- - **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:
12
12
  - `time` → `timestamp`
13
13
  - `severity` → `status`
14
14
  - `progname` → `logger.name`
15
15
  - `pid` → `pid`
16
16
  - **Exception Handling**: Structured exception logging with `kind`, `message`, and `stack` fields
17
- - **Duration Logging**: Automatic conversion of duration values to nanoseconds for DataDog
17
+ - **Duration Logging**: Automatic conversion of duration values to nanoseconds for Datadog
18
18
  - **Configurable Message Truncation**: Limit message length to prevent oversized logs
19
- - **Thread Information**: Optional thread name logging
20
- - **Tag Remapping**: Flexible tag transformation and remapping
21
- - **Pretty JSON**: Optional pretty-printed JSON output for development
19
+ - **Attribute Remapping**: Flexible attribute transformation and remapping
22
20
 
23
21
  ## Usage
24
22
 
25
23
  ### Basic Setup
26
24
 
27
25
  ```ruby
28
- require 'lumberjack_data_dog'
29
-
30
26
  # Create a logger that outputs to STDOUT
31
- logger = Lumberjack::DataDog.setup
27
+ logger = Lumberjack::Logger.new(:datadog)
32
28
 
33
- # Log messages
34
- logger.info("Application started")
35
- logger.warn("This is a warning", user_id: 123)
36
- 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")
37
31
  ```
38
32
 
39
33
  ### Advanced Configuration
40
34
 
35
+ You can pass options to the logger during initialization to further customize how entries are formatted:
36
+
41
37
  ```ruby
42
- # The output device and logger options can be passed in the setup method.
43
- # These are passed through to Lumberjack::Logger.new.
44
- logger = Lumberjack::DataDog.setup(log_device, level: :info) do |config|
45
- # Truncate messages longer than 1000 characters
46
- config.max_message_length = 1000
47
-
48
- # Include thread information
49
- config.thread_name = true # or :global for global thread ID
50
-
51
- # Disable PID logging
52
- config.pid = false
53
-
54
- # Remap custom tags to DataDog attributes
55
- config.remap_tags(
56
- user_id: "usr.id",
57
- request_id: "http.request_id"
58
- )
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
+ ```
59
48
 
60
- # Add a backtrace cleaner to remove unnecessary noise when logging exceptions.
61
- # The cleaner object must respond to `clean` method.
62
- config.backtrace_cleaner = Rails.backtrace_cleaner
49
+ #### Configuration Options
63
50
 
64
- # Enable pretty JSON for development
65
- config.pretty = true
66
- end
67
- ```
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. |
58
+
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.
68
61
 
69
- ### Logging to a File
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.
70
65
 
71
66
  ```ruby
72
- # Log to a file instead of STDOUT
73
- File.open("/var/log/app.log", "a") do |file|
74
- logger = Lumberjack::DataDog.setup(file)
75
- logger.info("Logged to file")
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
81
+ config.remap_attributes(
82
+ request_id: "trace_id", # Simple remapping
83
+ user_id: "usr.id" # Nested attribute mapping
84
+ )
85
+
86
+ # Pretty print JSON (useful for development)
87
+ config.pretty = true
88
+
89
+ # Custom backtrace cleaner for exceptions
90
+ config.backtrace_cleaner = MyCustomBacktraceCleaner.new
76
91
  end
77
92
  ```
78
93
 
94
+ ### Entry Formatter
95
+
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.
97
+
79
98
  ### Exception Logging
80
99
 
81
- Exceptions are automatically structured with DataDog's error attributes:
100
+ Exceptions are automatically structured with Datadog's standard error attributes.
82
101
 
83
102
  ```ruby
84
103
  begin
85
- raise StandardError, "Something went wrong"
104
+ do_something
86
105
  rescue => e
87
- # 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
88
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)
89
112
  end
90
113
  ```
91
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
+
92
121
  ### Duration Logging
93
122
 
94
- 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.
95
124
 
96
125
  ```ruby
97
- # Log execution time
98
- start_time = Time.now
99
- # ... do some work ...
100
- duration = Time.now - start_time
126
+ duration = Benchmark.realtime do
127
+ do_something
128
+ end
101
129
 
130
+ # duration is automatically converted to nanoseconds in the logs.
102
131
  logger.info("Operation completed", duration: duration)
103
- # duration is automatically converted to nanoseconds
104
132
 
105
- # You can also use specific duration units
133
+ # You can also use specific duration units.
106
134
  logger.info("DB query", duration_ms: 150.5) # milliseconds
107
135
  logger.info("API call", duration_micros: 1500) # microseconds
108
136
  logger.info("Function", duration_ns: 1500000) # nanoseconds
109
137
  ```
110
138
 
111
- ### Custom Tag Transformation
112
-
113
- ```ruby
114
- logger = Lumberjack::DataDog.setup do |config|
115
- config.remap_tags(
116
- # Simple remapping
117
- correlation_id: "trace.correlation_id",
118
-
119
- # Transform with a lambda
120
- user_email: ->(email) { {"usr.email" => email.downcase} }
121
- )
122
- end
123
-
124
- logger.info("User logged in", user_email: "USER@EXAMPLE.COM")
125
- # Results in usr.email: "user@example.com"
126
- ```
127
-
128
139
  ## Installation
129
140
 
130
141
  Add this line to your application's Gemfile:
131
142
 
132
143
  ```ruby
133
- gem "lumberjack_data_dog"
144
+ gem "lumberjack_datadog"
134
145
  ```
135
146
 
136
147
  And then execute:
@@ -140,7 +151,7 @@ $ bundle install
140
151
 
141
152
  Or install it yourself as:
142
153
  ```bash
143
- $ gem install lumberjack_data_dog
154
+ $ gem install lumberjack_datadog
144
155
  ```
145
156
 
146
157
  ## Contributing
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
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_tags
19
- attr_reader :tag_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_tags = true
28
- @tag_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_tags(tag_mapping)
33
- @tag_mapping = @tag_mapping.merge(tag_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 tag 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 tag up into standard attributes if it is an exception.
65
- logger.tag_formatter.add(Exception, exception_tag_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_globally("logger.thread_name" => -> { Lumberjack::Utils.global_thread_id })
70
- else
71
- logger.tag_globally("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_globally("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(stream, mapping: json_mapping(config), pretty: config.pretty)
84
- end
85
-
86
- def json_mapping(config)
87
- mapping = config.tag_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[:tags] = "*" if config.allow_all_tags
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_tag_formatter(config)
133
- lambda do |error|
134
- error_tags = {"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_tags["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_tags
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"
@@ -8,6 +8,12 @@ Gem::Specification.new do |spec|
8
8
  spec.homepage = "https://github.com/bdurand/lumberjack_data_dog"
9
9
  spec.license = "MIT"
10
10
 
11
+ spec.metadata = {
12
+ "homepage_uri" => spec.homepage,
13
+ "source_code_uri" => spec.homepage,
14
+ "changelog_uri" => "#{spec.homepage}/blob/main/CHANGE_LOG.md"
15
+ }
16
+
11
17
  # Specify which files should be added to the gem when it is released.
12
18
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
13
19
  ignore_files = %w[
@@ -26,7 +32,7 @@ Gem::Specification.new do |spec|
26
32
 
27
33
  spec.require_paths = ["lib"]
28
34
 
29
- spec.required_ruby_version = ">= 2.5"
35
+ spec.required_ruby_version = ">= 2.7"
30
36
 
31
- spec.add_dependency "lumberjack_json_device", ">= 2.0.0"
37
+ spec.add_dependency "lumberjack_json_device", ">= 3.0.0"
32
38
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lumberjack_data_dog
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-07-19 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: lumberjack_json_device
@@ -16,15 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: 2.0.0
18
+ version: 3.0.0
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: 2.0.0
27
- description:
25
+ version: 3.0.0
28
26
  email:
29
27
  - bbdurand@gmail.com
30
28
  executables: []
@@ -39,13 +37,18 @@ files:
39
37
  - README.md
40
38
  - VERSION
41
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
42
43
  - lib/lumberjack_data_dog.rb
43
44
  - lumberjack_data_dog.gemspec
44
45
  homepage: https://github.com/bdurand/lumberjack_data_dog
45
46
  licenses:
46
47
  - MIT
47
- metadata: {}
48
- post_install_message:
48
+ metadata:
49
+ homepage_uri: https://github.com/bdurand/lumberjack_data_dog
50
+ source_code_uri: https://github.com/bdurand/lumberjack_data_dog
51
+ changelog_uri: https://github.com/bdurand/lumberjack_data_dog/blob/main/CHANGE_LOG.md
49
52
  rdoc_options: []
50
53
  require_paths:
51
54
  - lib
@@ -53,15 +56,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
53
56
  requirements:
54
57
  - - ">="
55
58
  - !ruby/object:Gem::Version
56
- version: '2.5'
59
+ version: '2.7'
57
60
  required_rubygems_version: !ruby/object:Gem::Requirement
58
61
  requirements:
59
62
  - - ">="
60
63
  - !ruby/object:Gem::Version
61
64
  version: '0'
62
65
  requirements: []
63
- rubygems_version: 3.4.10
64
- signing_key:
66
+ rubygems_version: 3.6.9
65
67
  specification_version: 4
66
68
  summary: Lumberjack logging device that outputs JSON formatted for DataDog with standard
67
69
  attribute mapping.