logcraft 1.0.0.rc → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -1
- data/Gemfile +1 -1
- data/README.md +151 -65
- data/gemfiles/rails_7.0.gemfile +13 -0
- data/lib/logcraft/log_layout.rb +10 -7
- data/lib/logcraft/rails/extensions.rb +11 -1
- data/lib/logcraft/rails/request_logger.rb +4 -2
- data/lib/logcraft/railtie.rb +6 -2
- data/lib/logcraft/rspec/matchers.rb +18 -20
- data/lib/logcraft/version.rb +1 -1
- data/lib/logcraft.rb +2 -2
- data/logcraft.gemspec +3 -3
- metadata +8 -7
- data/Rakefile +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2256a97ecb30662f543512b9ace85cabe956a49ee4407f0f3575a56ff36055bd
|
4
|
+
data.tar.gz: 89a522ff6b287c903110b9df445f201a65955a52edf371bd3df125691dfa3482
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2cefe4bd29be7ccabaedd3503e3d135fa05f95128d939df500d516644d1cb7fab18aaf8a410a1077bf549b345dfa2cf773dee01baba5d941d47e9a95828c14fd
|
7
|
+
data.tar.gz: 915fcd06110d9e200652d5f9bbe62c3d9a3ee49151e7147df68855d4d58639a3ac7788df74b2ca3c037984c4a5026b2e923494849408731c10584fdae0b0054c
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,22 @@ 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] - 2022-07-31
|
8
|
+
### Added
|
9
|
+
- Added the option to change the log level or suppress logging of unhandled errors which are, in fact,
|
10
|
+
handled by Rails (e.g. 404 Not Found).
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
- The initial context is now fully dynamic; it can be either a Hash or a lambda/Proc returning a Hash.
|
14
|
+
Using a Hash with lambda values is no longer supported.
|
15
|
+
- Renamed `initial_context` configuration setting to `global_context` everywhere.
|
16
|
+
|
17
|
+
### Fixed
|
18
|
+
- Fixed a bug where the request ID was missing from the access log.
|
19
|
+
|
20
|
+
### Added
|
21
|
+
- The provided RSpec matchers can now take other matchers as part of the log expectation.
|
22
|
+
|
7
23
|
## [1.0.0.rc] - 2022-06-26
|
8
24
|
### Added
|
9
|
-
- Logcraft was rewritten from the ground up, based on its predecessor: [Ezlog](https://github.com/emartech/ezlog)
|
25
|
+
- Logcraft was rewritten from the ground up, based on its predecessor: [Ezlog](https://github.com/emartech/ezlog).
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,36 +1,66 @@
|
|
1
1
|
# Logcraft
|
2
2
|
|
3
3
|
[![Build Status](https://github.com/zormandi/logcraft/actions/workflows/main.yml/badge.svg)](https://github.com/zormandi/logcraft/actions/workflows/main.yml)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/logcraft.svg)](https://badge.fury.io/rb/logcraft)
|
4
5
|
|
5
|
-
Logcraft is a zero-configuration structured logging library for pure Ruby or [Ruby on Rails](https://rubyonrails.org/)
|
6
|
-
It is the successor to [Ezlog](https://github.com/emartech/ezlog) with which it shares its ideals but is
|
7
|
-
reimplemented to be more versatile and much more thoroughly tested.
|
6
|
+
Logcraft is a zero-configuration structured logging library for pure Ruby or [Ruby on Rails](https://rubyonrails.org/)
|
7
|
+
applications. It is the successor to [Ezlog](https://github.com/emartech/ezlog) with which it shares its ideals but is
|
8
|
+
reimagined and reimplemented to be more versatile and much more thoroughly tested.
|
8
9
|
|
9
10
|
Logcraft's purpose is threefold:
|
11
|
+
|
10
12
|
1. Make sure that our applications are logging in a concise and sensible manner; emitting no unnecessary "noise" but
|
11
13
|
containing all relevant and necessary information (like timing or a request ID).
|
12
14
|
2. Make sure that all log messages are written to STDOUT in a machine-processable format (JSON).
|
13
15
|
3. Achieving the above goals should require no configuration in the projects where the library is used.
|
14
16
|
|
15
17
|
Logcraft supports:
|
16
|
-
* [Ruby](https://www.ruby-lang.org) 2.6 and up (tested with 2.6, 2.7, 3.0 and 3.1)
|
17
|
-
* [Rails](https://rubyonrails.org/) 5 and up (tested with 5.2, 6.0 and 6.1)
|
18
|
-
* [Sidekiq](https://github.com/mperham/sidekiq) support is coming soon via a separate gem (_logcraft-sidekiq_)
|
19
18
|
|
20
|
-
|
19
|
+
* [Ruby](https://www.ruby-lang.org) 2.6 and up (tested with 2.6, 2.7, 3.0 and 3.1)
|
20
|
+
* [Rails](https://rubyonrails.org/) 5 and up (tested with 5.2, 6.0, 6.1 and 7.0)
|
21
|
+
* [Sidekiq](https://github.com/mperham/sidekiq) integration is provided via
|
22
|
+
the [Logcraft::Sidekiq](https://github.com/zormandi/logcraft-sidekiq) gem
|
23
|
+
|
24
|
+
Logcraft uses Tim Pease's wonderful [Logging](https://github.com/TwP/logging) gem under the hood for an all-purpose
|
25
|
+
structured logging solution.
|
26
|
+
|
27
|
+
## Table of contents
|
28
|
+
|
29
|
+
* [Installation](#installation)
|
30
|
+
* [Rails](#rails)
|
31
|
+
* [Non-Rails applications](#non-rails-applications)
|
32
|
+
* [Usage](#usage)
|
33
|
+
* [Structured logging](#structured-logging)
|
34
|
+
* [Adding context information to log messages](#adding-context-information-to-log-messages)
|
35
|
+
* [Rails logging](#rails-logging)
|
36
|
+
* [The log level](#the-log-level)
|
37
|
+
* [JSON serialization](#json-serialization)
|
38
|
+
* [Configuration options](#configuration-options)
|
39
|
+
* [Rails configuration](#rails-configuration)
|
40
|
+
* [Non-Rails configuration](#non-rails-configuration)
|
41
|
+
* [Integration with DataDog](#integration-with-datadog)
|
42
|
+
* [RSpec support](#rspec-support)
|
43
|
+
* [Development](#development)
|
44
|
+
* [Contributing](#contributing)
|
45
|
+
* [Disclaimer](#disclaimer)
|
46
|
+
* [License](#license)
|
47
|
+
* [Code of Conduct](#code-of-conduct)
|
21
48
|
|
22
49
|
## Installation
|
23
50
|
|
24
51
|
### Rails
|
25
52
|
|
26
53
|
Add this line to your application's Gemfile:
|
54
|
+
|
27
55
|
```ruby
|
28
56
|
gem 'logcraft'
|
57
|
+
gem 'oj' # Optional, but recommended; see the "JSON serialization" section in the README
|
29
58
|
```
|
30
59
|
|
31
|
-
Although Logcraft sets up sensible defaults for all logging configuration settings, it leaves you the option to override
|
32
|
-
settings manually in the way you're used to; via Rails's configuration mechanism. Unfortunately the Rails new
|
33
|
-
automatically generates code for the production environment configuration that overrides some of these
|
60
|
+
Although Logcraft sets up sensible defaults for all logging configuration settings, it leaves you the option to override
|
61
|
+
these settings manually in the way you're used to; via Rails's configuration mechanism. Unfortunately the Rails new
|
62
|
+
project generator automatically generates code for the production environment configuration that overrides some of these
|
63
|
+
default settings.
|
34
64
|
|
35
65
|
For Logcraft to work properly, you need to delete or comment out the logging configuration options in the generated
|
36
66
|
`config/environments/production.rb` file.
|
@@ -38,13 +68,17 @@ For Logcraft to work properly, you need to delete or comment out the logging con
|
|
38
68
|
### Non-Rails applications
|
39
69
|
|
40
70
|
Add this line to your application's Gemfile:
|
71
|
+
|
41
72
|
```ruby
|
42
73
|
gem 'logcraft'
|
43
74
|
```
|
75
|
+
|
44
76
|
and call
|
77
|
+
|
45
78
|
```ruby
|
46
79
|
Logcraft.initialize
|
47
80
|
```
|
81
|
+
|
48
82
|
any time during your application's startup.
|
49
83
|
|
50
84
|
## Usage
|
@@ -60,6 +94,7 @@ messages in JSON format to the standard output. These loggers can handle a varie
|
|
60
94
|
* any other object that can be coerced into a String
|
61
95
|
|
62
96
|
The logger also automatically adds some basic information to all messages, such as:
|
97
|
+
|
63
98
|
* name of the logger
|
64
99
|
* timestamp
|
65
100
|
* log level (as string)
|
@@ -67,20 +102,21 @@ The logger also automatically adds some basic information to all messages, such
|
|
67
102
|
* PID
|
68
103
|
|
69
104
|
Examples:
|
105
|
+
|
70
106
|
```ruby
|
71
107
|
logger = Logcraft.logger 'Application'
|
72
108
|
|
73
109
|
logger.info 'Log message'
|
74
|
-
# => {"timestamp":"2022-06-26T17:52:57.845+02:00","level":"INFO","logger":"Application","hostname":"
|
110
|
+
# => {"timestamp":"2022-06-26T17:52:57.845+02:00","level":"INFO","logger":"Application","hostname":"MacbookPro.local","pid":80422,"message":"Log message"}
|
75
111
|
|
76
112
|
logger.info message: 'User logged in', user_id: 42
|
77
113
|
# => {"timestamp":"2022-06-26T17:44:01.926+02:00","level":"INFO","logger":"Application","hostname":"MacbookPro.local","pid":80422,"message":"User logged in","user_id":42}
|
78
114
|
|
79
|
-
logger.
|
115
|
+
logger.error error
|
80
116
|
# Formatted for better readability (the original is a single line string):
|
81
117
|
# => {
|
82
118
|
# "timestamp": "2022-06-26T17:46:42.418+02:00",
|
83
|
-
# "level": "
|
119
|
+
# "level": "ERROR",
|
84
120
|
# "logger": "Application",
|
85
121
|
# "hostname": "MacbookPro.local",
|
86
122
|
# "pid": 80422,
|
@@ -96,6 +132,22 @@ logger.warn error
|
|
96
132
|
# }
|
97
133
|
# }
|
98
134
|
# }
|
135
|
+
|
136
|
+
# Any top-level field of the log message can be an Exception (so the error message can be customized):
|
137
|
+
logger.warn message: 'An error occured', warning: error
|
138
|
+
# => {
|
139
|
+
# "timestamp": "2022-06-26T17:46:42.418+02:00",
|
140
|
+
# "level": "WARN",
|
141
|
+
# "logger": "Application",
|
142
|
+
# "hostname": "MacbookPro.local",
|
143
|
+
# "pid": 80422,
|
144
|
+
# "message": "An error occured",
|
145
|
+
# "warning": {
|
146
|
+
# "class": "StandardError",
|
147
|
+
# "message": "original error message",
|
148
|
+
# "backtrace": [...]
|
149
|
+
# }
|
150
|
+
# }
|
99
151
|
```
|
100
152
|
|
101
153
|
#### Adding context information to log messages
|
@@ -131,17 +183,20 @@ You can access these methods either in the global scope by calling them via `Log
|
|
131
183
|
|
132
184
|
### Rails logging
|
133
185
|
|
134
|
-
Logcraft automatically configures Rails to provide you with structured logging capability via the `Rails.logger`.
|
186
|
+
Logcraft automatically configures Rails to provide you with structured logging capability via the `Rails.logger`.
|
135
187
|
It also changes Rails's default logging configuration to be more concise and emit less "noise".
|
136
188
|
|
137
189
|
In more detail:
|
190
|
+
|
138
191
|
* The `Rails.logger` is set up to be a Logcraft logger with the name `Application`.
|
139
|
-
* Rails's default logging of uncaught errors is modified and instead of spreading the error message across several
|
140
|
-
Logcraft log every uncaught error in 1 line (per error), including the error's name and context (stack trace,
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
192
|
+
* Rails's default logging of uncaught errors is modified and instead of spreading the error message across several
|
193
|
+
lines, Logcraft log every uncaught error in 1 line (per error), including the error's name and context (stack trace,
|
194
|
+
etc.).
|
195
|
+
* Most importantly, Rails's default request logging - which logs several lines per event during the processing of an
|
196
|
+
action - is replaced by Logcraft's own access log middleware. The end result is an access log that
|
197
|
+
* contains all relevant information (request ID, method, path, params, client IP, duration and
|
198
|
+
response status code), and
|
199
|
+
* has 1 log line per request, logged at the end of the request.
|
145
200
|
|
146
201
|
Thanks to Mathias Meyer for writing [Lograge](https://github.com/roidrage/lograge), which inspired the solution.
|
147
202
|
If Logcraft is not your cup of tea but you're looking for a way to tame Rails's logging then be sure to check out
|
@@ -168,7 +223,8 @@ Formatted for readability:
|
|
168
223
|
"level": "INFO",
|
169
224
|
"logger": "AccessLog",
|
170
225
|
"hostname": "MacbookPro.local",
|
171
|
-
"pid": 80908,
|
226
|
+
"pid": 80908,
|
227
|
+
"request_id": "9a43631b-284c-4677-9d08-9c1cc5c7d3a7",
|
172
228
|
"message": "GET /welcome?subsession_id=34ea8596f9764f475f81158667bc2654 - 200 (OK)",
|
173
229
|
"remote_ip": "127.0.0.1",
|
174
230
|
"method": "GET",
|
@@ -187,51 +243,74 @@ Formatted for readability:
|
|
187
243
|
By default, Logcraft logs all request parameters as a hash (JSON object) under the `params` key. This is very convenient
|
188
244
|
in a structured logging system and makes it easy to search for specific request parameter values e.g. in ElasticSearch
|
189
245
|
(should you happen to store your logs there). Unfortunately, in some cases - such as when handling large forms - this
|
190
|
-
can create quite a bit of noise and impact the searchability of your logs negatively or pose a security risk or data
|
191
|
-
violation. You have the option to restrict the logging of certain parameters via configuration options (see the
|
246
|
+
can create quite a bit of noise and impact the searchability of your logs negatively or pose a security risk or data
|
247
|
+
policy violation. You have the option to restrict the logging of certain parameters via configuration options (see the
|
192
248
|
Configuration section).
|
193
249
|
|
194
250
|
#### The log level
|
195
251
|
|
196
252
|
The logger's log level is determined as follows (in order of precedence):
|
253
|
+
|
197
254
|
* the log level set in the application's configuration (for Rails applications),
|
198
|
-
* the LOG_LEVEL environment variable, or
|
255
|
+
* the `LOG_LEVEL` environment variable, or
|
199
256
|
* `INFO` as the default log level if none of the above are set.
|
200
257
|
|
201
258
|
The following log levels are available: `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`.
|
202
259
|
|
260
|
+
#### JSON serialization
|
261
|
+
|
262
|
+
Logcraft uses the [MultiJSON](https://github.com/intridea/multi_json) gem for serializing data to JSON, which in turn
|
263
|
+
uses ActiveSupport's JSON encoder by default (unless you have some other JSON gem loaded in your project).
|
264
|
+
However, ActiveSupport's JSON encoder has some quirks which you might not be aware of:
|
265
|
+
|
266
|
+
```ruby
|
267
|
+
ActiveSupport::JSON.encode test: 'foo > bar'
|
268
|
+
#=> "{\"test\":\"foo \\u003e bar\"}"
|
269
|
+
|
270
|
+
{test: 'foo > bar'}.to_json
|
271
|
+
#=> "{\"test\":\"foo \\u003e bar\"}"
|
272
|
+
```
|
273
|
+
|
274
|
+
I highly recommend using the [Oj](https://github.com/ohler55/oj) gem which - if present - will be automatically
|
275
|
+
picked up by Logcraft, as it is significantly faster and will serialize your messages as you would expect.
|
276
|
+
|
203
277
|
## Configuration options
|
204
278
|
|
205
|
-
### Rails
|
279
|
+
### Rails configuration
|
206
280
|
|
207
281
|
Logcraft provides the following configuration options for Rails:
|
208
282
|
|
209
|
-
| Option
|
210
|
-
|
211
|
-
| logcraft.
|
212
|
-
| logcraft.layout_options
|
213
|
-
| logcraft.access_log.logger_name
|
214
|
-
| logcraft.access_log.exclude_paths
|
215
|
-
| logcraft.access_log.log_only_whitelisted_params
|
216
|
-
| logcraft.access_log.whitelisted_params
|
283
|
+
| Option | Default value | Description |
|
284
|
+
|-------------------------------------------------------|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
285
|
+
| logcraft.global_context | `{}` | A global log context that will be included in every log message. Must be either a Hash or a lambda/Proc returning a Hash. |
|
286
|
+
| logcraft.layout_options | `{}` | Custom options for the log layout. Currently only the `level_formatter` option is supported (see examples). |
|
287
|
+
| logcraft.access_log.logger_name | `'AccessLog'` | The name of the logger emitting access log messages. |
|
288
|
+
| logcraft.access_log.exclude_paths | `[]` | A list of paths (array of strings or RegExps) not to include in the access log. |
|
289
|
+
| logcraft.access_log.log_only_whitelisted_params | `false` | If `true`, the access log will only contain whitelisted parameters. |
|
290
|
+
| logcraft.access_log.whitelisted_params | `[:controller, :action]` | The only parameters to be logged in the access log if whitelisting is enabled. |
|
291
|
+
| logcraft.unhandled_errors.log_level | `:fatal` | The log level with which to log unhandled errors. Rails logs these with FATAL, by default. |
|
292
|
+
| logcraft.unhandled_errors.log_errors_handled_by_rails | `true` | Whether or not to log unhandled errors which are actually handled by Rails (e.g. 404). For a detailed list, see `ActionDispatch::ExceptionWrapper.rescue_responses`. |
|
217
293
|
|
218
294
|
Examples:
|
295
|
+
|
219
296
|
```ruby
|
220
297
|
# Use these options in your Rails configuration files (e.g. application.rb)
|
221
298
|
|
222
299
|
# Set up a global context you want to see in every log message
|
223
|
-
config.logcraft.
|
224
|
-
|
225
|
-
|
226
|
-
|
300
|
+
config.logcraft.global_context = -> do
|
301
|
+
{
|
302
|
+
environment: ENV['RAILS_ENV'],
|
303
|
+
timestamp_linux: Time.current.to_i # evaluated every time when emitting a log message
|
304
|
+
}
|
305
|
+
end
|
227
306
|
|
228
307
|
# Set up a custom log level formatter (e.g. Ougai-like numbers)
|
229
308
|
config.logcraft.layout_options = {
|
230
|
-
level_formatter: ->(level_number) { (level_number + 2) * 10 }
|
309
|
+
level_formatter: ->(level_number) { (level_number + 2) * 10 }
|
231
310
|
}
|
232
311
|
Rails.logger.error('Boom!')
|
233
312
|
# => {...,"level":50,"message":"Boom!"}
|
234
|
-
|
313
|
+
|
235
314
|
# Exclude healthcheck and monitoring URLs from your access log:
|
236
315
|
config.logcraft.exclude_paths = ['/healthcheck', %r(/monitoring/.*)]
|
237
316
|
|
@@ -239,35 +318,36 @@ config.logcraft.exclude_paths = ['/healthcheck', %r(/monitoring/.*)]
|
|
239
318
|
config.logcraft.log_only_whitelisted_params = true
|
240
319
|
```
|
241
320
|
|
242
|
-
### Non-Rails
|
321
|
+
### Non-Rails configuration
|
243
322
|
|
244
|
-
The `
|
323
|
+
The `global_context` and `layout_options` configuration options (see above) are available to non-Rails projects
|
245
324
|
via Logcraft's initialization mechanism. You can also set the default log level this way.
|
246
325
|
|
247
326
|
```ruby
|
248
|
-
Logcraft.initialize log_level: :info,
|
327
|
+
Logcraft.initialize log_level: :info, global_context: {}, layout_options: {}
|
249
328
|
```
|
250
329
|
|
251
330
|
## Integration with DataDog
|
252
331
|
|
253
|
-
You can set up tracing with [DataDog](https://www.datadoghq.com/) by providing
|
332
|
+
You can set up tracing with [DataDog](https://www.datadoghq.com/) by providing a global context to be included in
|
333
|
+
every log message:
|
254
334
|
|
255
335
|
```ruby
|
256
|
-
config.logcraft.
|
257
|
-
|
258
|
-
return unless Datadog::Tracing.enabled?
|
336
|
+
config.logcraft.global_context = -> do
|
337
|
+
return {} unless Datadog::Tracing.enabled?
|
259
338
|
|
260
|
-
|
261
|
-
|
339
|
+
correlation = Datadog::Tracing.correlation
|
340
|
+
{
|
341
|
+
dd: {
|
262
342
|
trace_id: correlation.trace_id.to_s,
|
263
343
|
span_id: correlation.span_id.to_s,
|
264
344
|
env: correlation.env.to_s,
|
265
345
|
service: correlation.service.to_s,
|
266
346
|
version: correlation.version.to_s
|
267
|
-
}
|
268
|
-
|
269
|
-
|
270
|
-
|
347
|
+
},
|
348
|
+
ddsource: ['ruby']
|
349
|
+
}
|
350
|
+
end
|
271
351
|
```
|
272
352
|
|
273
353
|
## RSpec support
|
@@ -280,12 +360,13 @@ require 'logcraft/rspec'
|
|
280
360
|
```
|
281
361
|
|
282
362
|
What you get:
|
363
|
+
|
283
364
|
* Helpers
|
284
|
-
|
285
|
-
|
365
|
+
* `log_output` provides access to the complete log output (array of strings) in your specs
|
366
|
+
* `log_output_is_expected` shorthand for writing expectations for the log output
|
286
367
|
* Matchers
|
287
|
-
|
288
|
-
|
368
|
+
* `include_log_message` matcher for expecting a certain message in the log output
|
369
|
+
* `log` matcher for expecting an operation to log a certain message
|
289
370
|
|
290
371
|
```ruby
|
291
372
|
# Check that the log output contains a certain message
|
@@ -293,8 +374,13 @@ expect(log_output).to include_log_message message: 'Test message'
|
|
293
374
|
log_output_is_expected.to include_log_message message: 'Test message'
|
294
375
|
|
295
376
|
# Check that the message is not present in the logs before the operation but is present after it
|
296
|
-
expect { operation }.to log message: 'Test message',
|
297
|
-
user_id: 123456
|
377
|
+
expect { operation }.to log message: 'Test message',
|
378
|
+
user_id: 123456
|
379
|
+
|
380
|
+
# RSpec's matchers can be used inside the expectation
|
381
|
+
expect { get '/' }.to log message: 'GET / - 200 (OK)',
|
382
|
+
request_id: match(/[\w-]+/),
|
383
|
+
duration: be_within(100).of(100)
|
298
384
|
|
299
385
|
# Expect a certain log level
|
300
386
|
log_output_is_expected.to include_log_message(message: 'Test message').at_level(:info)
|
@@ -303,22 +389,22 @@ expect { operation }.to log(message: 'Test message').at_level(:info)
|
|
303
389
|
|
304
390
|
## Development
|
305
391
|
|
306
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
392
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
307
393
|
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
308
394
|
|
309
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version,
|
310
|
-
update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag
|
395
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version,
|
396
|
+
update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag
|
311
397
|
for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
312
398
|
|
313
399
|
## Contributing
|
314
400
|
|
315
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/zormandi/logcraft. This project is intended
|
316
|
-
to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the
|
401
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/zormandi/logcraft. This project is intended
|
402
|
+
to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the
|
317
403
|
[code of conduct](https://github.com/zormandi/logcraft/blob/master/CODE_OF_CONDUCT.md).
|
318
404
|
|
319
405
|
## Disclaimer
|
320
406
|
|
321
|
-
Logcraft is highly opinionated software and does in no way aim or claim to be useful for everyone.
|
407
|
+
Logcraft is highly opinionated software and does in no way aim or claim to be useful for everyone.
|
322
408
|
Use at your own discretion.
|
323
409
|
|
324
410
|
## License
|
@@ -327,5 +413,5 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
327
413
|
|
328
414
|
## Code of Conduct
|
329
415
|
|
330
|
-
Everyone interacting in the Logcraft project's codebases, issue trackers, chat rooms and mailing lists is expected
|
416
|
+
Everyone interacting in the Logcraft project's codebases, issue trackers, chat rooms and mailing lists is expected
|
331
417
|
to follow the [code of conduct](https://github.com/zormandi/logcraft/blob/master/CODE_OF_CONDUCT.md).
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in logcraft.gemspec
|
6
|
+
gemspec path: '..'
|
7
|
+
|
8
|
+
group :test do
|
9
|
+
gem 'rails', '~> 7.0.0'
|
10
|
+
gem 'rspec-rails', '~> 4.0'
|
11
|
+
gem 'sqlite3', '~> 1.4'
|
12
|
+
gem 'net-smtp', require: false
|
13
|
+
end
|
data/lib/logcraft/log_layout.rb
CHANGED
@@ -4,13 +4,13 @@ require 'time'
|
|
4
4
|
|
5
5
|
module Logcraft
|
6
6
|
class LogLayout < Logging::Layout
|
7
|
-
def initialize(
|
8
|
-
@
|
7
|
+
def initialize(global_context = {}, options = {})
|
8
|
+
@global_context = global_context
|
9
9
|
@level_formatter = options.fetch :level_formatter, ->(level) { Logging::LNAMES[level] }
|
10
10
|
end
|
11
11
|
|
12
12
|
def format(event)
|
13
|
-
log_entry = background_of(event).merge
|
13
|
+
log_entry = background_of(event).merge evaluated_global_context,
|
14
14
|
dynamic_log_context,
|
15
15
|
message_from(event.data)
|
16
16
|
MultiJson.dump(log_entry) + "\n"
|
@@ -28,8 +28,12 @@ module Logcraft
|
|
28
28
|
}
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
32
|
-
@
|
31
|
+
def evaluated_global_context
|
32
|
+
if @global_context.respond_to? :call
|
33
|
+
@global_context.call
|
34
|
+
else
|
35
|
+
@global_context
|
36
|
+
end
|
33
37
|
end
|
34
38
|
|
35
39
|
def dynamic_log_context
|
@@ -52,8 +56,7 @@ module Logcraft
|
|
52
56
|
end
|
53
57
|
|
54
58
|
def format_exception(exception)
|
55
|
-
error_hash = {'class' => exception.class.name,
|
56
|
-
'message' => exception.message}
|
59
|
+
error_hash = {'class' => exception.class.name, 'message' => exception.message}
|
57
60
|
error_hash['backtrace'] = exception.backtrace.first(20) if exception.backtrace
|
58
61
|
error_hash['cause'] = format_cause(exception.cause) if exception.cause
|
59
62
|
error_hash
|
@@ -1,12 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionDispatch
|
2
4
|
class DebugExceptions
|
5
|
+
|
6
|
+
private
|
7
|
+
|
3
8
|
def log_error_with_logcraft(request, wrapper)
|
4
9
|
logger = logger(request)
|
5
10
|
exception = wrapper.exception
|
6
|
-
|
11
|
+
config = Rails.configuration.logcraft.unhandled_errors
|
12
|
+
logger.public_send config.log_level, exception if config.log_errors_handled_by_rails || !handled_by_rails?(exception)
|
7
13
|
end
|
8
14
|
|
9
15
|
alias_method :original_log_error, :log_error
|
10
16
|
alias_method :log_error, :log_error_with_logcraft
|
17
|
+
|
18
|
+
def handled_by_rails?(exception)
|
19
|
+
ActionDispatch::ExceptionWrapper.rescue_responses.key? exception.class.name
|
20
|
+
end
|
11
21
|
end
|
12
22
|
end
|
@@ -16,9 +16,10 @@ module Logcraft
|
|
16
16
|
instrumentation_start request
|
17
17
|
|
18
18
|
status, headers, body = @app.call env
|
19
|
+
request_id = Logging.mdc[:request_id]
|
19
20
|
body = ::Rack::BodyProxy.new(body) do
|
20
21
|
instrumentation_finish request
|
21
|
-
log_request request, status, start_time
|
22
|
+
log_request request, status, start_time, request_id
|
22
23
|
end
|
23
24
|
|
24
25
|
[status, headers, body]
|
@@ -46,7 +47,7 @@ module Logcraft
|
|
46
47
|
instrumenter.finish 'request.action_dispatch', request: request
|
47
48
|
end
|
48
49
|
|
49
|
-
def log_request(request, status, start_time)
|
50
|
+
def log_request(request, status, start_time, request_id = nil)
|
50
51
|
return if path_ignored? request
|
51
52
|
|
52
53
|
end_time = current_time_in_milliseconds
|
@@ -60,6 +61,7 @@ module Logcraft
|
|
60
61
|
duration: end_time - start_time,
|
61
62
|
duration_sec: (end_time - start_time) / 1000.0
|
62
63
|
}
|
64
|
+
message[:request_id] = request_id if request_id
|
63
65
|
@logger.info message
|
64
66
|
end
|
65
67
|
|
data/lib/logcraft/railtie.rb
CHANGED
@@ -5,7 +5,7 @@ require 'rails/railtie'
|
|
5
5
|
module Logcraft
|
6
6
|
class Railtie < ::Rails::Railtie
|
7
7
|
config.logcraft = ActiveSupport::OrderedOptions.new
|
8
|
-
config.logcraft.
|
8
|
+
config.logcraft.global_context = {}
|
9
9
|
config.logcraft.layout_options = {}
|
10
10
|
|
11
11
|
config.logcraft.access_log = ActiveSupport::OrderedOptions.new
|
@@ -14,9 +14,13 @@ module Logcraft
|
|
14
14
|
config.logcraft.access_log.log_only_whitelisted_params = false
|
15
15
|
config.logcraft.access_log.whitelisted_params = [:controller, :action]
|
16
16
|
|
17
|
+
config.logcraft.unhandled_errors = ActiveSupport::OrderedOptions.new
|
18
|
+
config.logcraft.unhandled_errors.log_level = :fatal
|
19
|
+
config.logcraft.unhandled_errors.log_errors_handled_by_rails = true
|
20
|
+
|
17
21
|
initializer 'logcraft.initialize' do |app|
|
18
22
|
Logcraft.initialize log_level: app.config.log_level,
|
19
|
-
|
23
|
+
global_context: app.config.logcraft.global_context,
|
20
24
|
layout_options: app.config.logcraft.layout_options
|
21
25
|
end
|
22
26
|
|
@@ -2,21 +2,30 @@
|
|
2
2
|
|
3
3
|
require 'json'
|
4
4
|
|
5
|
-
RSpec::Matchers.define :include_log_message do |
|
5
|
+
RSpec::Matchers.define :include_log_message do |expected_message|
|
6
6
|
chain :at_level, :log_level
|
7
7
|
|
8
8
|
match do |actual|
|
9
|
-
actual.any? { |log_line| includes? log_line,
|
9
|
+
actual.any? { |log_line| includes? log_line, expected_message }
|
10
10
|
end
|
11
11
|
|
12
|
-
def includes?(log_line,
|
13
|
-
|
14
|
-
|
12
|
+
def includes?(log_line, expected)
|
13
|
+
actual = JSON.parse log_line, symbolize_names: true
|
14
|
+
expected = normalize_expectation expected
|
15
|
+
RSpec::Matchers::BuiltIn::Include.new(expected).matches? actual
|
15
16
|
end
|
16
17
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
18
|
+
def normalize_expectation(expected)
|
19
|
+
result = case expected
|
20
|
+
when String
|
21
|
+
{message: expected}
|
22
|
+
when Hash
|
23
|
+
expected
|
24
|
+
else
|
25
|
+
raise ArgumentError, 'Log expectation must be either a String or a Hash'
|
26
|
+
end
|
27
|
+
result[:level] = log_level_string(log_level) unless log_level.nil?
|
28
|
+
result
|
20
29
|
end
|
21
30
|
|
22
31
|
def log_level_string(log_level)
|
@@ -24,19 +33,8 @@ RSpec::Matchers.define :include_log_message do |expected|
|
|
24
33
|
log_level.to_s.upcase
|
25
34
|
end
|
26
35
|
|
27
|
-
def expected_messages_from(object)
|
28
|
-
@expected_messages ||= case object
|
29
|
-
when Hash
|
30
|
-
object.map { |k, v| JSON.dump(k => v)[1...-1] }
|
31
|
-
when String
|
32
|
-
[object]
|
33
|
-
else
|
34
|
-
raise NotImplementedError, 'log expectation must be Hash or String'
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
36
|
failure_message do |actual|
|
39
|
-
error_message = "expected log output\n\t'#{actual.join('')}'\nto include log message\n\t'#{
|
37
|
+
error_message = "expected log output\n\t'#{actual.join('')}'\nto include log message\n\t'#{expected_message}'"
|
40
38
|
error_message += " at #{log_level} level" if log_level
|
41
39
|
error_message
|
42
40
|
end
|
data/lib/logcraft/version.rb
CHANGED
data/lib/logcraft.rb
CHANGED
@@ -12,8 +12,8 @@ module Logcraft
|
|
12
12
|
|
13
13
|
extend LogContextHelper
|
14
14
|
|
15
|
-
def self.initialize(log_level: :info,
|
16
|
-
Logging.logger.root.appenders = Logging.appenders.stdout layout: LogLayout.new(
|
15
|
+
def self.initialize(log_level: :info, global_context: {}, layout_options: {})
|
16
|
+
Logging.logger.root.appenders = Logging.appenders.stdout layout: LogLayout.new(global_context, layout_options)
|
17
17
|
Logging.logger.root.level = log_level
|
18
18
|
end
|
19
19
|
|
data/logcraft.gemspec
CHANGED
@@ -8,20 +8,20 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.authors = ["Zoltan Ormandi"]
|
9
9
|
spec.email = ["zoltan.ormandi@gmail.com"]
|
10
10
|
|
11
|
-
spec.summary = "A zero-configuration logging solution for Ruby on Rails."
|
11
|
+
spec.summary = "A zero-configuration structured logging solution for pure Ruby or Ruby on Rails projects."
|
12
12
|
spec.homepage = "https://github.com/zormandi/logcraft"
|
13
13
|
spec.license = "MIT"
|
14
14
|
spec.required_ruby_version = ">= 2.6.0"
|
15
15
|
|
16
16
|
spec.metadata["homepage_uri"] = spec.homepage
|
17
17
|
spec.metadata["source_code_uri"] = "https://github.com/zormandi/logcraft"
|
18
|
-
spec.metadata["changelog_uri"] = "https://github.com/zormandi/logcraft/blob/
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/zormandi/logcraft/blob/main/CHANGELOG.md"
|
19
19
|
|
20
20
|
# Specify which files should be added to the gem when it is released.
|
21
21
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
22
|
spec.files = Dir.chdir(__dir__) do
|
23
23
|
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
-
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
24
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor|Rakefile)})
|
25
25
|
end
|
26
26
|
end
|
27
27
|
spec.bindir = "exe"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: logcraft
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zoltan Ormandi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-07-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: logging
|
@@ -93,10 +93,10 @@ files:
|
|
93
93
|
- Gemfile
|
94
94
|
- LICENSE.txt
|
95
95
|
- README.md
|
96
|
-
- Rakefile
|
97
96
|
- gemfiles/rails_5.2.gemfile
|
98
97
|
- gemfiles/rails_6.0.gemfile
|
99
98
|
- gemfiles/rails_6.1.gemfile
|
99
|
+
- gemfiles/rails_7.0.gemfile
|
100
100
|
- lib/logcraft.rb
|
101
101
|
- lib/logcraft/log_context_helper.rb
|
102
102
|
- lib/logcraft/log_layout.rb
|
@@ -119,7 +119,7 @@ licenses:
|
|
119
119
|
metadata:
|
120
120
|
homepage_uri: https://github.com/zormandi/logcraft
|
121
121
|
source_code_uri: https://github.com/zormandi/logcraft
|
122
|
-
changelog_uri: https://github.com/zormandi/logcraft/blob/
|
122
|
+
changelog_uri: https://github.com/zormandi/logcraft/blob/main/CHANGELOG.md
|
123
123
|
post_install_message:
|
124
124
|
rdoc_options: []
|
125
125
|
require_paths:
|
@@ -131,12 +131,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
131
131
|
version: 2.6.0
|
132
132
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
133
|
requirements:
|
134
|
-
- - "
|
134
|
+
- - ">="
|
135
135
|
- !ruby/object:Gem::Version
|
136
|
-
version:
|
136
|
+
version: '0'
|
137
137
|
requirements: []
|
138
138
|
rubygems_version: 3.1.6
|
139
139
|
signing_key:
|
140
140
|
specification_version: 4
|
141
|
-
summary: A zero-configuration logging solution for Ruby on
|
141
|
+
summary: A zero-configuration structured logging solution for pure Ruby or Ruby on
|
142
|
+
Rails projects.
|
142
143
|
test_files: []
|
data/Rakefile
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "bundler/gem_tasks"
|
4
|
-
require "rspec/core/rake_task"
|
5
|
-
|
6
|
-
def add_logcraft_options_to_application_configuration
|
7
|
-
project_root = File.dirname __FILE__
|
8
|
-
Dir.chdir(project_root + '/spec') do
|
9
|
-
app_config = File.readlines 'test-app/config/application.rb'
|
10
|
-
modified_config = app_config.each_with_object([]) do |line, config|
|
11
|
-
config << line
|
12
|
-
if line.include? 'config.load_defaults'
|
13
|
-
logcraft_config = File.readlines 'fixtures/test-app/config/logcraft_config.rb'
|
14
|
-
config.concat logcraft_config
|
15
|
-
end
|
16
|
-
end
|
17
|
-
File.write 'test-app/config/application.rb', modified_config.join
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
desc 'Generate sample Rails app for acceptance testing'
|
22
|
-
task :generate_rails_app do
|
23
|
-
project_root = File.dirname __FILE__
|
24
|
-
Dir.chdir(project_root + '/spec') do
|
25
|
-
FileUtils.rm_rf 'test-app'
|
26
|
-
system 'rails new test-app --database=sqlite3 --skip-gemfile --skip-git --skip-keeps --skip-action-mailer'\
|
27
|
-
'--skip-action-mailbox --skip-action-text --skip-active-job --skip-active-storage --skip-puma --skip-action-cable'\
|
28
|
-
'--skip-sprockets --skip-spring --skip-listen --skip-javascript --skip-turbolinks --skip-jbuilder --skip-test'\
|
29
|
-
'--skip-system-test --skip-bootsnap --skip-bundle --skip-webpack-install'
|
30
|
-
FileUtils.cp_r 'fixtures/test-app/.', 'test-app', remove_destination: true
|
31
|
-
add_logcraft_options_to_application_configuration
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
namespace :spec do
|
36
|
-
desc 'Run RSpec unit tests'
|
37
|
-
RSpec::Core::RakeTask.new(:unit) do |t|
|
38
|
-
t.exclude_pattern = 'spec/integration/*_spec.rb'
|
39
|
-
end
|
40
|
-
|
41
|
-
desc 'Run RSpec integration tests'
|
42
|
-
RSpec::Core::RakeTask.new(:integration) do |t|
|
43
|
-
t.pattern = 'spec/integration/*_spec.rb'
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
desc 'Run all RSpec examples'
|
48
|
-
RSpec::Core::RakeTask.new(:spec)
|
49
|
-
|
50
|
-
task default: :spec
|