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