lumberjack_json_device 2.2.0 → 3.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: 5056428eb1c260cf68a1742064c824645ef8df0c71164a20fd8f03bb5b3f1bbc
4
- data.tar.gz: 69ff5c7d4c88a90b5be63b9d77724b4c36f090a109791c0819bbe0f97934d0f7
3
+ metadata.gz: 93f11eda1a4055d1cc3b6ee2b9577e28f0a26ae937f88e49bc5c07aa0c1b327d
4
+ data.tar.gz: 96d500179158ea397ed2f52a3a096520125350cc673e5de5dbe352203b607d3f
5
5
  SHA512:
6
- metadata.gz: 03f44e6637e5fd9df15ccef421ebafcc035498ff272afae0c3845997bfd63099a11bc2425051fa4a94e8bf20eef7419ee961511b6fcba1a675537fc8ebaf28f3
7
- data.tar.gz: a5a0bfdc425825484dd1cd0c95ea788919189c594cf5fb7ed55107e14655b7be55a2826f71758b2b1c93e91695c2506f84eeacc690e88919b579da339abddba0
6
+ metadata.gz: 00773c4aa83e49275b69fa0d400d8c17783008aa013a0b84b881acd868ab9e3ce6f39e5fc183b8d1ce2584c7e306363cfb7aa60d9045edacd100c66821601d85
7
+ data.tar.gz: 396d28c5697ded1ef621a959287c356b71a2e33942510cc72c88d32bd41e77c548d0d359df852e41a7ebeff1b60b839c092954e654a7dbbd6b2bc322b207eb1b
@@ -5,8 +5,6 @@ on:
5
5
  branches:
6
6
  - main
7
7
  - actions-*
8
- tags:
9
- - v*
10
8
  pull_request:
11
9
  branches-ignore:
12
10
  - actions-*
@@ -27,9 +25,9 @@ jobs:
27
25
  include:
28
26
  - ruby: "ruby"
29
27
  standardrb: true
28
+ yard: true
30
29
  - ruby: "3.0"
31
30
  - ruby: "2.7"
32
- - ruby: "2.5"
33
31
  steps:
34
32
  - uses: actions/checkout@v4
35
33
  - name: Set up Ruby
@@ -44,3 +42,6 @@ jobs:
44
42
  - name: standardrb
45
43
  if: matrix.standardrb
46
44
  run: bundle exec standardrb
45
+ - name: yard
46
+ if: matrix.yard
47
+ run: bundle exec yard --fail-on-warning
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,44 @@ 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
+ ## 3.0.0
8
+
9
+ ### Added
10
+
11
+ - Support for Lumberjack 2.0.
12
+ - **Breaking Change** The constructor now takes an options hash rather than keyword arguments.
13
+ - Added the `:output` key to the constructor options hash to specify the output stream. This argument can take either a stream or a file path.
14
+ - Added `:utc` option to force timestamps to be in UTC.
15
+
16
+ ### Changed
17
+
18
+ - **Breaking Change** Tags are now called attributes and are put in the `"attributes"` JSON field by default. If you want to keep the old behavior, you will need to set the mapping to:
19
+
20
+ ```ruby
21
+ {
22
+ time: true,
23
+ severity: true,
24
+ progname: true,
25
+ pid: true,
26
+ message: true,
27
+ attributes: ["tags"]
28
+ }
29
+ ```
30
+
31
+ ### Deprecated
32
+
33
+ - Deprecated passing in the output stream as the first positional argument. The output stream should now be specified using the `:output` key in the options hash.
34
+
35
+ ### Removed
36
+
37
+ - Support for Ruby versions less than 2.7
38
+
39
+ ## 2.2.1
40
+
41
+ ## Changed
42
+
43
+ - Added error handling for JSON serialization. This ensures that logs can still be written to even if an error occurs during serialization.
44
+
7
45
  ## 2.2.0
8
46
 
9
47
  ### Changed
@@ -29,9 +67,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
29
67
 
30
68
  ### Changed
31
69
 
32
- - 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.
70
+ - Tag structure is now consistently expanded from dot notation into nested hashes in the `attribute` field. Previoulsly this was only done when the template copied attributes to the root level of the JSON document.
33
71
  - The mapping options now supports setting the value to `false` to exclude a field from the JSON output.
34
- - Tag mapping can now be set to `"*"` to copy all tags into the root of the JSON document.
72
+ - Tag mapping can now be set to `"*"` to copy all attributes into the root of the JSON document.
35
73
 
36
74
  ### Removed
37
75
 
