lumberjack_json_device 1.0.0 → 2.1.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/dependabot.yml +12 -0
- data/.github/workflows/continuous_integration.yml +46 -0
- data/.standard.yml +8 -0
- data/CHANGE_LOG.md +35 -2
- data/README.md +174 -27
- data/VERSION +1 -1
- data/lib/lumberjack_json_device.rb +129 -33
- data/lumberjack_json_device.gemspec +12 -14
- metadata +16 -54
- /data/{MIT_LICENSE → MIT_LICENSE.txt} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81b9e1a335053608bb8428db2f6828acd748c02ec0610c0c064e060ab94fe36c
|
4
|
+
data.tar.gz: 9a1bf5cb6cce667cde7837624be8db44826976abb4c07c9568481d89efd38c16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3226afbeeb3c72a0e60b940e26fa47973882d6f5770dff91d0d859087c0399a033c1dd54982da74a03fddd8e8fc7acc2849773b0a20bf362d1c4551314a5ba4e
|
7
|
+
data.tar.gz: 2068343690c36690deda5c7c6b91d9c9f1ba8663ad375f28f064ab88d9acdb9ffdb8912b04611c4898363c024bcc01d9e6fca8ab8293a58db3a72992234ff4b7
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Dependabot update strategy
|
2
|
+
version: 2
|
3
|
+
updates:
|
4
|
+
- package-ecosystem: bundler
|
5
|
+
directory: "/"
|
6
|
+
schedule:
|
7
|
+
interval: weekly
|
8
|
+
allow:
|
9
|
+
# Automatically keep all runtime dependencies updated
|
10
|
+
- dependency-name: "*"
|
11
|
+
dependency-type: "production"
|
12
|
+
versioning-strategy: lockfile-only
|
@@ -0,0 +1,46 @@
|
|
1
|
+
name: Continuous Integration
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- main
|
7
|
+
- actions-*
|
8
|
+
tags:
|
9
|
+
- v*
|
10
|
+
pull_request:
|
11
|
+
branches-ignore:
|
12
|
+
- actions-*
|
13
|
+
workflow_dispatch:
|
14
|
+
|
15
|
+
env:
|
16
|
+
BUNDLE_CLEAN: "true"
|
17
|
+
BUNDLE_PATH: vendor/bundle
|
18
|
+
BUNDLE_JOBS: 3
|
19
|
+
BUNDLE_RETRY: 3
|
20
|
+
|
21
|
+
jobs:
|
22
|
+
build:
|
23
|
+
name: ${{ matrix.ruby }} build
|
24
|
+
runs-on: ubuntu-latest
|
25
|
+
strategy:
|
26
|
+
matrix:
|
27
|
+
include:
|
28
|
+
- ruby: "ruby"
|
29
|
+
standardrb: true
|
30
|
+
- ruby: "3.0"
|
31
|
+
- ruby: "2.7"
|
32
|
+
- ruby: "2.5"
|
33
|
+
steps:
|
34
|
+
- uses: actions/checkout@v4
|
35
|
+
- name: Set up Ruby
|
36
|
+
uses: ruby/setup-ruby@v1
|
37
|
+
with:
|
38
|
+
ruby-version: ${{ matrix.ruby}}
|
39
|
+
- name: Install gems
|
40
|
+
run: |
|
41
|
+
bundle install
|
42
|
+
- name: Run Tests
|
43
|
+
run: bundle exec rake
|
44
|
+
- name: standardrb
|
45
|
+
if: matrix.standardrb
|
46
|
+
run: bundle exec standardrb
|
data/.standard.yml
ADDED
data/CHANGE_LOG.md
CHANGED
@@ -1,3 +1,36 @@
|
|
1
|
-
#
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
2
3
|
|
3
|
-
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
|
+
|
7
|
+
## 2.1.0
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
|
11
|
+
- Tags that contain arrays of hashes are no longer expanded to nested hashes if the hashes in the array use dot nottion in their keys. The hashes in the array will now be included as is in JSON output.
|
12
|
+
|
13
|
+
## 2.0.0
|
14
|
+
|
15
|
+
### Added
|
16
|
+
|
17
|
+
- Field mapping for the JSON can now be set to an array where the first element is the key to map and the second element is a callable object that will transform the value.
|
18
|
+
- Output can be set to pretty for better display in development environments.
|
19
|
+
- Added post_processor option to allow custom processing of the log entry hash before it is written to the output stream.
|
20
|
+
|
21
|
+
### Changed
|
22
|
+
|
23
|
+
- Tag structure is now consistently expanded from dot notation into nested hashes in the `tag` field. Previoulsly this was only done when the template copied tags to the root level of the JSON document.
|
24
|
+
- The mapping options now supports setting the value to `false` to exclude a field from the JSON output.
|
25
|
+
- Tag mapping can now be set to `"*"` to copy all tags into the root of the JSON document.
|
26
|
+
|
27
|
+
### Removed
|
28
|
+
|
29
|
+
- Remove gem dependency on `multi_json`. The gem now uses the `JSON` code from the Ruby standard library for consistency. `JSON` is also now amoung the fastest JSON libraries available in Ruby so performance is no longer a concern.
|
30
|
+
- Removed support for Ruby versions below 2.5.
|
31
|
+
|
32
|
+
## 1.0.0
|
33
|
+
|
34
|
+
### Added
|
35
|
+
|
36
|
+
- Initial release
|
data/README.md
CHANGED
@@ -1,13 +1,27 @@
|
|
1
1
|
# Lumberjack JSON Device
|
2
2
|
|
3
|
-
[](https://github.com/bdurand/lumberjack_json_device/actions/workflows/continuous_integration.yml)
|
4
|
+
[](https://github.com/testdouble/standard)
|
5
|
+
[](https://badge.fury.io/rb/lumberjack_json_device)
|
5
6
|
|
6
|
-
This gem provides a logging device for the [lumberjack](https://github.com/bdurand/lumberjack) gem that
|
7
|
+
This gem provides a logging device for the [lumberjack](https://github.com/bdurand/lumberjack) gem that outputs JSON formatted log entries to a stream with one JSON document per line. This format is ideal for structured logging pipelines and can be easily consumed by log aggregation services, search engines, and monitoring tools.
|
7
8
|
|
8
|
-
##
|
9
|
+
## Quick Start
|
9
10
|
|
10
|
-
|
11
|
+
```ruby
|
12
|
+
require 'lumberjack_json_device'
|
13
|
+
|
14
|
+
# Create a logger with JSON output to STDOUT
|
15
|
+
logger = Lumberjack::Logger.new(Lumberjack::JsonDevice.new(STDOUT))
|
16
|
+
|
17
|
+
# Log a message with tags
|
18
|
+
logger.info("User logged in", user_id: 123, session_id: "abc")
|
19
|
+
# Output: {"time":"2020-01-02T19:47:45.123455-0800","severity":"INFO","progname":null,"pid":12345,"message":"User logged in","tags":{"user_id":123,"session_id":"abc"}}
|
20
|
+
```
|
21
|
+
|
22
|
+
## Output Destinations
|
23
|
+
|
24
|
+
You can send the JSON output to either a stream or to another Lumberjack device.
|
11
25
|
|
12
26
|
```ruby
|
13
27
|
# Send to STDOUT
|
@@ -20,17 +34,23 @@ device = Lumberjack::JsonDevice.new(log_file)
|
|
20
34
|
|
21
35
|
## JSON Structure
|
22
36
|
|
23
|
-
By default, the JSON document
|
37
|
+
By default, the JSON document maps to the `Lumberjack::LogEntry` data structure and includes all standard fields:
|
24
38
|
|
25
39
|
```json
|
26
|
-
{"
|
40
|
+
{"time": "2020-01-02T19:47:45.123455-0800", "severity": "INFO", "progname": "web", "pid": 101, "message": "test", "tags": {"foo": "bar"}}
|
27
41
|
```
|
28
42
|
|
29
|
-
|
43
|
+
### Custom Field Mapping
|
44
|
+
|
45
|
+
You can customize the JSON document structure by providing a mapping that specifies how log entry fields should be transformed. The mapping supports several different value types:
|
30
46
|
|
31
|
-
|
47
|
+
- **String**: Maps the field to a custom JSON key name
|
48
|
+
- **Array**: Creates nested JSON structures
|
49
|
+
- **`true`**: Maps the field to the same name as the key
|
50
|
+
- **`false`**: Excludes the field from the JSON output
|
51
|
+
- **Callable**: Transforms the value using custom logic
|
32
52
|
|
33
|
-
|
53
|
+
You can map the standard field names (`time`, `severity`, `progname`, `pid`, `message`, and `tags`) as well as extract specific tags by name.
|
34
54
|
|
35
55
|
```ruby
|
36
56
|
device = Lumberjack::JsonDevice.new(STDOUT, mapping: {
|
@@ -39,36 +59,41 @@ device = Lumberjack::JsonDevice.new(STDOUT, mapping: {
|
|
39
59
|
progname: ["app", "name"],
|
40
60
|
pid: ["app", "pid"],
|
41
61
|
message: "message",
|
42
|
-
duration: "duration",
|
62
|
+
duration: "duration", # Extracts the "duration" tag
|
43
63
|
tags: "tags"
|
44
64
|
})
|
45
65
|
```
|
46
66
|
|
47
67
|
```json
|
48
|
-
{"timestamp": "2020-01-02T19:47:45.123455", "level": "INFO", "app": {"name": "web", "pid": 101}, "message": "test", "duration": 5, "tags": {"foo": "bar"}}
|
68
|
+
{"timestamp": "2020-01-02T19:47:45.123455-0800", "level": "INFO", "app": {"name": "web", "pid": 101}, "message": "test", "duration": 5, "tags": {"foo": "bar"}}
|
49
69
|
```
|
50
70
|
|
51
|
-
|
71
|
+
### Excluding Fields
|
72
|
+
|
73
|
+
If you omit fields from the mapping or set them to `false`, they will not appear in the JSON output:
|
52
74
|
|
53
75
|
```ruby
|
54
76
|
device = Lumberjack::JsonDevice.new(STDOUT, mapping: {
|
55
77
|
time: "timestamp",
|
56
78
|
severity: "level",
|
57
79
|
message: "message",
|
80
|
+
pid: false # Exclude PID from output
|
58
81
|
})
|
59
82
|
```
|
60
83
|
|
61
84
|
```json
|
62
|
-
{"timestamp": "2020-01-02T19:47:45.123455", "level": "INFO", "message": "test"}
|
85
|
+
{"timestamp": "2020-01-02T19:47:45.123455-0800", "level": "INFO", "message": "test"}
|
63
86
|
```
|
64
87
|
|
65
|
-
|
88
|
+
### Custom Transformations
|
89
|
+
|
90
|
+
You can provide a callable object (proc, lambda, or any object responding to `call`) to transform field values. The callable receives the original value and should return a hash that will be merged into the JSON document:
|
66
91
|
|
67
92
|
```ruby
|
68
93
|
device = Lumberjack::JsonDevice.new(STDOUT, mapping: {
|
69
|
-
time: lambda { |val|
|
94
|
+
time: lambda { |val| {timestamp: (val.to_f * 1000).round} },
|
70
95
|
severity: "level",
|
71
|
-
message: "
|
96
|
+
message: "message",
|
72
97
|
})
|
73
98
|
```
|
74
99
|
|
@@ -76,35 +101,157 @@ device = Lumberjack::JsonDevice.new(STDOUT, mapping: {
|
|
76
101
|
{"timestamp": 1578125375588, "level": "INFO", "message": "test"}
|
77
102
|
```
|
78
103
|
|
79
|
-
|
104
|
+
### Shortcut Mapping
|
105
|
+
|
106
|
+
Use `true` as a shortcut to map a field to the same name:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
device = Lumberjack::JsonDevice.new(STDOUT, mapping: {
|
110
|
+
time: "timestamp",
|
111
|
+
severity: true, # Maps to "severity"
|
112
|
+
progname: true, # Maps to "progname"
|
113
|
+
pid: false, # Excluded from output
|
114
|
+
message: "message",
|
115
|
+
tags: true # Maps to "tags"
|
116
|
+
})
|
117
|
+
```
|
118
|
+
|
119
|
+
### Tag Extraction and Dot Notation
|
120
|
+
|
121
|
+
You can extract specific tags from the log entry and map them to custom locations in the JSON. Tags with dot notation in their names are automatically expanded into nested structures:
|
80
122
|
|
81
123
|
```ruby
|
82
124
|
device = Lumberjack::JsonDevice.new(STDOUT, mapping: {
|
83
125
|
"message" => true,
|
84
|
-
"http.status" => true,
|
85
|
-
"http.method" => true,
|
86
|
-
"http.path" => true
|
126
|
+
"http.status" => true, # Extracts "http.status" tag
|
127
|
+
"http.method" => true, # Extracts "http.method" tag
|
128
|
+
"http.path" => true, # Extracts "http.path" tag
|
129
|
+
"tags" => true
|
87
130
|
})
|
88
131
|
```
|
89
132
|
|
90
133
|
```json
|
91
|
-
{"message": "test", "http": {"status": 200, "method": "GET", "path": "/resource"}}
|
134
|
+
{"message": "test", "http": {"status": 200, "method": "GET", "path": "/resource"}, "tags": {"other": "values"}}
|
92
135
|
```
|
93
136
|
|
137
|
+
**Important**: All tags are automatically expanded from dot notation into nested hash structures, not just extracted tags. For example, if you have a tag named `"user.profile.name"`, it will automatically become `{"user": {"profile": {"name": "value"}}}` in the tags section.
|
138
|
+
|
139
|
+
### Flattening Tags to Root Level
|
140
|
+
|
141
|
+
Use `"*"` as the tags mapping value to copy all remaining tags directly to the root level of the JSON document:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
device = Lumberjack::JsonDevice.new(STDOUT, mapping: {
|
145
|
+
"message" => true,
|
146
|
+
"tags" => "*"
|
147
|
+
})
|
148
|
+
```
|
149
|
+
|
150
|
+
```json
|
151
|
+
{"message": "test", "tag1": "value", "tag2": "value"}
|
152
|
+
```
|
94
153
|
|
95
154
|
## Data Formatting
|
96
155
|
|
97
|
-
The device
|
156
|
+
The device includes a `Lumberjack::Formatter` that formats objects before serializing them as JSON. You can add custom formatters for specific classes or supply your own formatter when creating the device.
|
98
157
|
|
99
158
|
```ruby
|
100
|
-
device.formatter.add(Exception,
|
101
|
-
device.formatter.add(ActiveRecord::Base,
|
159
|
+
device.formatter.add(Exception, Lumberjack::Formatter::InspectFormatter.new)
|
160
|
+
device.formatter.add(ActiveRecord::Base, Lumberjack::Formatter::IdFormatter.new)
|
102
161
|
```
|
103
162
|
|
104
|
-
|
163
|
+
### Dynamic Mapping
|
164
|
+
|
165
|
+
You can incrementally add field mappings after creating the device using the `map` method:
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
device.map(duration: "response_time", user_id: ["user", "id"])
|
169
|
+
```
|
170
|
+
|
171
|
+
### DateTime Formatting
|
172
|
+
|
173
|
+
You can specify the `datetime_format` that will be used to serialize Time and DateTime objects:
|
105
174
|
|
106
175
|
```ruby
|
107
176
|
device.datetime_format = "%Y-%m-%dT%H:%M:%S.%3N"
|
108
177
|
```
|
109
178
|
|
110
|
-
|
179
|
+
### Post Processing
|
180
|
+
|
181
|
+
You can provide a post processor that will be called on the hash before it is serialized to JSON. This allows you to modify any aspect of the log entry:
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
# Filter out sensitive elements using Rails parameter filter
|
185
|
+
param_filter = ActiveSupport::ParameterFilter.new(Rails.application.config.filter_parameters)
|
186
|
+
device = Lumberjack::JsonDevice.new(STDOUT, post_processor: ->(data) { param_filter.filter(data) }
|
187
|
+
```
|
188
|
+
|
189
|
+
Note that all hash keys will be strings.If the post processor does not return a hash, it will be ignored.
|
190
|
+
|
191
|
+
### Pretty Printing
|
192
|
+
|
193
|
+
For development or debugging, you can format the JSON output with indentation and newlines by setting the `pretty` option to `true`:
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
device = Lumberjack::JsonDevice.new(STDOUT, pretty: true)
|
197
|
+
```
|
198
|
+
|
199
|
+
This will format each log entry as multi-line JSON instead of single-line output. You can check if pretty formatting is enabled using the `pretty?` method:
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
device.pretty? # => true or false
|
203
|
+
```
|
204
|
+
|
205
|
+
### Empty Messages
|
206
|
+
|
207
|
+
Log entries with empty or nil messages will not be written to the output.
|
208
|
+
|
209
|
+
## Configuration Options
|
210
|
+
|
211
|
+
The `JsonDevice` constructor accepts the following options:
|
212
|
+
|
213
|
+
- **`mapping`**: Hash defining how log fields should be mapped to JSON (default: maps all standard fields)
|
214
|
+
- **`formatter`**: Custom `Lumberjack::Formatter` instance for formatting values before JSON serialization
|
215
|
+
- **`datetime_format`**: String format for Time/DateTime objects (default: `"%Y-%m-%dT%H:%M:%S.%6N%z"`)
|
216
|
+
- **`post_processor`**: Callable that receives and can modify the final hash before JSON serialization
|
217
|
+
- **`pretty`**: Boolean to enable pretty-printed JSON output (default: `false`)
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
device = Lumberjack::JsonDevice.new(
|
221
|
+
STDOUT,
|
222
|
+
mapping: { time: "timestamp", message: true, tags: "*" },
|
223
|
+
datetime_format: "%Y-%m-%d %H:%M:%S",
|
224
|
+
pretty: true,
|
225
|
+
post_processor: lambda { |data| data.merge(app: "myapp") }
|
226
|
+
)
|
227
|
+
```
|
228
|
+
|
229
|
+
## Installation
|
230
|
+
|
231
|
+
Add this line to your application's Gemfile:
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
gem "lumberjack_json_device"
|
235
|
+
```
|
236
|
+
|
237
|
+
And then execute:
|
238
|
+
|
239
|
+
```bash
|
240
|
+
bundle install
|
241
|
+
```
|
242
|
+
|
243
|
+
Or install it yourself as:
|
244
|
+
|
245
|
+
```bash
|
246
|
+
gem install lumberjack_json_device
|
247
|
+
```
|
248
|
+
|
249
|
+
## Contributing
|
250
|
+
|
251
|
+
Open a pull request on GitHub.
|
252
|
+
|
253
|
+
Please use the [standardrb](https://github.com/testdouble/standard) syntax and lint your code with `standardrb --fix` before submitting.
|
254
|
+
|
255
|
+
## License
|
256
|
+
|
257
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0
|
1
|
+
2.1.0
|
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require 'thread'
|
3
|
+
require "lumberjack"
|
4
|
+
require "json"
|
6
5
|
|
7
6
|
module Lumberjack
|
8
7
|
# This Lumberjack device logs output to another device as JSON formatted text with one document per line.
|
@@ -24,7 +23,6 @@ module Lumberjack
|
|
24
23
|
#
|
25
24
|
# You can create a nested JSON structure by specifying an array as the JSON key.
|
26
25
|
class JsonDevice < Device
|
27
|
-
|
28
26
|
DEFAULT_MAPPING = {
|
29
27
|
time: true,
|
30
28
|
severity: true,
|
@@ -37,15 +35,32 @@ module Lumberjack
|
|
37
35
|
DEFAULT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%6N%z"
|
38
36
|
|
39
37
|
attr_accessor :formatter
|
38
|
+
attr_accessor :post_processor
|
39
|
+
attr_writer :pretty
|
40
40
|
attr_reader :mapping
|
41
41
|
|
42
|
-
|
42
|
+
# @param stream_or_device [IO, Lumberjack::Device] The output stream or Lumberjack device to write
|
43
|
+
# the JSON formatted log entries to.
|
44
|
+
# @param mapping [Hash] A hash where the key is the log entry field name and the value indicates how
|
45
|
+
# to map the field if it exists. If the value is `true`, the field will be mapped to the same name.
|
46
|
+
# If the value is an array, it will be mapped to a nested structure that follows the array elements.
|
47
|
+
# If the value is a callable object, it will be called with the value and is expected to return
|
48
|
+
# a hash that will be merged into the JSON document.
|
49
|
+
# If the value is `false`, the field will not be included in the JSON output.
|
50
|
+
# @param formatter [Lumberjack::Formatter] An optional formatter to use for formatting the log entry data.
|
51
|
+
# @param datetime_format [String] An optional datetime format string to use for formatting the log timestamp.
|
52
|
+
# @param post_processor [Proc] An optional callable object that will be called with the log entry hash
|
53
|
+
# before it is written to the output stream. This can be used to modify the log entry data
|
54
|
+
# before it is serialized to JSON.
|
55
|
+
# @param pretty [Boolean] If true, the output will be formatted as pretty JSON with indentation and newlines.
|
56
|
+
# The default is false, which writes each log entry as a single line JSON document.
|
57
|
+
def initialize(stream_or_device, mapping: DEFAULT_MAPPING, formatter: nil, datetime_format: nil, post_processor: nil, pretty: false)
|
43
58
|
@mutex = Mutex.new
|
44
59
|
|
45
|
-
if stream_or_device.is_a?(Device)
|
46
|
-
|
60
|
+
@device = if stream_or_device.is_a?(Device)
|
61
|
+
stream_or_device
|
47
62
|
else
|
48
|
-
|
63
|
+
Lumberjack::Device::Writer.new(stream_or_device)
|
49
64
|
end
|
50
65
|
|
51
66
|
self.mapping = mapping
|
@@ -57,12 +72,17 @@ module Lumberjack
|
|
57
72
|
datetime_format = DEFAULT_TIME_FORMAT if datetime_format.nil?
|
58
73
|
end
|
59
74
|
add_datetime_formatter!(datetime_format) unless datetime_format.nil?
|
75
|
+
|
76
|
+
@post_processor = post_processor
|
77
|
+
|
78
|
+
@pretty = !!pretty
|
60
79
|
end
|
61
80
|
|
62
81
|
def write(entry)
|
63
|
-
return if entry.
|
82
|
+
return if entry.empty?
|
83
|
+
|
64
84
|
data = entry_as_json(entry)
|
65
|
-
json =
|
85
|
+
json = @pretty ? JSON.pretty_generate(data) : JSON.generate(data)
|
66
86
|
@device.write(json)
|
67
87
|
end
|
68
88
|
|
@@ -70,16 +90,31 @@ module Lumberjack
|
|
70
90
|
@device.flush
|
71
91
|
end
|
72
92
|
|
73
|
-
|
74
|
-
@datetime_format
|
75
|
-
end
|
93
|
+
attr_reader :datetime_format
|
76
94
|
|
77
95
|
# Set the datetime format for the log timestamp.
|
96
|
+
#
|
97
|
+
# @param format [String] The datetime format string to use for formatting the log timestamp.
|
78
98
|
def datetime_format=(format)
|
79
99
|
add_datetime_formatter!(format)
|
80
100
|
end
|
81
101
|
|
102
|
+
# Return true if the output is written in a multi-line pretty format. The default is to write each
|
103
|
+
# log entry as a single line JSON document.
|
104
|
+
#
|
105
|
+
# @return [Boolean]
|
106
|
+
def pretty?
|
107
|
+
!!@pretty
|
108
|
+
end
|
109
|
+
|
82
110
|
# Set the mapping for how to map an entry to a JSON object.
|
111
|
+
#
|
112
|
+
# @param mapping [Hash] A hash where the key is the log entry field name and the value is the JSON key.
|
113
|
+
# If the value is `true`, the field will be mapped to the same name
|
114
|
+
# If the value is an array, it will be mapped to a nested structure.
|
115
|
+
# If the value is a callable object, it will be called with the value and should return a hash that will be merged into the JSON document.
|
116
|
+
# If the value is `false`, the field will not be included in the JSON output.
|
117
|
+
# @return [void]
|
83
118
|
def mapping=(mapping)
|
84
119
|
@mutex.synchronize do
|
85
120
|
keys = {}
|
@@ -97,48 +132,110 @@ module Lumberjack
|
|
97
132
|
@pid_key = keys.delete(:pid)
|
98
133
|
@message_key = keys.delete(:message)
|
99
134
|
@tags_key = keys.delete(:tags)
|
100
|
-
@custom_keys = keys
|
135
|
+
@custom_keys = keys.map do |name, key|
|
136
|
+
[name.to_s.split("."), key]
|
137
|
+
end.to_h
|
101
138
|
@mapping = mapping
|
102
139
|
end
|
103
|
-
nil
|
104
140
|
end
|
105
141
|
|
142
|
+
# Add a field mapping to the existing mappings.
|
143
|
+
#
|
144
|
+
# @param field_mapping [Hash] A hash where the key is the log entry field name and the value is the JSON key.
|
145
|
+
# If the value is `true`, the field will be mapped to the same name
|
146
|
+
# If the value is an array, it will be mapped to a nested structure.
|
147
|
+
# If the value is a callable object, it will be called with the value and should return a hash that will be merged into the JSON document.
|
148
|
+
# If the value is `false`, the field will not be included in the JSON output.
|
149
|
+
# @return [void]
|
106
150
|
def map(field_mapping)
|
107
|
-
new_mapping =
|
108
|
-
field_mapping.each do |key, value|
|
109
|
-
new_mapping[key.to_sym] = value
|
110
|
-
end
|
151
|
+
new_mapping = field_mapping.transform_keys(&:to_sym)
|
111
152
|
self.mapping = mapping.merge(new_mapping)
|
112
153
|
end
|
113
154
|
|
114
155
|
# Convert a Lumberjack::LogEntry to a Hash using the specified field mapping.
|
156
|
+
#
|
157
|
+
# @param entry [Lumberjack::LogEntry] The log entry to convert.
|
158
|
+
# @return [Hash] A hash representing the log entry in JSON format.
|
115
159
|
def entry_as_json(entry)
|
116
160
|
data = {}
|
117
|
-
set_attribute(data, @time_key, entry.time)
|
118
|
-
set_attribute(data, @severity_key, entry.severity_label)
|
119
|
-
set_attribute(data, @
|
120
|
-
set_attribute(data, @
|
121
|
-
set_attribute(data, @
|
122
|
-
|
123
|
-
tags = entry.tags
|
124
|
-
|
125
|
-
|
161
|
+
set_attribute(data, @time_key, entry.time) if @time_key
|
162
|
+
set_attribute(data, @severity_key, entry.severity_label) if @severity_key
|
163
|
+
set_attribute(data, @message_key, entry.message) if @message_key
|
164
|
+
set_attribute(data, @progname_key, entry.progname) if @progname_key
|
165
|
+
set_attribute(data, @pid_key, entry.pid) if @pid_key
|
166
|
+
|
167
|
+
tags = Lumberjack::Utils.expand_tags(entry.tags) if entry.tags
|
168
|
+
extracted_tags = nil
|
169
|
+
if @custom_keys.size > 0 && !tags&.empty?
|
170
|
+
extracted_tags = []
|
126
171
|
@custom_keys.each do |name, key|
|
127
|
-
set_attribute(data, key, tags
|
172
|
+
set_attribute(data, key, tag_value(tags, name))
|
173
|
+
extracted_tags << name
|
174
|
+
end
|
175
|
+
|
176
|
+
extracted_tags.each do |path|
|
177
|
+
tags = deep_remove_tag(tags, path, entry.tags)
|
128
178
|
end
|
129
179
|
end
|
130
180
|
|
131
|
-
|
181
|
+
if @tags_key
|
132
182
|
tags ||= {}
|
133
|
-
|
183
|
+
if @tags_key == "*"
|
184
|
+
data = tags.merge(data) unless tags.empty?
|
185
|
+
else
|
186
|
+
set_attribute(data, @tags_key, tags)
|
187
|
+
end
|
134
188
|
end
|
135
189
|
|
136
190
|
data = @formatter.format(data) if @formatter
|
191
|
+
if @post_processor
|
192
|
+
processed_result = @post_processor.call(data)
|
193
|
+
data = processed_result if processed_result.is_a?(Hash)
|
194
|
+
end
|
195
|
+
|
137
196
|
data
|
138
197
|
end
|
139
198
|
|
140
199
|
private
|
141
200
|
|
201
|
+
def tag_value(tags, name)
|
202
|
+
return nil if tags.nil?
|
203
|
+
return tags[name] unless name.is_a?(Array)
|
204
|
+
|
205
|
+
val = tags[name.first]
|
206
|
+
return val if name.length == 1
|
207
|
+
return nil unless val.is_a?(Hash)
|
208
|
+
|
209
|
+
tag_value(val, name[1, name.length])
|
210
|
+
end
|
211
|
+
|
212
|
+
def deep_remove_tag(tags, path, original_tags)
|
213
|
+
return nil if tags.nil?
|
214
|
+
|
215
|
+
dup_needed = tags.equal?(original_tags)
|
216
|
+
key = path.first
|
217
|
+
val = tags[key] if path.length > 1
|
218
|
+
unless val.is_a?(Hash)
|
219
|
+
if tags.include?(key)
|
220
|
+
tags = tags.dup if dup_needed
|
221
|
+
tags.delete(key)
|
222
|
+
end
|
223
|
+
return tags
|
224
|
+
end
|
225
|
+
|
226
|
+
new_val = deep_remove_tag(val, path[1, path.length], original_tags[key])
|
227
|
+
if new_val.empty? || !new_val.equal?(val)
|
228
|
+
tags = tags.dup if dup_needed
|
229
|
+
if new_val.empty?
|
230
|
+
tags.delete(key)
|
231
|
+
else
|
232
|
+
tags[key] = new_val
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
tags
|
237
|
+
end
|
238
|
+
|
142
239
|
def set_attribute(data, key, value)
|
143
240
|
return if value.nil?
|
144
241
|
|
@@ -191,13 +288,12 @@ module Lumberjack
|
|
191
288
|
hash.merge!(other_hash) do |key, this_val, other_val|
|
192
289
|
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
193
290
|
deep_merge!(this_val, other_val, &block)
|
194
|
-
elsif
|
291
|
+
elsif block
|
195
292
|
block.call(key, this_val, other_val)
|
196
293
|
else
|
197
294
|
other_val
|
198
295
|
end
|
199
296
|
end
|
200
297
|
end
|
201
|
-
|
202
298
|
end
|
203
299
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
|
-
spec.name =
|
3
|
-
spec.version = File.read(File.
|
4
|
-
spec.authors = [
|
5
|
-
spec.email = [
|
2
|
+
spec.name = "lumberjack_json_device"
|
3
|
+
spec.version = File.read(File.join(__dir__, "VERSION")).strip
|
4
|
+
spec.authors = ["Brian Durand"]
|
5
|
+
spec.email = ["bbdurand@gmail.com"]
|
6
6
|
|
7
|
-
spec.summary = "A logging device for the lumberjack gem that writes log entries as JSON
|
7
|
+
spec.summary = "A logging device for the lumberjack gem that writes log entries as JSON documents for use with structured logging."
|
8
8
|
spec.homepage = "https://github.com/bdurand/lumberjack_json_device"
|
9
9
|
spec.license = "MIT"
|
10
10
|
|
11
11
|
# Specify which files should be added to the gem when it is released.
|
12
12
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
13
|
-
ignore_files = %w
|
13
|
+
ignore_files = %w[
|
14
14
|
.gitignore
|
15
15
|
.travis.yml
|
16
16
|
Appraisals
|
@@ -19,16 +19,14 @@ Gem::Specification.new do |spec|
|
|
19
19
|
Rakefile
|
20
20
|
gemfiles/
|
21
21
|
spec/
|
22
|
-
|
23
|
-
spec.files = Dir.chdir(
|
24
|
-
`git ls-files -z`.split("\x0").reject{ |f| ignore_files.any?{ |path| f.start_with?(path) } }
|
22
|
+
]
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
|
25
25
|
end
|
26
26
|
|
27
|
-
spec.require_paths = [
|
27
|
+
spec.require_paths = ["lib"]
|
28
28
|
|
29
|
-
spec.
|
30
|
-
spec.add_dependency "multi_json"
|
29
|
+
spec.required_ruby_version = ">= 2.5"
|
31
30
|
|
32
|
-
spec.
|
33
|
-
spec.add_development_dependency "rake"
|
31
|
+
spec.add_dependency "lumberjack", ">=1.3.3"
|
34
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lumberjack_json_device
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Durand
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-07-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: lumberjack
|
@@ -16,65 +16,26 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 1.3.3
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
27
|
-
|
28
|
-
name: multi_json
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rspec
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '3.0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '3.0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rake
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
|
-
description:
|
26
|
+
version: 1.3.3
|
27
|
+
description:
|
70
28
|
email:
|
71
29
|
- bbdurand@gmail.com
|
72
30
|
executables: []
|
73
31
|
extensions: []
|
74
32
|
extra_rdoc_files: []
|
75
33
|
files:
|
34
|
+
- ".github/dependabot.yml"
|
35
|
+
- ".github/workflows/continuous_integration.yml"
|
36
|
+
- ".standard.yml"
|
76
37
|
- CHANGE_LOG.md
|
77
|
-
- MIT_LICENSE
|
38
|
+
- MIT_LICENSE.txt
|
78
39
|
- README.md
|
79
40
|
- VERSION
|
80
41
|
- lib/lumberjack_json_device.rb
|
@@ -83,7 +44,7 @@ homepage: https://github.com/bdurand/lumberjack_json_device
|
|
83
44
|
licenses:
|
84
45
|
- MIT
|
85
46
|
metadata: {}
|
86
|
-
post_install_message:
|
47
|
+
post_install_message:
|
87
48
|
rdoc_options: []
|
88
49
|
require_paths:
|
89
50
|
- lib
|
@@ -91,15 +52,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
91
52
|
requirements:
|
92
53
|
- - ">="
|
93
54
|
- !ruby/object:Gem::Version
|
94
|
-
version: '
|
55
|
+
version: '2.5'
|
95
56
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
57
|
requirements:
|
97
58
|
- - ">="
|
98
59
|
- !ruby/object:Gem::Version
|
99
60
|
version: '0'
|
100
61
|
requirements: []
|
101
|
-
rubygems_version: 3.
|
102
|
-
signing_key:
|
62
|
+
rubygems_version: 3.4.10
|
63
|
+
signing_key:
|
103
64
|
specification_version: 4
|
104
|
-
summary: A logging device for the lumberjack gem that writes log entries as JSON
|
65
|
+
summary: A logging device for the lumberjack gem that writes log entries as JSON documents
|
66
|
+
for use with structured logging.
|
105
67
|
test_files: []
|
File without changes
|