logcraft 1.0.0.rc → 2.0.1
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/CHANGELOG.md +24 -1
- data/Gemfile +1 -1
- data/README.md +162 -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 +10 -16
- 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 +13 -12
- 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: 1553a9ab5e60ffb3beab51543cefcca10632c655fafa559ed18fe123dad9243a
|
4
|
+
data.tar.gz: 673ea0771ff8846f9b191f1ab745233cc6002dc2b454b92b97a4e5a8088dea33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d440d1fb9a136f36e30911912ec79fff375ad4086b87697399e6530422d92e8749307ac043acdec270a04d507281e009d5f2bf86f5c01ab00192e40de34149d8
|
7
|
+
data.tar.gz: 5a207a0a5ed398a1ecd95db4252cd0455c265f8a93b4b3b22ab7a60f852d2b8b072ee59366117665476fd35730c134aac958b16d2f1c3670bcf538f08773ea97
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,29 @@ 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.1] - 2022-10-07
|
8
|
+
### Fixed
|
9
|
+
- Fixed a bug where request log tracing didn't work with the DataDog integration. Had to move up logging
|
10
|
+
the request to the point where the middleware is done processing instead of where we originally had it;
|
11
|
+
at the time when the response body was closed. We lost some precision in terms of measuring request duration
|
12
|
+
but some context (e.g. DataDog active trace) would not be available otherwise.
|
13
|
+
|
14
|
+
## [2.0.0] - 2022-07-31
|
15
|
+
### Added
|
16
|
+
- Added the option to change the log level or suppress logging of unhandled errors which are, in fact,
|
17
|
+
handled by Rails (e.g. 404 Not Found).
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
- The initial context is now fully dynamic; it can be either a Hash or a lambda/Proc returning a Hash.
|
21
|
+
Using a Hash with lambda values is no longer supported.
|
22
|
+
- Renamed `initial_context` configuration setting to `global_context` everywhere.
|
23
|
+
|
24
|
+
### Fixed
|
25
|
+
- Fixed a bug where the request ID was missing from the access log.
|
26
|
+
|
27
|
+
### Added
|
28
|
+
- The provided RSpec matchers can now take other matchers as part of the log expectation.
|
29
|
+
|
7
30
|
## [1.0.0.rc] - 2022-06-26
|
8
31
|
### Added
|
9
|
-
- Logcraft was rewritten from the ground up, based on its predecessor: [Ezlog](https://github.com/emartech/ezlog)
|
32
|
+
- 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
|
[](https://github.com/zormandi/logcraft/actions/workflows/main.yml)
|
4
|
+
[](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,85 @@ 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
|
+
|
277
|
+
In a nutshell:
|
278
|
+
```ruby
|
279
|
+
# With default ActiveSupport serialization
|
280
|
+
Rails.logger.info 'foo > bar'
|
281
|
+
#=> {...,"message":"foo \u003e bar"}
|
282
|
+
|
283
|
+
# With Oj
|
284
|
+
Rails.logger.info 'foo > bar'
|
285
|
+
#=> {...,"message":"foo > bar"}
|
286
|
+
```
|
287
|
+
|
203
288
|
## Configuration options
|
204
289
|
|
205
|
-
### Rails
|
290
|
+
### Rails configuration
|
206
291
|
|
207
292
|
Logcraft provides the following configuration options for Rails:
|
208
293
|
|
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
|
294
|
+
| Option | Default value | Description |
|
295
|
+
|-------------------------------------------------------|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
296
|
+
| 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. |
|
297
|
+
| logcraft.layout_options | `{}` | Custom options for the log layout. Currently only the `level_formatter` option is supported (see examples). |
|
298
|
+
| logcraft.access_log.logger_name | `'AccessLog'` | The name of the logger emitting access log messages. |
|
299
|
+
| logcraft.access_log.exclude_paths | `[]` | A list of paths (array of strings or RegExps) not to include in the access log. |
|
300
|
+
| logcraft.access_log.log_only_whitelisted_params | `false` | If `true`, the access log will only contain whitelisted parameters. |
|
301
|
+
| logcraft.access_log.whitelisted_params | `[:controller, :action]` | The only parameters to be logged in the access log if whitelisting is enabled. |
|
302
|
+
| logcraft.unhandled_errors.log_level | `:fatal` | The log level with which to log unhandled errors. Rails logs these with FATAL, by default. |
|
303
|
+
| 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
304
|
|
218
305
|
Examples:
|
306
|
+
|
219
307
|
```ruby
|
220
308
|
# Use these options in your Rails configuration files (e.g. application.rb)
|
221
309
|
|
222
310
|
# Set up a global context you want to see in every log message
|
223
|
-
config.logcraft.
|
224
|
-
|
225
|
-
|
226
|
-
|
311
|
+
config.logcraft.global_context = -> do
|
312
|
+
{
|
313
|
+
environment: ENV['RAILS_ENV'],
|
314
|
+
timestamp_linux: Time.current.to_i # evaluated every time when emitting a log message
|
315
|
+
}
|
316
|
+
end
|
227
317
|
|
228
318
|
# Set up a custom log level formatter (e.g. Ougai-like numbers)
|
229
319
|
config.logcraft.layout_options = {
|
230
|
-
level_formatter: ->(level_number) { (level_number + 2) * 10 }
|
320
|
+
level_formatter: ->(level_number) { (level_number + 2) * 10 }
|
231
321
|
}
|
232
322
|
Rails.logger.error('Boom!')
|
233
323
|
# => {...,"level":50,"message":"Boom!"}
|
234
|
-
|
324
|
+
|
235
325
|
# Exclude healthcheck and monitoring URLs from your access log:
|
236
326
|
config.logcraft.exclude_paths = ['/healthcheck', %r(/monitoring/.*)]
|
237
327
|
|
@@ -239,35 +329,36 @@ config.logcraft.exclude_paths = ['/healthcheck', %r(/monitoring/.*)]
|
|
239
329
|
config.logcraft.log_only_whitelisted_params = true
|
240
330
|
```
|
241
331
|
|
242
|
-
### Non-Rails
|
332
|
+
### Non-Rails configuration
|
243
333
|
|
244
|
-
The `
|
334
|
+
The `global_context` and `layout_options` configuration options (see above) are available to non-Rails projects
|
245
335
|
via Logcraft's initialization mechanism. You can also set the default log level this way.
|
246
336
|
|
247
337
|
```ruby
|
248
|
-
Logcraft.initialize log_level: :info,
|
338
|
+
Logcraft.initialize log_level: :info, global_context: {}, layout_options: {}
|
249
339
|
```
|
250
340
|
|
251
341
|
## Integration with DataDog
|
252
342
|
|
253
|
-
You can set up tracing with [DataDog](https://www.datadoghq.com/) by providing
|
343
|
+
You can set up tracing with [DataDog](https://www.datadoghq.com/) by providing a global context to be included in
|
344
|
+
every log message:
|
254
345
|
|
255
346
|
```ruby
|
256
|
-
config.logcraft.
|
257
|
-
|
258
|
-
return unless Datadog::Tracing.enabled?
|
347
|
+
config.logcraft.global_context = -> do
|
348
|
+
return {} unless Datadog::Tracing.enabled?
|
259
349
|
|
260
|
-
|
261
|
-
|
350
|
+
correlation = Datadog::Tracing.correlation
|
351
|
+
{
|
352
|
+
dd: {
|
262
353
|
trace_id: correlation.trace_id.to_s,
|
263
354
|
span_id: correlation.span_id.to_s,
|
264
355
|
env: correlation.env.to_s,
|
265
356
|
service: correlation.service.to_s,
|
266
357
|
version: correlation.version.to_s
|
267
|
-
}
|
268
|
-
|
269
|
-
|
270
|
-
|
358
|
+
},
|
359
|
+
ddsource: ['ruby']
|
360
|
+
}
|
361
|
+
end
|
271
362
|
```
|
272
363
|
|
273
364
|
## RSpec support
|
@@ -280,12 +371,13 @@ require 'logcraft/rspec'
|
|
280
371
|
```
|
281
372
|
|
282
373
|
What you get:
|
374
|
+
|
283
375
|
* Helpers
|
284
|
-
|
285
|
-
|
376
|
+
* `log_output` provides access to the complete log output (array of strings) in your specs
|
377
|
+
* `log_output_is_expected` shorthand for writing expectations for the log output
|
286
378
|
* Matchers
|
287
|
-
|
288
|
-
|
379
|
+
* `include_log_message` matcher for expecting a certain message in the log output
|
380
|
+
* `log` matcher for expecting an operation to log a certain message
|
289
381
|
|
290
382
|
```ruby
|
291
383
|
# Check that the log output contains a certain message
|
@@ -293,8 +385,13 @@ expect(log_output).to include_log_message message: 'Test message'
|
|
293
385
|
log_output_is_expected.to include_log_message message: 'Test message'
|
294
386
|
|
295
387
|
# 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
|
388
|
+
expect { operation }.to log message: 'Test message',
|
389
|
+
user_id: 123456
|
390
|
+
|
391
|
+
# RSpec's matchers can be used inside the expectation
|
392
|
+
expect { get '/' }.to log message: 'GET / - 200 (OK)',
|
393
|
+
request_id: match(/[\w-]+/),
|
394
|
+
duration: be_within(100).of(100)
|
298
395
|
|
299
396
|
# Expect a certain log level
|
300
397
|
log_output_is_expected.to include_log_message(message: 'Test message').at_level(:info)
|
@@ -303,22 +400,22 @@ expect { operation }.to log(message: 'Test message').at_level(:info)
|
|
303
400
|
|
304
401
|
## Development
|
305
402
|
|
306
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
403
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
307
404
|
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
308
405
|
|
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
|
406
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version,
|
407
|
+
update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag
|
311
408
|
for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
312
409
|
|
313
410
|
## Contributing
|
314
411
|
|
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
|
412
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/zormandi/logcraft. This project is intended
|
413
|
+
to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the
|
317
414
|
[code of conduct](https://github.com/zormandi/logcraft/blob/master/CODE_OF_CONDUCT.md).
|
318
415
|
|
319
416
|
## Disclaimer
|
320
417
|
|
321
|
-
Logcraft is highly opinionated software and does in no way aim or claim to be useful for everyone.
|
418
|
+
Logcraft is highly opinionated software and does in no way aim or claim to be useful for everyone.
|
322
419
|
Use at your own discretion.
|
323
420
|
|
324
421
|
## License
|
@@ -327,5 +424,5 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
327
424
|
|
328
425
|
## Code of Conduct
|
329
426
|
|
330
|
-
Everyone interacting in the Logcraft project's codebases, issue trackers, chat rooms and mailing lists is expected
|
427
|
+
Everyone interacting in the Logcraft project's codebases, issue trackers, chat rooms and mailing lists is expected
|
331
428
|
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
|
@@ -14,12 +14,9 @@ module Logcraft
|
|
14
14
|
request = ActionDispatch::Request.new env
|
15
15
|
|
16
16
|
instrumentation_start request
|
17
|
-
|
18
17
|
status, headers, body = @app.call env
|
19
|
-
body = ::Rack::BodyProxy.new(body)
|
20
|
-
|
21
|
-
log_request request, status, start_time
|
22
|
-
end
|
18
|
+
body = ::Rack::BodyProxy.new(body) { instrumentation_finish request }
|
19
|
+
log_request request, status, start_time
|
23
20
|
|
24
21
|
[status, headers, body]
|
25
22
|
rescue Exception => ex
|
@@ -50,17 +47,14 @@ module Logcraft
|
|
50
47
|
return if path_ignored? request
|
51
48
|
|
52
49
|
end_time = current_time_in_milliseconds
|
53
|
-
message
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
duration_sec: (end_time - start_time) / 1000.0
|
62
|
-
}
|
63
|
-
@logger.info message
|
50
|
+
@logger.info message: '%s %s - %i (%s)' % [request.method, request.filtered_path, status, Rack::Utils::HTTP_STATUS_CODES[status]],
|
51
|
+
remote_ip: request.remote_ip,
|
52
|
+
method: request.method,
|
53
|
+
path: request.filtered_path,
|
54
|
+
params: params_to_log(request),
|
55
|
+
response_status_code: status,
|
56
|
+
duration: end_time - start_time,
|
57
|
+
duration_sec: (end_time - start_time) / 1000.0
|
64
58
|
end
|
65
59
|
|
66
60
|
def path_ignored?(request)
|
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.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zoltan Ormandi
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: logging
|
@@ -80,7 +80,7 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '3.0'
|
83
|
-
description:
|
83
|
+
description:
|
84
84
|
email:
|
85
85
|
- zoltan.ormandi@gmail.com
|
86
86
|
executables: []
|
@@ -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,8 +119,8 @@ 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/
|
123
|
-
post_install_message:
|
122
|
+
changelog_uri: https://github.com/zormandi/logcraft/blob/main/CHANGELOG.md
|
123
|
+
post_install_message:
|
124
124
|
rdoc_options: []
|
125
125
|
require_paths:
|
126
126
|
- lib
|
@@ -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
|
-
rubygems_version: 3.
|
139
|
-
signing_key:
|
138
|
+
rubygems_version: 3.3.7
|
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
|