semantic_logger 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -0
  3. data/Rakefile +29 -0
  4. data/docs/api.md +480 -0
  5. data/docs/appenders.md +990 -0
  6. data/docs/config.md +605 -0
  7. data/docs/index.md +197 -0
  8. data/docs/log.md +108 -0
  9. data/docs/metrics.md +168 -0
  10. data/docs/operations.md +400 -0
  11. data/docs/rails.md +816 -0
  12. data/docs/security.md +119 -0
  13. data/docs/testing.md +285 -0
  14. data/docs/upgrading.md +409 -0
  15. data/lib/semantic_logger/appender/cloudwatch_logs.rb +4 -2
  16. data/lib/semantic_logger/appender/elasticsearch.rb +2 -5
  17. data/lib/semantic_logger/appender/elasticsearch_base.rb +17 -17
  18. data/lib/semantic_logger/appender/elasticsearch_http.rb +14 -7
  19. data/lib/semantic_logger/appender/file.rb +5 -0
  20. data/lib/semantic_logger/appender/new_relic.rb +6 -0
  21. data/lib/semantic_logger/appender/new_relic_logs.rb +2 -1
  22. data/lib/semantic_logger/appender/open_telemetry.rb +2 -1
  23. data/lib/semantic_logger/appender/opensearch.rb +2 -1
  24. data/lib/semantic_logger/appender/rabbitmq.rb +4 -3
  25. data/lib/semantic_logger/appender/sentry.rb +5 -0
  26. data/lib/semantic_logger/appender/syslog.rb +6 -3
  27. data/lib/semantic_logger/appender/tcp.rb +4 -3
  28. data/lib/semantic_logger/appender/wrapper.rb +2 -1
  29. data/lib/semantic_logger/appender.rb +6 -3
  30. data/lib/semantic_logger/appenders.rb +2 -1
  31. data/lib/semantic_logger/base.rb +0 -2
  32. data/lib/semantic_logger/formatters/color.rb +5 -2
  33. data/lib/semantic_logger/formatters/default.rb +7 -2
  34. data/lib/semantic_logger/formatters/logfmt.rb +4 -2
  35. data/lib/semantic_logger/formatters/pattern.rb +3 -1
  36. data/lib/semantic_logger/formatters/syslog.rb +2 -1
  37. data/lib/semantic_logger/formatters/syslog_cee.rb +2 -1
  38. data/lib/semantic_logger/metric/signalfx.rb +4 -2
  39. data/lib/semantic_logger/queue_processor.rb +5 -2
  40. data/lib/semantic_logger/reporters/minitest.rb +4 -4
  41. data/lib/semantic_logger/test/capture_log_events.rb +1 -1
  42. data/lib/semantic_logger/version.rb +1 -1
  43. metadata +14 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff43a3438164b6bf70367a7b2806350e65da8762fc6fb4e333a769cdf200e95b
4
- data.tar.gz: 3d12647aaed6e22b7abc7a4c377c0d0470f00ffee3401d2db6043aece3ce09c1
3
+ metadata.gz: f18701015c0b556099814aff1b79e251df13f556c103a8366b58357d9d10fd8c
4
+ data.tar.gz: cf18f050902946ac09a065a52a6f4cee9841f5c250ef30162d39347977e071d2
5
5
  SHA512:
6
- metadata.gz: 2893b8e69a4d246c1346e5fc6b20ca6a8fcdc3a29e78a44e4e1c8dae5466971298d9cfcabd736080beef17c2eb68914cb90ddef844bf6afd3ede388850232a90
7
- data.tar.gz: 944f2cbf7232a4cae3ecee1f67d770d4b6a9306fae810b6045710c6920cc5876a5d56246d0a2605c7e066722df4abba1d8dccc29b43964c677a1bd33296dac73
6
+ metadata.gz: f62d59fc517122f6da01702c62cf5a33e47d629605f0dd5e096f953ebdcd7d3e6dbb9bf2e25d166d32f171dbe16ba1470726ebe760343e6e04994cef7b5c6dcd
7
+ data.tar.gz: 3c741614f72b723e50ed3e3ec2a785de85eb6a026b9ed52d67bb064c1617814d77afeb67e5f8f7f4cb6f20f339affd14f54a82ffe23194e256ba66da9e8aedf0
data/README.md CHANGED
@@ -24,6 +24,12 @@ logger = SemanticLogger["MyApp"]
24
24
 