data/README.md CHANGED
@@ -4,43 +4,49 @@
4
4
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
5
5
  [![Gem Version](https://badge.fury.io/rb/lumberjack_json_device.svg)](https://badge.fury.io/rb/lumberjack_json_device)
6
6
 
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
+ This gem provides a logging device for the [lumberjack](https://github.com/bdurand/lumberjack) gem that outputs [JSONL](https://jsonlines.org/) formatted log entries to a stream. This format with one JSON document per line is ideal for structured logging pipelines and can be easily consumed by log aggregation services, search engines, and monitoring tools.
8
8
 
9
- ## Quick Start
9
+ ## Usage
10
+
11
+ ### Quick Start
10
12
 
11
13
  ```ruby
12
14
  require 'lumberjack_json_device'
13
15
 
14
16
  # Create a logger with JSON output to STDOUT
15
- logger = Lumberjack::Logger.new(Lumberjack::JsonDevice.new(STDOUT))
17
+ logger = Lumberjack::Logger.new(Lumberjack::JsonDevice.new(output: STDOUT))
16
18
 
17
- # Log a message with tags
19
+ # Log a message with attributes
18
20
  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
  ```
21
22
 
22
- ## Output Destinations
23
+ This will output JSON like:
24
+
25
+ ```
26
+ { "time":"2020-01-02T19:47:45.123456-0800","severity":"INFO","progname":null,"pid":12345,"message":"User logged in","attributes":{ "user_id":123,"session_id":"abc" } }
27
+ ```
28
+
29
+ ### Output Destinations
23
30
 
24
31
  You can send the JSON output to either a stream or to another Lumberjack device.
25
32
 
26
33
  ```ruby
27
- # Send to STDOUT
28
- device = Lumberjack::JsonDevice.new(STDOUT)
34
+ # Send to stream (STDOUT is the default)
35
+ device = Lumberjack::JsonDevice.new(output: STDOUT)
29
36
 
30
- # Send to another logging device
31
- log_file = Lumberjack::Device::LogFile.new("/var/log/app.log")
32
- device = Lumberjack::JsonDevice.new(log_file)
37
+ # Send to a log file
38
+ device = Lumberjack::JsonDevice.new(output: "/var/log/app.log")
33
39
  ```
34
40
 
35
- ## JSON Structure
41
+ ### JSON Structure
36
42
 
37
43
  By default, the JSON document maps to the `Lumberjack::LogEntry` data structure and includes all standard fields:
38
44
 
39
- ```json
40
- {"time": "2020-01-02T19:47:45.123455-0800", "severity": "INFO", "progname": "web", "pid": 101, "message": "test", "tags": {"foo": "bar"}}
45
+ ```
46
+ { "time": "2020-01-02T19:47:45.123456-0800", "severity": "INFO", "progname": "web", "pid": 101, "message": "test", "attributes": { "foo": "bar" } }
41
47
  ```
42
48
 
43
- ### Custom Field Mapping
49
+ #### Custom Field Mapping
44
50
 
45
51
  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:
46
52
 
@@ -50,117 +56,146 @@ You can customize the JSON document structure by providing a mapping that specif
50
56
  - **`false`**: Excludes the field from the JSON output
51
57
  - **Callable**: Transforms the value using custom logic
52
58
 
53
- You can map the standard field names (`time`, `severity`, `progname`, `pid`, `message`, and `tags`) as well as extract specific tags by name.
59
+ You can map the standard field names (`time`, `severity`, `progname`, `pid`, `message`, and `attributes`) as well as extract specific attributes by name.
54
60
 
55
61
  ```ruby
56
- device = Lumberjack::JsonDevice.new(STDOUT, mapping: {
57
- time: "timestamp",
58
- severity: "level",
59
- progname: ["app", "name"],
60
- pid: ["app", "pid"],
61
- message: "message",
62
- duration: "duration", # Extracts the "duration" tag
63
- tags: "tags"
64
- })
62
+ device = Lumberjack::JsonDevice.new(
63
+ output: STDOUT,
64
+ mapping: {
65
+ time: "timestamp",
66
+ severity: "level",
67
+ progname: ["app", "name"],
68
+ pid: ["app", "pid"],
69
+ message: "message",
70
+ duration: "duration", # Extracts the "duration" attribute
71
+ attributes: "attributes"
72
+ }
73
+ )
65
74
  ```
66
75
 
67
- ```json
68
- {"timestamp": "2020-01-02T19:47:45.123455-0800", "level": "INFO", "app": {"name": "web", "pid": 101}, "message": "test", "duration": 5, "tags": {"foo": "bar"}}
76
+ Example output:
77
+
78
+ ```
79
+ { "timestamp": "2020-01-02T19:47:45.123456-0800", "level": "INFO", "app": { "name": "web", "pid": 101 }, "message": "test", "duration": 5, "attributes": { "foo": "bar" } }
69
80
  ```
70
81
 
71
- ### Excluding Fields
82
+ #### Excluding Fields
72
83
 
73
84
  If you omit fields from the mapping or set them to `false`, they will not appear in the JSON output:
74
85
 
75
86
  ```ruby
76
- device = Lumberjack::JsonDevice.new(STDOUT, mapping: {
77
- time: "timestamp",
78
- severity: "level",
79
- message: "message",
80
- pid: false # Exclude PID from output
81
- })
87
+ device = Lumberjack::JsonDevice.new(
88
+ output: STDOUT,
89
+ mapping: {
90
+ time: "timestamp",
91
+ severity: "level",
92
+ message: "message",
93
+ pid: false # Exclude PID from output
94
+ }
95
+ )
82
96
  ```
83
97
 
84
- ```json
85
- {"timestamp": "2020-01-02T19:47:45.123455-0800", "level": "INFO", "message": "test"}
98
+ Example output:
99
+
100
+ ```
101
+ { "timestamp": "2020-01-02T19:47:45.123456-0800", "level": "INFO", "message": "test" }
86
102
  ```
87
103
 
88
- ### Custom Transformations
104
+ #### Custom Transformations
89
105
 
90
106
  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:
91
107
 
92
108
  ```ruby
93
- device = Lumberjack::JsonDevice.new(STDOUT, mapping: {
94
- time: lambda { |val| {timestamp: (val.to_f * 1000).round} },
95
- severity: "level",
96
- message: "message",
97
- })
109
+ device = Lumberjack::JsonDevice.new(
110
+ output: STDOUT,
111
+ mapping: {
112
+ time: lambda { |val| { timestamp: (val.to_f * 1000).round } },
113
+ severity: "level",
114
+ message: "message"
115
+ }
116
+ )
98
117
  ```
99
118
 
100
- ```json
101
- {"timestamp": 1578125375588, "level": "INFO", "message": "test"}
119
+ Example output:
120
+
121
+ ```
122
+ { "timestamp": 1578125375588, "level": "INFO", "message": "test" }
102
123
  ```
103
124
 
104
- ### Shortcut Mapping
125
+ #### Shortcut Mapping
105
126
 
106
127
  Use `true` as a shortcut to map a field to the same name:
107
128
 
108
129
  ```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
- })
130
+ device = Lumberjack::JsonDevice.new(
131
+ output: STDOUT,
132
+ mapping: {
133
+ time: "timestamp",
134
+ severity: true, # Maps to "severity"
135
+ progname: true, # Maps to "progname"
136
+ pid: false, # Excluded from output
137
+ message: "message",
138
+ attributes: true # Maps to "attributes"
139
+ }
140
+ )
117
141
  ```
118
142
 
119
- ### Tag Extraction and Dot Notation
143
+ #### Tag Extraction and Dot Notation
120
144
 
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:
145
+ You can extract specific attributes 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:
122
146
 
123
147
  ```ruby
124
- device = Lumberjack::JsonDevice.new(STDOUT, mapping: {
125
- "message" => 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
130
- })
148
+ device = Lumberjack::JsonDevice.new(
149
+ output: STDOUT,
150
+ mapping: {
151
+ message: true,
152
+ "http.status": true, # Extracts "http.status" attribute
153
+ "http.method": true, # Extracts "http.method" attribute
154
+ "http.path": true, # Extracts "http.path" attribute
155
+ attributes: true
156
+ }
157
+ )
131
158
  ```
132
159
 
133
- ```json
134
- {"message": "test", "http": {"status": 200, "method": "GET", "path": "/resource"}, "tags": {"other": "values"}}
160
+ Example output:
161
+
162
+ ```
163
+ { "message": "test", "http": { "status": 200, "method": "GET", "path": "/resource" }, "attributes": { "other": "values" } }
135
164
  ```
136
165
 
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.
166
+ **Important**: All attributes are automatically expanded from dot notation into nested hash structures, not just extracted attributes. For example, if you have an attribute named `"user.profile.name"`, it will automatically become `{"user": {"profile": {"name": "value"}}}` in the attributes section.
138
167
 
139
- ### Flattening Tags to Root Level
168
+ #### Flattening Tags to Root Level
140
169
 
141
- Use `"*"` as the tags mapping value to copy all remaining tags directly to the root level of the JSON document:
170
+ Use `"*"` as the attributes mapping value to copy all remaining attributes directly to the root level of the JSON document:
142
171
 
143
172
  ```ruby
144
- device = Lumberjack::JsonDevice.new(STDOUT, mapping: {
145
- "message" => true,
146
- "tags" => "*"
147
- })
173
+ device = Lumberjack::JsonDevice.new(
174
+ output: STDOUT,
175
+ mapping: {
176
+ message: true,
177
+ attributes: "*"
178
+ }
179
+ )
148
180
  ```
149
181
 
150
- ```json
151
- {"message": "test", "tag1": "value", "tag2": "value"}
182
+ Example output:
183
+
184
+ ```
185
+ { "message": "test", "attribute1": "value", "attribute2": "value" }
152
186
  ```
153
187
 
154
- ## Data Formatting
188
+ ### Data Formatting
155
189
 
156
190
  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.
157
191
 
158
192
  ```ruby
159
- device.formatter.add(Exception, Lumberjack::Formatter::InspectFormatter.new)
160
- device.formatter.add(ActiveRecord::Base, Lumberjack::Formatter::IdFormatter.new)
193
+ device.formatter.add(Exception, :inspect)
194
+ device.formatter.add(ActiveRecord::Base, :id)
195
+ device.formatter.add("User") { |user| user.username }
161
196
  ```
162
197
 
163
- ### Dynamic Mapping
198
+ #### Dynamic Mapping
164
199
 
165
200
  You can incrementally add field mappings after creating the device using the `map` method:
166
201
 
@@ -168,7 +203,7 @@ You can incrementally add field mappings after creating the device using the `ma
168
203
  device.map(duration: "response_time", user_id: ["user", "id"])
169
204
  ```
170
205
 
171
- ### DateTime Formatting
206
+ #### DateTime Formatting
172
207
 
173
208
  You can specify the `datetime_format` that will be used to serialize Time and DateTime objects:
174
209
 
@@ -176,24 +211,29 @@ You can specify the `datetime_format` that will be used to serialize Time and Da
176
211
  device.datetime_format = "%Y-%m-%dT%H:%M:%S.%3N"
177
212
  ```
178
213
 
179
- ### Post Processing
214
+ The default format is [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) with millisecond precision.
215
+
216
+ #### Post Processing
180
217
 
181
218
  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
219
 
183
220
  ```ruby
184
221
  # Filter out sensitive elements using Rails parameter filter
185
222
  param_filter = ActiveSupport::ParameterFilter.new(Rails.application.config.filter_parameters)
186
- device = Lumberjack::JsonDevice.new(STDOUT, post_processor: ->(data) { param_filter.filter(data) }
223
+ device = Lumberjack::JsonDevice.new(
224
+ output: STDOUT,
225
+ post_processor: ->(data) { param_filter.filter(data) }
226
+ )
187
227
  ```
188
228
 
189
229
  Note that all hash keys will be strings and the values will be JSON-safe. If the post processor does not return a hash, it will be ignored.
190
230
 
191
- ### Pretty Printing
231
+ #### Pretty Printing
192
232
 
193
233
  For development or debugging, you can format the JSON output with indentation and newlines by setting the `pretty` option to `true`:
194
234
 
195
235
  ```ruby
196
- device = Lumberjack::JsonDevice.new(STDOUT, pretty: true)
236
+ device = Lumberjack::JsonDevice.new(output: STDOUT, pretty: true)
197
237
  ```
198
238
 
199
239
  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:
@@ -202,24 +242,26 @@ This will format each log entry as multi-line JSON instead of single-line output
202
242
  device.pretty? # => true or false
203
243
  ```
204
244
 
205
- ### Empty Messages
245
+ #### Empty Messages
206
246
 
207
247
  Log entries with empty or nil messages will not be written to the output.
208
248
 
209
- ## Configuration Options
249
+ ### Configuration Options
210
250
 
211
251
  The `JsonDevice` constructor accepts the following options:
212
252
 
253
+ - **`output`**: The output stream, file path, or Lumberjack device to write to (default: STDOUT)
213
254
  - **`mapping`**: Hash defining how log fields should be mapped to JSON (default: maps all standard fields)
214
255
  - **`formatter`**: Custom `Lumberjack::Formatter` instance for formatting values before JSON serialization
215
256
  - **`datetime_format`**: String format for Time/DateTime objects (default: `"%Y-%m-%dT%H:%M:%S.%6N%z"`)
216
257
  - **`post_processor`**: Callable that receives and can modify the final hash before JSON serialization
217
258
  - **`pretty`**: Boolean to enable pretty-printed JSON output (default: `false`)
259
+ - **`utc`**: Boolean to force timestamps to UTC before formatting (default: `false`)
218
260
 
219
261
  ```ruby
220
262
  device = Lumberjack::JsonDevice.new(
221
- STDOUT,
222
- mapping: { time: "timestamp", message: true, tags: "*" },
263
+ output: STDOUT,
264
+ mapping: { time: "timestamp", message: true, attributes: "*" },
223
265
  datetime_format: "%Y-%m-%d %H:%M:%S",
224
266
  pretty: true,
225
267
  post_processor: lambda { |data| data.merge(app: "myapp") }
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.2.0
1
+ 3.0.0
@@ -0,0 +1,382 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lumberjack"
4
+ require "json"
5
+ require "time"
6
+
7
+ # Lumberjack is a simple, powerful, and fast logging library for Ruby that
8
+ # provides a consistent interface for logging across different output streams.
9
+ module Lumberjack
10
+ # This Lumberjack device logs output to another device as JSON formatted text with one document per line.
11
+ # This format (JSONL) is ideal for structured logging pipelines and can be easily consumed by log
12
+ # aggregation services, search engines, and monitoring tools.
13
+ #
14
+ # The device supports flexible field mapping to customize the JSON structure, datetime formatting,
15
+ # post-processing, and pretty printing for development use.
16
+ #
17
+ # @example Basic usage
18
+ # device = Lumberjack::JsonDevice.new(output: STDOUT)
19
+ # logger = Lumberjack::Logger.new(device)
20
+ # logger.info("User logged in", user_id: 123)
21
+ #
22
+ # @example Custom field mapping
23
+ # device = Lumberjack::JsonDevice.new(
24
+ # output: STDOUT,
25
+ # mapping: {
26
+ # time: "timestamp",
27
+ # severity: "level",
28
+ # message: true,
29
+ # attributes: "*"
30
+ # }
31
+ # )
32
+ #
33
+ # The mapping parameter can be used to define the JSON data structure. To define the structure pass in a
34
+ # hash with key indicating the log entry field and the value indicating the JSON document key.
35
+ #
36
+ # The standard entry fields are mapped with the following keys:
37
+ #
38
+ # * :time
39
+ # * :severity
40
+ # * :progname
41
+ # * :pid
42
+ # * :message
43
+ # * :attributes
44
+ #
45
+ # Any additional keys will be pulled from the attributes. If any of the standard keys are missing or have a nil
46
+ # mapping, the entry field will not be included in the JSON output.
47
+ #
48
+ # You can create a nested JSON structure by specifying an array as the JSON key.
49
+ class JsonDevice < Device
50
+ VERSION = File.read(File.join(__dir__, "..", "..", "VERSION")).strip.freeze
51
+
52
+ # Default mapping for standard log entry fields to JSON keys.
53
+ DEFAULT_MAPPING = {
54
+ time: true,
55
+ severity: true,
56
+ message: true,
57
+ progname: true,
58
+ pid: true,
59
+ attributes: true
60
+ }.freeze
61
+
62
+ # Default ISO 8601 datetime format with microsecond precision and timezone offset.
63
+ DEFAULT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%6N%z"
64
+
65
+ # Classes that can be serialized directly to JSON without transformation.
66
+ JSON_NATIVE_CLASSES = [String, NilClass, Numeric, TrueClass, FalseClass].freeze
67
+ private_constant :JSON_NATIVE_CLASSES
68
+
69
+ # Valid options that can be passed to the JsonDevice constructor.
70
+ JSON_OPTIONS = [:output, :mapping, :formatter, :datetime_format, :post_processor, :pretty, :utc].freeze
71
+ private_constant :JSON_OPTIONS
72
+
73
+ # Register the JsonDevice with the device registry for easier instantiation.
74
+ DeviceRegistry.add(:json, self)
75
+
76
+ # @!attribute [rw] formatter
77
+ # @return [Lumberjack::Formatter] The formatter used to format log entry values before JSON serialization.
78
+ attr_accessor :formatter
79
+
80
+ # @!attribute [rw] post_processor
81
+ # @return [Proc, nil] A callable object that can modify the log entry hash before JSON serialization.
82
+ attr_accessor :post_processor
83
+
84
+ # @!attribute [w] pretty
85
+ # @param value [Boolean] Whether to enable pretty-printed JSON output.
86
+ attr_writer :pretty
87
+
88
+ # @!attribute [r] mapping
89
+ # @return [Hash] The current field mapping configuration.
90
+ attr_reader :mapping
91
+
92
+ # Create a new JsonDevice instance.
93
+ #
94
+ # @param options [Hash<Symbol, Object>] The options for the JSON device.
95
+ # @param deprecated_options [Hash<Symbol, Object>] The device options for the JSON device if the output
96
+ # stream or device is specified in the first argument. This is deprecated behavior for backward
97
+ # compatibility with version 2.x.
98
+ # @option options [IO, Lumberjack::Device, Symbol, String, Pathname, nil] :output The output stream or
99
+ # Lumberjack device to write the JSON formatted log entries to. If this is a string or Pathname,
100
+ # then the output will be written to that file path. The values :stdout and :stderr can be used
101
+ # to write to STDOUT and STDERR respectively. Defaults to STDOUT.
102
+ # @option options [Hash] :mapping A hash where the key is the log entry field name and the value indicates how
103
+ # to map the field if it exists. If the value is `true`, the field will be mapped to the same name.
104
+ # If the value is a String, the field will be mapped to that key name.
105
+ # If the value is an Array, it will be mapped to a nested structure that follows the array elements.
106
+ # If the value is a callable object, it will be called with the value and is expected to return
107
+ # a hash that will be merged into the JSON document.
108
+ # If the value is `false` or `nil`, the field will not be included in the JSON output.
109
+ # Special value `"*"` for `:attributes` will flatten all remaining attributes to the root level.
110
+ # @option options [Lumberjack::Formatter] :formatter An optional formatter to use for formatting the log entry data.
111
+ # @option options [String] :datetime_format An optional datetime format string to use for formatting the log timestamp.
112
+ # Defaults to ISO 8601 format with microsecond precision.
113
+ # @option options [Proc] :post_processor An optional callable object that will be called with the log entry hash
114
+ # before it is written to the output stream. This can be used to modify the log entry data
115
+ # before it is serialized to JSON. The callable should return a Hash or the result will be ignored.
116
+ # @option options [Boolean] :pretty If true, the output will be formatted as pretty JSON with indentation and newlines.
117
+ # The default is false, which writes each log entry as a single line JSON document.
118
+ # @option options [Boolean] :utc If true, all times will be converted to UTC before formatting.
119
+ def initialize(options = {}, deprecated_options = nil)
120
+ unless options.is_a?(Hash)
121
+ Lumberjack::Utils.deprecated(:new, "Passing a stream or device as the first argument is no longer supported and will be removed in version 3.1; specify the output stream in the :output key of the options hash.") do
122
+ options = (deprecated_options || {}).merge(output: options)
123
+ end
124
+ end
125
+
126
+ @mutex = Mutex.new
127
+
128
+ stream_options = options.dup
129
+ JSON_OPTIONS.each { |key| stream_options.delete(key) }
130
+ @output = output_stream(options[:output], stream_options)
131
+
132
+ self.mapping = options.fetch(:mapping, DEFAULT_MAPPING)
133
+
134
+ @force_utc = options.fetch(:utc, false)
135
+ @formatter = default_formatter
136
+ self.datetime_format = options.fetch(:datetime_format, DEFAULT_TIME_FORMAT)
137
+ @formatter.include(options[:formatter]) if options[:formatter]
138
+
139
+ @post_processor = options[:post_processor]
140
+
141
+ @pretty = !!options[:pretty]
142
+ end
143
+
144
+ # Write a log entry to the output stream as JSON.
145
+ # Each entry is written as a single line JSON document (JSONL format) unless pretty printing is enabled.
146
+ # Empty log entries (nil or empty message) are ignored.
147
+ #
148
+ # @param entry [Lumberjack::LogEntry] The log entry to write.
149
+ # @return [void]
150
+ def write(entry)
151
+ return if entry.empty?
152
+
153
+ data = entry_as_json(entry)
154
+ json = @pretty ? JSON.pretty_generate(data) : JSON.generate(data)
155
+ @output.write("#{json}\n")
156
+ end
157
+
158
+ # Get the underlying device from the output stream.
159
+ #
160
+ # @return [Object] The underlying device.
161
+ def dev
162
+ @output.dev
163
+ end
164
+
165
+ # Flush the output stream.
166
+ #
167
+ # @return [void]
168
+ def flush
169
+ @output.flush
170
+ end
171
+
172
+ # @!attribute [r] datetime_format
173
+ # @return [String] The current datetime format string.
174
+ attr_reader :datetime_format
175
+
176
+ # Set the datetime format for the log timestamp.
177
+ #
178
+ # @param format [String] The datetime format string to use for formatting the log timestamp.
179
+ def datetime_format=(format)
180
+ @datetime_format = format
181
+ fmttr = time_formatter(datetime_format: format, force_utc: @force_utc)
182
+ @formatter.add(Time, fmttr)
183
+ @formatter.add(DateTime, fmttr)
184
+ end
185
+
186
+ # Return true if the output is written in a multi-line pretty format. The default is to write each
187
+ # log entry as a single line JSON document.
188
+ #
189
+ # @return [Boolean]
190
+ def pretty?
191
+ !!@pretty
192
+ end
193
+
194
+ # Set the mapping for how to map an entry to a JSON object.
195
+ #
196
+ # @param mapping [Hash] A hash where the key is the log entry field name and the value is the JSON key.
197
+ # If the value is `true`, the field will be mapped to the same name
198
+ # If the value is an array, it will be mapped to a nested structure.
199
+ # 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.
200
+ # If the value is `false`, the field will not be included in the JSON output.
201
+ # @return [void]
202
+ def mapping=(mapping)
203
+ @mutex.synchronize do
204
+ keys = {}
205
+ mapping.each do |key, value|
206
+ if value == true
207
+ value = key.to_s.split(".")
208
+ value = value.first if value.size == 1
209
+ end
210
+ keys[key.to_sym] = value if value
211
+ end
212
+
213
+ @time_key = keys.delete(:time)
214
+ @severity_key = keys.delete(:severity)
215
+ @message_key = keys.delete(:message)
216
+ @progname_key = keys.delete(:progname)
217
+ @pid_key = keys.delete(:pid)
218
+ @attributes_key = keys.delete(:attributes)
219
+ @custom_keys = keys.map do |name, key|
220
+ [name.to_s.split("."), key]
221
+ end.to_h
222
+
223
+ @mapping = mapping
224
+ end
225
+ end
226
+
227
+ # Add a field mapping to the existing mappings.
228
+ #
229
+ # @param field_mapping [Hash] A hash where the key is the log entry field name and the value is the JSON key.
230
+ # If the value is `true`, the field will be mapped to the same name
231
+ # If the value is an array, it will be mapped to a nested structure.
232
+ # 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.
233
+ # If the value is `false`, the field will not be included in the JSON output.
234
+ # @return [void]
235
+ def map(field_mapping)
236
+ new_mapping = field_mapping.transform_keys(&:to_sym)
237
+ self.mapping = mapping.merge(new_mapping)
238
+ end
239
+
240
+ # Convert a Lumberjack::LogEntry to a Hash using the specified field mapping.
241
+ #
242
+ # @param entry [Lumberjack::LogEntry] The log entry to convert.
243
+ # @return [Hash] A hash representing the log entry in JSON format.
244
+ def entry_as_json(entry)
245
+ data = {}
246
+ set_attribute(data, @time_key, entry.time) if @time_key
247
+ set_attribute(data, @severity_key, entry.severity_label) if @severity_key
248
+ set_attribute(data, @message_key, json_safe(entry.message)) if @message_key
249
+ set_attribute(data, @progname_key, json_safe(entry.progname)) if @progname_key && entry.progname
250
+ set_attribute(data, @pid_key, entry.pid) if @pid_key
251
+
252
+ attributes = entry.attributes.transform_values { |value| json_safe(value) } if entry.attributes
253
+
254
+ if @custom_keys.size > 0 && attributes && !attributes&.empty?
255
+ @custom_keys.each do |name, key|
256
+ name = name.is_a?(Array) ? name.join(".") : name.to_s
257
+ value = attributes.delete(name)
258
+ next if value.nil?
259
+
260
+ value = Lumberjack::Utils.expand_attributes(value) if value.is_a?(Hash)
261
+ set_attribute(data, key, value)
262
+ end
263
+ end
264
+
265
+ if @attributes_key && !attributes&.empty?
266
+ attributes = Lumberjack::Utils.expand_attributes(attributes)
267
+ if @attributes_key == "*"
268
+ attributes.each { |k, v| data[k] = v unless data.include?(k) }
269
+ else
270
+ set_attribute(data, @attributes_key, attributes)
271
+ end
272
+ end
273
+
274
+ data = @formatter.format(data) if @formatter
275
+ if @post_processor
276
+ processed_result = @post_processor.call(data)
277
+ data = processed_result if processed_result.is_a?(Hash)
278
+ end
279
+
280
+ data
281
+ end
282
+
283
+ private
284
+
285
+ def output_stream(output, options)
286
+ output ||= $stdout
287
+
288
+ if output.is_a?(Lumberjack::Device)
289
+ output
290
+ elsif output.is_a?(String) || (defined?(Pathname) && output.is_a?(Pathname))
291
+ options = options.slice(:binmode, :autoflush, :shift_age, :shift_size, :shift_period_suffix)
292
+ Lumberjack::Device::LogFile.new(output, options)
293
+ else
294
+ if output == :stdout
295
+ output = $stdout
296
+ elsif output == :stderr
297
+ output = $stderr
298
+ end
299
+ options = options.slice(:binmode, :autoflush)
300
+ Lumberjack::Device::Writer.new(output, options)
301
+ end
302
+ end
303
+
304
+ def default_formatter
305
+ Lumberjack::Formatter.build do |formatter|
306
+ formatter.add(::Enumerable, Lumberjack::Formatter::StructuredFormatter.new(formatter))
307
+ formatter.add(::Object) { |value| json_safe(value) }
308
+ end
309
+ end
310
+
311
+ def time_formatter(datetime_format: nil, force_utc: false)
312
+ lambda do |time|
313
+ time = time.utc if force_utc && !time.utc?
314
+ datetime_format ? time.strftime(datetime_format) : time
315
+ end
316
+ end
317
+
318
+ def set_attribute(data, key, value)
319
+ return if value.nil?
320
+
321
+ key = key.split(".") if key.is_a?(String) && key.include?(".")
322
+
323
+ if key.is_a?(Array)
324
+ unless key.empty?
325
+ if key.size == 1
326
+ data[key.first] = value
327
+ else
328
+ data[key.first] ||= {}
329
+ set_attribute(data[key.first], key[1, key.size], value)
330
+ end
331
+ end
332
+ elsif key.respond_to?(:call)
333
+ hash = key.call(value)
334
+ if hash.is_a?(Hash)
335
+ deep_merge!(data, hash)
336
+ end
337
+ else
338
+ data[key.to_s] = value unless key.nil?
339
+ end
340
+ end
341
+
342
+ def deep_merge!(hash, other_hash, &block)
343
+ other_hash = other_hash.transform_keys(&:to_s)
344
+ hash.merge!(other_hash) do |key, this_val, other_val|
345
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
346
+ deep_merge!(this_val, other_val, &block)
347
+ elsif block
348
+ block.call(key, this_val, other_val)
349
+ else
350
+ other_val
351
+ end
352
+ end
353
+ end
354
+
355
+ def json_safe(value, seen = nil)
356
+ return value if JSON_NATIVE_CLASSES.include?(value.class)
357
+ return nil if seen&.include?(value.object_id)
358
+
359
+ # Check if the as_json method is defined and takes no parameters
360
+ as_json_arity = value.method(:as_json).arity if !value.nil? && value.respond_to?(:as_json)
361
+
362
+ if as_json_arity == 0 || as_json_arity == -1
363
+ value.as_json
364
+ elsif !value.is_a?(Enumerable)
365
+ value
366
+ else
367
+ seen ||= Set.new
368
+ seen << value.object_id
369
+ if value.is_a?(Hash)
370
+ value.transform_values { |v| json_safe(v, seen) }
371
+ else
372
+ value.collect { |v| json_safe(v, seen) }
373
+ end
374
+ end
375
+ rescue SystemStackError, StandardError => e
376
+ error_message = e.class.name
377
+ error_message = "#{error_message} #{e.message}" if e.message && e.message != ""
378
+ warn("<Error serializing #{value.class} to JSON: #{error_message}>")
379
+ "<Error serializing #{value.class} to JSON: #{error_message}>"
380
+ end
381
+ end
382
+ end
@@ -1,283 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "lumberjack"
4
- require "json"
5
- require "time"
6
-
7
- module Lumberjack
8
- # This Lumberjack device logs output to another device as JSON formatted text with one document per line.
9
- #
10
- # The mapping parameter can be used to define the JSON data structure. To define the structure pass in a
11
- # hash with key indicating the log entry field and the value indicating the JSON document key.
12
- #
13
- # The standard entry fields are mapped with the following keys:
14
- #
15
- # * :time
16
- # * :severity
17
- # * :progname
18
- # * :pid
19
- # * :message
20
- # * :tags
21
- #
22
- # Any additional keys will be pulled from the tags. If any of the standard keys are missing or have a nil
23
- # mapping, the entry field will not be included in the JSON output.
24
- #
25
- # You can create a nested JSON structure by specifying an array as the JSON key.
26
- class JsonDevice < Device
27
- DEFAULT_MAPPING = {
28
- time: true,
29
- severity: true,
30
- progname: true,
31
- pid: true,
32
- message: true,
33
- tags: true
34
- }.freeze
35
-
36
- DEFAULT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%6N%z"
37
-
38
- attr_accessor :formatter
39
- attr_accessor :post_processor
40
- attr_writer :pretty
41
- attr_reader :mapping
42
-
43
- # @param stream_or_device [IO, Lumberjack::Device] The output stream or Lumberjack device to write
44
- # the JSON formatted log entries to.
45
- # @param mapping [Hash] A hash where the key is the log entry field name and the value indicates how
46
- # to map the field if it exists. If the value is `true`, the field will be mapped to the same name.
47
- # If the value is an array, it will be mapped to a nested structure that follows the array elements.
48
- # If the value is a callable object, it will be called with the value and is expected to return
49
- # a hash that will be merged into the JSON document.
50
- # If the value is `false`, the field will not be included in the JSON output.
51
- # @param formatter [Lumberjack::Formatter] An optional formatter to use for formatting the log entry data.
52
- # @param datetime_format [String] An optional datetime format string to use for formatting the log timestamp.
53
- # @param post_processor [Proc] An optional callable object that will be called with the log entry hash
54
- # before it is written to the output stream. This can be used to modify the log entry data
55
- # before it is serialized to JSON.
56
- # @param pretty [Boolean] If true, the output will be formatted as pretty JSON with indentation and newlines.
57
- # The default is false, which writes each log entry as a single line JSON document.
58
- def initialize(stream_or_device, mapping: DEFAULT_MAPPING, formatter: nil, datetime_format: nil, post_processor: nil, pretty: false)
59
- @mutex = Mutex.new
60
-
61
- @device = if stream_or_device.is_a?(Device)
62
- stream_or_device
63
- else
64
- Lumberjack::Device::Writer.new(stream_or_device)
65
- end
66
-
67
- self.mapping = mapping
68
-
69
- if formatter
70
- @formatter = formatter
71
- else
72
- @formatter = default_formatter
73
- datetime_format = DEFAULT_TIME_FORMAT if datetime_format.nil?
74
- end
75
- add_datetime_formatter!(datetime_format) unless datetime_format.nil?
76
-
77
- @post_processor = post_processor
78
-
79
- @pretty = !!pretty
80
- end
81
-
82
- def write(entry)
83
- return if entry.empty?
84
-
85
- data = entry_as_json(entry)
86
- json = @pretty ? JSON.pretty_generate(data) : JSON.generate(data)
87
- @device.write(json)
88
- end
89
-
90
- def flush
91
- @device.flush
92
- end
93
-
94
- attr_reader :datetime_format
95
-
96
- # Set the datetime format for the log timestamp.
97
- #
98
- # @param format [String] The datetime format string to use for formatting the log timestamp.
99
- def datetime_format=(format)
100
- add_datetime_formatter!(format)
101
- end
102
-
103
- # Return true if the output is written in a multi-line pretty format. The default is to write each
104
- # log entry as a single line JSON document.
105
- #
106
- # @return [Boolean]
107
- def pretty?
108
- !!@pretty
109
- end
110
-
111
- # Set the mapping for how to map an entry to a JSON object.
112
- #
113
- # @param mapping [Hash] A hash where the key is the log entry field name and the value is the JSON key.
114
- # If the value is `true`, the field will be mapped to the same name
115
- # If the value is an array, it will be mapped to a nested structure.
116
- # 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.
117
- # If the value is `false`, the field will not be included in the JSON output.
118
- # @return [void]
119
- def mapping=(mapping)
120
- @mutex.synchronize do
121
- keys = {}
122
- mapping.each do |key, value|
123
- if value == true
124
- value = key.to_s.split(".")
125
- value = value.first if value.size == 1
126
- end
127
- keys[key.to_sym] = value if value
128
- end
129
-
130
- @time_key = keys.delete(:time)
131
- @severity_key = keys.delete(:severity)
132
- @progname_key = keys.delete(:progname)
133
- @pid_key = keys.delete(:pid)
134
- @message_key = keys.delete(:message)
135
- @tags_key = keys.delete(:tags)
136
- @custom_keys = keys.map do |name, key|
137
- [name.to_s.split("."), key]
138
- end.to_h
139
- @mapping = mapping
140
- end
141
- end
142
-
143
- # Add a field mapping to the existing mappings.
144
- #
145
- # @param field_mapping [Hash] A hash where the key is the log entry field name and the value is the JSON key.
146
- # If the value is `true`, the field will be mapped to the same name
147
- # If the value is an array, it will be mapped to a nested structure.
148
- # 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.
149
- # If the value is `false`, the field will not be included in the JSON output.
150
- # @return [void]
151
- def map(field_mapping)
152
- new_mapping = field_mapping.transform_keys(&:to_sym)
153
- self.mapping = mapping.merge(new_mapping)
154
- end
155
-
156
- # Convert a Lumberjack::LogEntry to a Hash using the specified field mapping.
157
- #
158
- # @param entry [Lumberjack::LogEntry] The log entry to convert.
159
- # @return [Hash] A hash representing the log entry in JSON format.
160
- def entry_as_json(entry)
161
- data = {}
162
- set_attribute(data, @time_key, entry.time) if @time_key
163
- set_attribute(data, @severity_key, entry.severity_label) if @severity_key
164
- set_attribute(data, @message_key, json_safe(entry.message)) if @message_key
165
- set_attribute(data, @progname_key, json_safe(entry.progname)) if @progname_key && entry.progname
166
- set_attribute(data, @pid_key, entry.pid) if @pid_key
167
-
168
- tags = entry.tags.transform_values { |value| json_safe(value) } if entry.tags
169
-
170
- extracted_tags = nil
171
- if @custom_keys.size > 0 && !tags&.empty?
172
- extracted_tags = []
173
- @custom_keys.each do |name, key|
174
- name = name.is_a?(Array) ? name.join(".") : name.to_s
175
- value = tags.delete(name)
176
- next if value.nil?
177
-
178
- value = Lumberjack::Utils.expand_tags(value) if value.is_a?(Hash)
179
- set_attribute(data, key, value)
180
- extracted_tags << name
181
- end
182
- end
183
-
184
- if @tags_key && !tags&.empty?
185
- tags = Lumberjack::Utils.expand_tags(tags)
186
- if @tags_key == "*"
187
- tags.each { |k, v| data[k] = v unless data.include?(k) }
188
- else
189
- set_attribute(data, @tags_key, tags)
190
- end
191
- end
192
-
193
- data = @formatter.format(data) if @formatter
194
- if @post_processor
195
- processed_result = @post_processor.call(data)
196
- data = processed_result if processed_result.is_a?(Hash)
197
- end
198
-
199
- data
200
- end
201
-
202
- private
203
-
204
- def set_attribute(data, key, value)
205
- return if value.nil?
206
-
207
- if (value.is_a?(Time) || value.is_a?(DateTime)) && @time_formatter
208
- value = @time_formatter.call(value)
209
- end
210
-
211
- key = key.split(".") if key.is_a?(String) && key.include?(".")
212
-
213
- if key.is_a?(Array)
214
- unless key.empty?
215
- if key.size == 1
216
- data[key.first] = value
217
- else
218
- data[key.first] ||= {}
219
- set_attribute(data[key.first], key[1, key.size], value)
220
- end
221
- end
222
- elsif key.respond_to?(:call)
223
- hash = key.call(value)
224
- if hash.is_a?(Hash)
225
- deep_merge!(data, Lumberjack::Tags.stringify_keys(hash))
226
- end
227
- else
228
- data[key.to_s] = value unless key.nil?
229
- end
230
- end
231
-
232
- def default_formatter
233
- formatter = Formatter.new.clear
234
- object_formatter = Lumberjack::Formatter::ObjectFormatter.new
235
- formatter.add(String, object_formatter)
236
- formatter.add(Object, object_formatter)
237
- formatter.add(Enumerable, Formatter::StructuredFormatter.new(formatter))
238
- formatter
239
- end
240
-
241
- def add_datetime_formatter!(datetime_format)
242
- if datetime_format
243
- @datetime_format = datetime_format
244
- time_formatter = Lumberjack::Formatter::DateTimeFormatter.new(datetime_format)
245
- formatter.add(Time, time_formatter)
246
- formatter.add(Date, time_formatter)
247
- else
248
- @datetime_format = nil
249
- formatter.remove(Time)
250
- formatter.remove(Date)
251
- end
252
- end
253
-
254
- def deep_merge!(hash, other_hash, &block)
255
- hash.merge!(other_hash) do |key, this_val, other_val|
256
- if this_val.is_a?(Hash) && other_val.is_a?(Hash)
257
- deep_merge!(this_val, other_val, &block)
258
- elsif block
259
- block.call(key, this_val, other_val)
260
- else
261
- other_val
262
- end
263
- end
264
- end
265
-
266
- def json_safe(value)
267
- return nil if value.nil?
268
-
269
- # Check if the as_json method is defined takes no parameters
270
- as_json_arity = value.method(:as_json).arity if value.respond_to?(:as_json)
271
-
272
- if as_json_arity == 0 || as_json_arity == -1
273
- value.as_json
274
- elsif value.is_a?(Hash)
275
- value.transform_values { |v| json_safe(v) }
276
- elsif value.is_a?(Enumerable)
277
- value.collect { |v| json_safe(v) }
278
- else
279
- value
280
- end
281
- end
282
- end
283
- end
3
+ require_relative "lumberjack/json_device"
@@ -8,6 +8,12 @@ Gem::Specification.new do |spec|
8
8
  spec.homepage = "https://github.com/bdurand/lumberjack_json_device"
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", ">=1.4"
37
+ spec.add_dependency "lumberjack", ">=2.0"
32
38
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lumberjack_json_device
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 3.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-08-22 00:00:00.000000000 Z
10
+ date: 2025-10-20 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: lumberjack
@@ -16,15 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '1.4'
18
+ version: '2.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: '1.4'
27
- description:
25
+ version: '2.0'
28
26
  email:
29
27
  - bbdurand@gmail.com
30
28
  executables: []
@@ -38,13 +36,16 @@ files:
38
36
  - MIT_LICENSE.txt
39
37
  - README.md
40
38
  - VERSION
39
+ - lib/lumberjack/json_device.rb
41
40
  - lib/lumberjack_json_device.rb
42
41
  - lumberjack_json_device.gemspec
43
42
  homepage: https://github.com/bdurand/lumberjack_json_device
44
43
  licenses:
45
44
  - MIT
46
- metadata: {}
47
- post_install_message:
45
+ metadata:
46
+ homepage_uri: https://github.com/bdurand/lumberjack_json_device
47
+ source_code_uri: https://github.com/bdurand/lumberjack_json_device
48
+ changelog_uri: https://github.com/bdurand/lumberjack_json_device/blob/main/CHANGE_LOG.md
48
49
  rdoc_options: []
49
50
  require_paths:
50
51
  - lib
@@ -52,15 +53,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
52
53
  requirements:
53
54
  - - ">="
54
55
  - !ruby/object:Gem::Version
55
- version: '2.5'
56
+ version: '2.7'
56
57
  required_rubygems_version: !ruby/object:Gem::Requirement
57
58
  requirements:
58
59
  - - ">="
59
60
  - !ruby/object:Gem::Version
60
61
  version: '0'
61
62
  requirements: []
62
- rubygems_version: 3.4.10
63
- signing_key:
63
+ rubygems_version: 3.6.2
64
64
  specification_version: 4
65
65
  summary: A logging device for the lumberjack gem that writes log entries as JSON documents
66
66
  for use with structured logging.