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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cebda0be923bafa1e3409178e92baa0c0161540b94655c1ccf0cc756ff7dbc2f
4
- data.tar.gz: 35bcecda73c4bca768c4178fa5a0f1fe478d142b93f91f63ef8c8835dbc7d3f0
3
+ metadata.gz: 2256a97ecb30662f543512b9ace85cabe956a49ee4407f0f3575a56ff36055bd
4
+ data.tar.gz: 89a522ff6b287c903110b9df445f201a65955a52edf371bd3df125691dfa3482
5
5
  SHA512:
6
- metadata.gz: 4ee4ea6eafd4e3b4ca5ff71b542baf38a58685ae639e083ece215a7e3f27d8c4b4fd08569f07e62d96602a5f37d7495478be90b7844adfa28b8caf3c6df7c829
7
- data.tar.gz: 16f1d50ab12098b2e2ab341b4ff659f0504ff8282e8b78fe8cfe8a1ab8d8413e6d413b1ac4912e9e3af76080d7cc7407174a1b9a713b1922785180b562911f6a
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
@@ -6,7 +6,7 @@ source "https://rubygems.org"
6
6
  gemspec
7
7
 
8
8
  group :test do
9
- gem 'rails', '~> 6.1'
9
+ gem 'rails', '~> 7.0.0'
10
10
  gem 'rspec-rails', '~> 4.0'
11
11
  gem 'sqlite3', '~> 1.4'
12
12
  gem 'net-smtp', require: false
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/) applications.
6
- It is the successor to [Ezlog](https://github.com/emartech/ezlog) with which it shares its ideals but is reimagined and
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
- Logcraft uses Tim Pease's wonderful [Logging](https://github.com/TwP/logging) gem under the hood for an all-purpose structured logging solution.
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 these
32
- settings manually in the way you're used to; via Rails's configuration mechanism. Unfortunately the Rails new project generator
33
- automatically generates code for the production environment configuration that overrides some of these default settings.
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":"Zoltans-iPro","pid":80422,"message":"Log message"}
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.warn error
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": "WARN",
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 lines,
140
- Logcraft log every uncaught error in 1 line (per error), including the error's name and context (stack trace, etc.).
141
- * Most importantly, Rails's default request logging - which logs several lines per event during the processing of an action -
142
- is replaced by Logcraft's own access log middleware. The end result is an access log that
143
- * contains all relevant information (request ID, method, path, params, client IP, duration and response status code), and
144
- * has 1 log line per request, logged at the end of the request.
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, "request_id": "9a43631b-284c-4677-9d08-9c1cc5c7d3a7",
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 policy
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 | Default value | Description |
210
- |-------------------------------------------------|--------------------------|-------------------------------------------------------------------------------------------------------------|
211
- | logcraft.initial_context | `{}` | A global log context that will be included in every log message. May include lambdas (see examples). |
212
- | logcraft.layout_options | `{}` | Custom options for the log layout. Currently only the `level_formatter` option is supported (see examples). |
213
- | logcraft.access_log.logger_name | `'AccessLog'` | The name of the logger emitting access log messages. |
214
- | logcraft.access_log.exclude_paths | `[]` | A list of paths (array of strings or RegExps) not to include in the access log. |
215
- | logcraft.access_log.log_only_whitelisted_params | `false` | If `true`, the access log will only contain whitelisted parameters. |
216
- | logcraft.access_log.whitelisted_params | `[:controller, :action]` | The only parameters to be logged in the access log if whitelisting is enabled. |
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.initial_context = {
224
- environment: ENV['RAILS_ENV'],
225
- timestamp_linux: -> { Time.current.to_i } # evaluated when emitting a log message
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 `initial_context` and `layout_options` configuration options (see above) are available to non-Rails projects
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, initial_context: {}, layout_options: {}
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 an initial context to be included in every log message:
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.initial_context = {
257
- dd: -> do
258
- return unless Datadog::Tracing.enabled?
336
+ config.logcraft.global_context = -> do
337
+ return {} unless Datadog::Tracing.enabled?
259
338
 
260
- correlation = Datadog::Tracing.correlation
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
- end,
269
- ddsource: ['ruby']
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
- * `log_output` provides access to the complete log output (array of strings) in your specs
285
- * `log_output_is_expected` shorthand for writing expectations for the log output
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
- * `include_log_message` matcher for expecting a certain message in the log output
288
- * `log` matcher for expecting an operation to log a certain message
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
@@ -4,13 +4,13 @@ require 'time'
4
4
 
5
5
  module Logcraft
6
6
  class LogLayout < Logging::Layout
7
- def initialize(context = {}, options = {})
8
- @general_context = context
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 evaluated_general_context,
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 evaluated_general_context
32
- @general_context.transform_values { |v| v.is_a?(Proc) ? v.call : v }
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
- logger.fatal exception
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
 
@@ -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.initial_context = {}
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
- initial_context: app.config.logcraft.initial_context,
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 |expected|
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, expected_messages_from(expected) }
9
+ actual.any? { |log_line| includes? log_line, expected_message }
10
10
  end
11
11
 
12
- def includes?(log_line, messages)
13
- return false unless includes_log_level? log_line
14
- messages.all? { |message| log_line.include? message }
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 includes_log_level?(log_line)
18
- return true if log_level.nil?
19
- log_line.include? log_level_string(log_level)
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'#{expected}'"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Logcraft
4
- VERSION = "1.0.0.rc"
4
+ VERSION = "2.0.0"
5
5
  end
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, initial_context: {}, layout_options: {})
16
- Logging.logger.root.appenders = Logging.appenders.stdout layout: LogLayout.new(initial_context, layout_options)
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/master/CHANGELOG.md"
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: 1.0.0.rc
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-06-26 00:00:00.000000000 Z
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/master/CHANGELOG.md
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: 1.3.1
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 Rails.
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