25
25
  # A plain message, plus structured data that stays searchable
26
26
  logger.info("Queried users table", duration: 54, result: :ok, table: "users")
27
+
28
+ # Log an exception with its full backtrace, plus context
29
+ logger.error("Failed to process order", exception: exception, order_id: 42)
30
+
31
+ # Measure and log how long a block takes to run
32
+ logger.measure_info("Reindexing users") { reindex_users }
27
33
  ```
28
34
 
29
35
  When running Rails, use
@@ -36,6 +42,9 @@ Start with the [Introduction](https://logger.rocketjob.io/), then the
36
42
  [Programmer's Guide](https://logger.rocketjob.io/api.html).
37
43
 
38
44
  * Full guide: [https://logger.rocketjob.io/](https://logger.rocketjob.io/)
45
+ * For AI assistants: [llms.txt](https://logger.rocketjob.io/llms.txt) (documentation index) and
46
+ [llms-full.txt](https://logger.rocketjob.io/llms-full.txt) (complete documentation in one file).
47
+ The same pages are also included as markdown in the installed gem, under `docs/`.
39
48
 
40
49
  ## Logging Destinations
41
50
 
data/Rakefile CHANGED
@@ -4,10 +4,12 @@ require "rake/testtask"
4
4
  $LOAD_PATH.unshift File.expand_path("lib", __dir__)
5
5
  require "semantic_logger/version"
6
6
 
7
+ desc "Build the semantic_logger gem"
7
8
  task :gem do
8
9
  system "gem build semantic_logger.gemspec"
9
10
  end
10
11
 
12
+ desc "Tag and push the release, then publish the gem to RubyGems"
11
13
  task publish: :gem do
12
14
  system "git tag -a v#{SemanticLogger::VERSION} -m 'Tagging #{SemanticLogger::VERSION}'"
13
15
  system "git push --tags"
@@ -15,6 +17,33 @@ task publish: :gem do
15
17
  system "rm semantic_logger-#{SemanticLogger::VERSION}.gem"
16
18
  end
17
19
 
20
+ desc "Regenerate docs/llms-full.txt from the docs markdown pages"
21
+ task :llms_full do
22
+ pages = %w[index api config appenders log metrics rails testing operations security upgrading]
23
+ header = <<~HEADER
24
+ # Semantic Logger - Complete Documentation
25
+
26
+ > Semantic Logger is a high-performance, asynchronous structured logging framework for Ruby and Rails.
27
+
28
+ This file concatenates every page of https://logger.rocketjob.io for consumption by AI assistants.
29
+ It is generated from the markdown sources in docs/ by `bundle exec rake llms_full`; do not edit it directly.
30
+ A per-page index is available at https://logger.rocketjob.io/llms.txt
31
+ HEADER
32
+
33
+ sections = pages.map do |page|
34
+ text = File.read("docs/#{page}.md").
35
+ sub(/\A---\n.*?\n---\n/m, ""). # Jekyll front matter
36
+ gsub(/^\{:.*\}\n/, ""). # kramdown attribute lines ({:toc}, {:.no_toc}, ...)
37
+ gsub(/^\* TOC\n/, "").
38
+ gsub(/^\*\*Contents\*\*\n/, "").
39
+ gsub(/^!\[.*\n/, "") # images (relative paths, useless in plain text)
40
+ "<!-- source: docs/#{page}.md -->\n\n#{text.strip}\n"
41
+ end
42
+
43
+ File.write("docs/llms-full.txt", ([header] + sections).join("\n\n---\n\n"))
44
+ puts "Wrote docs/llms-full.txt (#{File.size('docs/llms-full.txt')} bytes)"
45
+ end
46
+
18
47
  Rake::TestTask.new(:test) do |t|
19
48
  t.pattern = "test/**/*_test.rb"
20
49
  t.verbose = true
data/docs/api.md ADDED
@@ -0,0 +1,480 @@
1
+ ---
2
+ layout: default
3
+ ---
4
+
5
+ ## Programming Guide
6
+ {:.no_toc}
7
+
8
+ **Contents**
9
+
10
+ * TOC
11
+ {:toc}
12
+
13
+ This guide covers the logging API: how to get a logger, and everything you can do with it. It builds
14
+ up from the simplest log call to more advanced features further down, so you can read it top to
15
+ bottom or jump to the part you need.
16
+
17
+ It assumes Semantic Logger is already installed with at least one appender (a destination such as the
18
+ screen or a file). If not, start with the [quick start](index.html#quick-start), then come back here.
19
+ For configuring the library itself (log levels, formatters, destinations), see
20
+ [Configuration](config.html).
21
+
22
+ Every example below uses a `logger` obtained in Step 1.
23
+
24
+ ## Step 1: Get a logger
25
+
26
+ Create one logger per class, passing the class itself:
27
+
28
+ ~~~ruby
29
+ logger = SemanticLogger[MyClass]
30
+ ~~~
31
+
32
+ When there is no class, for example in a script, pass a name instead:
33
+
34
+ ~~~ruby
35
+ logger = SemanticLogger["MyApp"]
36
+ ~~~
37
+
38
+ The class or name you supply is attached to every entry that logger writes, so entries from different
39
+ parts of the application stay easy to tell apart. Use one logger per class so each entry identifies
40
+ where it came from.
41
+
42
+ ### The Loggable mixin (recommended)
43
+
44
+ Rather than creating a logger by hand in every class, include `SemanticLogger::Loggable`. It adds a
45
+ `logger` method to both the class and its instances, already named for the class:
46
+
47
+ ~~~ruby
48
+ class Supplier
49
+ include SemanticLogger::Loggable
50
+
51
+ def self.some_class_method
52
+ logger.debug("Accessible from class methods")
53
+ end
54
+
55
+ def call_supplier
56
+ logger.debug("Accessible from instance methods")
57
+ end
58
+ end
59
+ ~~~
60
+
61
+ Every entry is identified as coming from `Supplier`:
62
+
63
+ ~~~
64
+ 2012-08-30 15:37:29.474 I [48308:main] Supplier -- Accessible from instance methods
65
+ ~~~
66
+
67
+ By default `SemanticLogger[...]` returns a brand new logger on each call. To share a single logger
68
+ per class instead, enable [logger caching](config.html#caching-loggers).
69
+
70
+ ## Step 2: Log a message
71
+
72
+ Semantic Logger supports the standard Ruby and Rails logger API, so existing code keeps working:
73
+
74
+ ~~~ruby
75
+ logger.info("Hello World")
76
+ ~~~
77
+
78
+ There is one method per level. The levels, from the most detail to the least, are:
79
+
80
+ :trace :debug :info :warn :error :fatal
81
+
82
+ ~~~ruby
83
+ logger.trace("Low level detail, such as data sent over a socket")
84
+ logger.debug("Information to aid problem determination")
85
+ logger.info("Something normal happened, such as a request received")
86
+ logger.warn("Something unexpected, but handled")
87
+ logger.error("An error occurred during processing")
88
+ logger.fatal("Something really bad happened")
89
+ ~~~
90
+
91
+ The active level acts as a threshold. At the default of `:info`, the `info`, `warn`, `error`, and
92
+ `fatal` calls are written while `debug` and `trace` are skipped. `:trace` is the most detailed level,
93
+ useful for tracing low level calls such as data exchanged with an external service. Setting the
94
+ global default level, and changing it at runtime, is covered in
95
+ [Configuration](config.html#default-log-level).
96
+
97
+ To check whether a level is active (for example before doing expensive work):
98
+
99
+ ~~~ruby
100
+ logger.info? # => true when :info and above are being logged
101
+ ~~~
102
+
103
+ ## Step 3: Add structured data
104
+
105
+ This is what sets Semantic Logger apart. Instead of building a sentence by hand, pass the data as a
106
+ Hash "payload" after the message:
107
+
108
+ ~~~ruby
109
+ # Traditional logging bakes the data into a string:
110
+ logger.info("Queried users in #{duration}ms, result #{result}")
111
+
112
+ # Semantic Logger keeps the message and the data separate:
113
+ logger.info("Queried users", duration: duration, result: result, table: "users")
114
+ ~~~
115
+
116
+ The message stays readable for a human, and the payload stays machine readable: a JSON, MongoDB, or
117
+ Elasticsearch appender indexes `duration`, `result`, and `table` as real fields, so you can search
118
+ and build dashboards on them without parsing log text. The fields that make up an entry are listed in
119
+ [Log Event](log.html).
120
+
121
+ ## Step 4: Log an exception
122
+
123
+ Pass a Ruby exception as the second argument. Its class, message, and backtrace are all captured:
124
+
125
+ ~~~ruby
126
+ begin
127
+ # ... code that may raise
128
+ rescue => exception
129
+ logger.error("Outbound call failed", exception)
130
+ end
131
+ ~~~
132
+
133
+ To log a payload and an exception together, pass both:
134
+
135
+ ~~~ruby
136
+ logger.error("Outbound call failed", {result: :failed}, exception)
137
+ ~~~
138
+
139
+ ## Step 5: Skip expensive messages with a block
140
+
141
+ When building the message is itself expensive, pass a block instead of a string. The block runs only
142
+ when the level is active, so it costs nothing when that level is turned off (for example in
143
+ production):
144
+
145
+ ~~~ruby
146
+ logger.debug { "Processed #{records.sum(&:size)} bytes across #{records.size} records" }
147
+ ~~~
148
+
149
+ ### The full call signature
150
+
151
+ Putting the pieces together, every level method accepts:
152
+
153
+ ~~~ruby
154
+ logger.info(message, payload_or_exception = nil, exception = nil, &block)
155
+ ~~~
156
+
157
+ - `message`: the text message. Optional only when a block is supplied.
158
+ - `payload_or_exception`: an optional Hash payload, or a Ruby exception.
159
+ - `exception`: an optional exception, used when you are also passing a payload.
160
+ - `&block`: evaluated only when the level is active; its return value becomes the message.
161
+
162
+ The same call can also be written as a single Hash, which is handy when assembling fields
163
+ programmatically:
164
+
165
+ ~~~ruby
166
+ logger.debug(message: "Calling Supplier", payload: {request: "update", user: "Jack"})
167
+
168
+ # Log a complete exception
169
+ logger.error(message: "Calling Supplier", exception: exception)
170
+
171
+ # Attach a duration of 100ms
172
+ logger.error(message: "Calling Supplier", duration: 100)
173
+
174
+ # Attach a metric (see Step 6)
175
+ logger.error(message: "Calling Supplier", metric: "Supplier/inquiry", metric_amount: 21)
176
+ ~~~
177
+
178
+ ## Step 6: Measure how long something takes
179
+
180
+ It is good practice to "measure everything" in production, so that when things slow down it is
181
+ obvious where the time is going. Wrap the code in a `measure_*` call:
182
+
183
+ ~~~ruby
184
+ logger.measure_info("Called external interface") do
185
+ # Code to call the external service ...
186
+ end
187
+ ~~~
188
+
189
+ The entry is written once the block completes, and includes the duration:
190
+
191
+ ~~~
192
+ 2012-08-30 15:37:29.474 I [48308:script/rails] (5.2ms) Rails -- Called external interface
193
+ ~~~
194
+
195
+ If the block raises, the exception is logged at the same level along with the duration, then re-raised
196
+ unchanged. There is a measure method for every level:
197
+
198
+ ~~~ruby
199
+ logger.measure_trace / measure_debug / measure_info / measure_warn / measure_error / measure_fatal
200
+ ~~~
201
+
202
+ Or supply the level dynamically as the first argument:
203
+
204
+ ~~~ruby
205
+ logger.measure(:info, "Request received") do
206
+ # ...
207
+ end
208
+ ~~~
209
+
210
+ ### Only log when it is slow (elastic logging)
211
+
212
+ Pass `min_duration` (in milliseconds) to log only when the block runs longer than the threshold.
213
+ This surfaces slow calls without the noise of the fast ones:
214
+
215
+ ~~~ruby
216
+ logger.measure_warn("Called memcache", min_duration: 3) do
217
+ # Usually fast; only logged when it takes longer than 3 ms
218
+ end
219
+ ~~~
220
+
221
+ ### Record a metric
222
+
223
+ Attach a `metric` name to feed dashboards. See [Metrics](metrics.html):
224
+
225
+ ~~~ruby
226
+ logger.measure_info("Called external interface", metric: "Supplier/inquiry") do
227
+ # ...
228
+ end
229
+ ~~~
230
+
231
+ ### Supply the duration yourself
232
+
233
+ When you already have a duration, log it without a block. This keeps the duration as structured data
234
+ rather than embedding it in the message text:
235
+
236
+ ~~~ruby
237
+ duration = Time.now - start_time
238
+ logger.measure_info("Called external interface", duration: duration)
239
+ ~~~
240
+
241
+ Either a block or `:duration` must be supplied on every measure call.
242
+
243
+ ### All measure options
244
+
245
+ The second argument to a measure call is a Hash of options:
246
+
247
+ - `:min_duration` [Float]: only log if the block takes longer than this many milliseconds. Default
248
+ `0.0` (always log).
249
+ - `:metric` [String]: notify metric subscribers with this metric name.
250
+ - `:payload` [Hash]: an optional payload to log with the entry.
251
+ - `:exception` [Exception]: an exception to log along with the duration.
252
+ - `:duration` [Float]: the duration in ms, used when no block is supplied (then it is mandatory; with
253
+ a block it is ignored).
254
+ - `:log_exception` [Symbol]: how to report an exception raised in the block. `:full` logs the class,
255
+ message, and backtrace; `:partial` logs the class and message only; `:off` does not log it.
256
+ Default `:partial`.
257
+ - `:on_exception_level` [Symbol]: if an exception is raised, raise the log level to this level.
258
+ - `:silence` [Symbol]: the level to silence other log messages to within the block (current thread
259
+ only).
260
+
261
+ Putting several together:
262
+
263
+ ~~~ruby
264
+ logger.measure_info("Called external interface",
265
+ log_exception: :full,
266
+ min_duration: 100,
267
+ metric: "Custom/Supplier/process") do
268
+ # Code to call the external service ...
269
+ end
270
+ ~~~
271
+
272
+ ## Step 7: Tag related entries
273
+
274
+ In a concurrent application it is invaluable to find every entry that belongs to one request or job.
275
+ `tagged` adds tags to every entry logged inside its block:
276
+
277
+ ~~~ruby
278
+ tracking_number = "15354128"
279
+
280
+ SemanticLogger.tagged(tracking_number) do
281
+ logger.debug("Hello World") # this entry carries the tracking_number tag
282
+ end
283
+ ~~~
284
+
285
+ Prefer named tags. They are clearer as a system grows, and easier to filter and alert on in a
286
+ centralized logging system:
287
+
288
+ ~~~ruby
289
+ SemanticLogger.tagged(user: "Jack", zip_code: 12345) do
290
+ logger.debug("Hello World") # carries user and zip_code
291
+ end
292
+ ~~~
293
+
294
+ Tags are scoped to the current thread, so a new thread started inside the block does not inherit them.
295
+ [Parallel Minion](https://github.com/reidmorrison/parallel_minion) creates threads that copy the tags
296
+ across automatically.
297
+
298
+ ### Bind tags to one logger (child loggers)
299
+
300
+ The block form above scopes tags to the thread. Sometimes it is more convenient to bind tags to a
301
+ single logger instance, for example when the logger belongs to an object with its own identity (an
302
+ ActiveRecord model or a background job).
303
+
304
+ Calling `tagged` (or its alias `with_tags`) **without a block** returns a new "child" logger that
305
+ permanently carries the supplied tags. Every entry from that child, and only that child, includes
306
+ them, even across threads:
307
+
308
+ ~~~ruby
309
+ class Cart
310
+ include SemanticLogger::Loggable
311
+
312
+ def initialize(id)
313
+ @id = id
314
+ # Bind this Cart's identity to its own logger instance.
315
+ @logger = SemanticLogger["Cart"].tagged(cart_id: id)
316
+ end
317
+
318
+ attr_reader :logger
319
+
320
+ def add_item(item_id)
321
+ # Automatically tagged with cart_id, without wrapping every method in a block.
322
+ logger.info("Added item", item_id: item_id)
323
+ end
324
+ end
325
+ ~~~
326
+
327
+ Positional and named tags can be mixed:
328
+
329
+ ~~~ruby
330
+ logger = SemanticLogger["Payments"].tagged("billing", region: "eu")
331
+ logger.info("Charged card") # tagged with ["billing"] and {region: "eu"}
332
+ ~~~
333
+
334
+ Notes:
335
+
336
+ - The original logger is never modified; `tagged` returns a copy. Child loggers can be nested, each
337
+ level adding to the tags inherited from its parent.
338
+ - Instance tags combine with any thread tags from a surrounding `tagged` block: positional thread tags
339
+ come first, then the logger's instance tags. For named tags, the logger's own tags win on a key
340
+ conflict, since they represent its identity.
341
+ - Child loggers are ordinary instances, registered nowhere, so they are garbage collected along with
342
+ the object that owns them.
343
+
344
+ ## Going further
345
+
346
+ The features above cover everyday logging. The rest of this guide covers less common needs.
347
+
348
+ ### Name your threads
349
+
350
+ Semantic Logger includes the thread name (or id) in every entry. On Ruby MRI the name defaults to the
351
+ thread's object id:
352
+
353
+ ~~~
354
+ 2013-11-07 16:25:14.279 I [35841:70184354571980] (0.0ms) ExternalSupplier -- Calling external interface
355
+ ~~~
356
+
357
+ Give a thread a readable name so it stands out in the logs:
358
+
359
+ ~~~ruby
360
+ Thread.current.name = "User calculation thread 32"
361
+ ~~~
362
+
363
+ ~~~
364
+ 2013-11-07 16:26:02.744 I [35841:User calculation thread 32] (0.0ms) ExternalSupplier -- Calling external interface
365
+ ~~~
366
+
367
+ Keep the name unique, otherwise concurrent threads are hard to tell apart. Including the object id is
368
+ one way to guarantee that:
369
+
370
+ ~~~ruby
371
+ Thread.current.name = "Worker Thread:#{Thread.current.object_id}"
372
+ ~~~
373
+
374
+ On JRuby this also sets the underlying JVM thread name, which is useful when monitoring the JVM over
375
+ JMX with tools such as jconsole.
376
+
377
+ ### Change one class's level at runtime
378
+
379
+ Because each class has its own logger, you can change one class's level on the fly, for example to
380
+ temporarily turn on `:trace` while diagnosing an issue, without touching the rest of the application:
381
+
382
+ ~~~ruby
383
+ # Raise the detail for this one class
384
+ ExternalSupplier.logger.level = :trace
385
+
386
+ # ... reproduce the issue; trace entries from ExternalSupplier are now logged ...
387
+
388
+ # Return it to following the global default level
389
+ ExternalSupplier.logger.level = nil
390
+ ~~~
391
+
392
+ To change the global default level for every logger that has not been set explicitly, set
393
+ `SemanticLogger.default_level`. See [Configuration](config.html#default-log-level), and
394
+ [Signals](operations.html#linux-signals) for changing it in a running process without a restart.
395
+
396
+ ### Silence noisy code
397
+
398
+ `silence` raises the level within a block, on the current thread only, to quiet a noisy section:
399
+
400
+ ~~~ruby
401
+ # Within this block, log only :error and above
402
+ logger.silence do
403
+ logger.info "not logged"
404
+ logger.warn "not logged"
405
+ logger.error "but errors are logged"
406
+ end
407
+ ~~~
408
+
409
+ It can also lower the level within the block, to get more detail from one section:
410
+
411
+ ~~~ruby
412
+ logger.silence(:trace) do
413
+ logger.debug "logged, even though the default level is higher"
414
+ end
415
+ ~~~
416
+
417
+ `silence` has no effect on loggers whose level was set explicitly (those that do not follow the global
418
+ default), and does not affect threads spawned inside the block.
419
+
420
+ ### Map a noisy gem's debug logs to trace
421
+
422
+ Some third party gems log a lot at `:debug`, because they do not have Semantic Logger's `:trace`
423
+ level. Wrap such a library's logger in `SemanticLogger::DebugAsTraceLogger` so its `debug` calls are
424
+ recorded as `:trace`, keeping them out of your `:debug` output:
425
+
426
+ ~~~ruby
427
+ logger = SemanticLogger::DebugAsTraceLogger.new("NoisyLibrary")
428
+ logger.debug "Some very low level noisy message" # logged as :trace
429
+ ~~~
430
+
431
+ ### Capture causal (nested) exceptions
432
+
433
+ When one exception is rescued and another raised, Ruby records the original as the new exception's
434
+ `cause`. Semantic Logger logs the whole chain automatically:
435
+
436
+ ~~~ruby
437
+ def oh_no
438
+ File.new("filename", "w").read # raises IOError: not opened for reading
439
+ rescue IOError
440
+ raise RuntimeError, "Failed to write to file"
441
+ end
442
+
443
+ begin
444
+ oh_no
445
+ rescue StandardError => exception
446
+ logger.error("Failed calling oh_no", exception)
447
+ end
448
+ ~~~
449
+
450
+ Both exceptions are logged, the second backtrace starting at `Cause:`:
451
+
452
+ ~~~
453
+ E [17641:70311685126260 demo.rb:17] Demo -- Failed calling oh_no -- Exception: RuntimeError: Failed to write to file
454
+ demo.rb:6:in `rescue in oh_no'
455
+ demo.rb:2:in `oh_no'
456
+ Cause: IOError: not opened for reading
457
+ demo.rb:4:in `read'
458
+ demo.rb:4:in `oh_no'
459
+ ~~~
460
+
461
+ ### Replace the logger in other gems
462
+
463
+ Rails Semantic Logger already replaces the loggers for many gems. When using Semantic Logger
464
+ stand-alone, hand them a Semantic Logger instance yourself:
465
+
466
+ ~~~ruby
467
+ Resque.logger = SemanticLogger[Resque] if defined?(Resque) && Resque.respond_to?(:logger)
468
+ Mongoid.logger = SemanticLogger[Mongoid] if defined?(Mongoid)
469
+
470
+ Sidekiq.configure_server do |config|
471
+ config.logger = SemanticLogger[Sidekiq]
472
+ end
473
+ ~~~
474
+
475
+ ## Next steps
476
+
477
+ - [Log Event](log.html): the structure of every entry your filters, formatters, and appenders
478
+ receive.
479
+ - [Configuration](config.html): global settings, custom formatters, filtering, and destinations.
480
+ - [Operations](operations.html): process forking, performance tuning, signals, and log rotation.