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 +4 -4
- data/.github/workflows/continuous_integration.yml +2 -5
- data/.standard.yml +1 -1
- data/CHANGE_LOG.md +26 -0
- data/README.md +85 -74
- data/VERSION +1 -1
- data/lib/lumberjack/data_dog/config.rb +95 -0
- data/lib/lumberjack/data_dog/device.rb +65 -0
- data/lib/lumberjack/data_dog/exception_attribute_formatter.rb +51 -0
- data/lib/lumberjack/data_dog.rb +144 -99
- data/lumberjack_data_dog.gemspec +8 -2
- metadata +13 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ea1beb91821ea1831d491e84701eb98ebc172a8f5cf863a7eb06dead64db440
|
|
4
|
+
data.tar.gz: f5e2eaffefff58790484d5c44ea83decda71b3be3facde69862a51765736066d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
-
|
|
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@
|
|
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
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
|
|
1
|
+
# Lumberjack Datadog
|
|
2
2
|
|
|
3
|
-
[](https://github.com/bdurand/lumberjack_datadog/actions/workflows/continuous_integration.yml)
|
|
4
4
|
[](https://github.com/testdouble/standard)
|
|
5
|
-
[](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
|
|
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
|
-
- **
|
|
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
|
|
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
|
-
- **
|
|
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::
|
|
27
|
+
logger = Lumberjack::Logger.new(:datadog)
|
|
32
28
|
|
|
33
|
-
#
|
|
34
|
-
logger.
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
61
|
-
# The cleaner object must respond to `clean` method.
|
|
62
|
-
config.backtrace_cleaner = Rails.backtrace_cleaner
|
|
49
|
+
#### Configuration Options
|
|
63
50
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
100
|
+
Exceptions are automatically structured with Datadog's standard error attributes.
|
|
82
101
|
|
|
83
102
|
```ruby
|
|
84
103
|
begin
|
|
85
|
-
|
|
104
|
+
do_something
|
|
86
105
|
rescue => e
|
|
87
|
-
# Results in
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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 "
|
|
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
|
|
154
|
+
$ gem install lumberjack_datadog
|
|
144
155
|
```
|
|
145
156
|
|
|
146
157
|
## Contributing
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
data/lib/lumberjack/data_dog.rb
CHANGED
|
@@ -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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
@tag_mapping = @tag_mapping.merge(tag_mapping)
|
|
34
|
-
end
|
|
73
|
+
mapping.merge!(attribute_mapping.transform_keys(&:to_sym))
|
|
35
74
|
|
|
36
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
83
|
+
mapping.transform_keys!(&:to_s)
|
|
54
84
|
end
|
|
55
85
|
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
94
|
+
formatter.format_attribute_name(:duration) do |seconds|
|
|
95
|
+
(seconds.to_f * 1_000_000_000).round
|
|
96
|
+
end
|
|
63
97
|
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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"
|
data/lumberjack_data_dog.gemspec
CHANGED
|
@@ -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.
|
|
35
|
+
spec.required_ruby_version = ">= 2.7"
|
|
30
36
|
|
|
31
|
-
spec.add_dependency "lumberjack_json_device", ">=
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|