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.
- checksums.yaml +4 -4
- data/README.md +9 -0
- data/Rakefile +29 -0
- data/docs/api.md +480 -0
- data/docs/appenders.md +990 -0
- data/docs/config.md +605 -0
- data/docs/index.md +197 -0
- data/docs/log.md +108 -0
- data/docs/metrics.md +168 -0
- data/docs/operations.md +400 -0
- data/docs/rails.md +816 -0
- data/docs/security.md +119 -0
- data/docs/testing.md +285 -0
- data/docs/upgrading.md +409 -0
- data/lib/semantic_logger/appender/cloudwatch_logs.rb +4 -2
- data/lib/semantic_logger/appender/elasticsearch.rb +2 -5
- data/lib/semantic_logger/appender/elasticsearch_base.rb +17 -17
- data/lib/semantic_logger/appender/elasticsearch_http.rb +14 -7
- data/lib/semantic_logger/appender/file.rb +5 -0
- data/lib/semantic_logger/appender/new_relic.rb +6 -0
- data/lib/semantic_logger/appender/new_relic_logs.rb +2 -1
- data/lib/semantic_logger/appender/open_telemetry.rb +2 -1
- data/lib/semantic_logger/appender/opensearch.rb +2 -1
- data/lib/semantic_logger/appender/rabbitmq.rb +4 -3
- data/lib/semantic_logger/appender/sentry.rb +5 -0
- data/lib/semantic_logger/appender/syslog.rb +6 -3
- data/lib/semantic_logger/appender/tcp.rb +4 -3
- data/lib/semantic_logger/appender/wrapper.rb +2 -1
- data/lib/semantic_logger/appender.rb +6 -3
- data/lib/semantic_logger/appenders.rb +2 -1
- data/lib/semantic_logger/base.rb +0 -2
- data/lib/semantic_logger/formatters/color.rb +5 -2
- data/lib/semantic_logger/formatters/default.rb +7 -2
- data/lib/semantic_logger/formatters/logfmt.rb +4 -2
- data/lib/semantic_logger/formatters/pattern.rb +3 -1
- data/lib/semantic_logger/formatters/syslog.rb +2 -1
- data/lib/semantic_logger/formatters/syslog_cee.rb +2 -1
- data/lib/semantic_logger/metric/signalfx.rb +4 -2
- data/lib/semantic_logger/queue_processor.rb +5 -2
- data/lib/semantic_logger/reporters/minitest.rb +4 -4
- data/lib/semantic_logger/test/capture_log_events.rb +1 -1
- data/lib/semantic_logger/version.rb +1 -1
- metadata +14 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f18701015c0b556099814aff1b79e251df13f556c103a8366b58357d9d10fd8c
|
|
4
|
+
data.tar.gz: cf18f050902946ac09a065a52a6f4cee9841f5c250ef30162d39347977e071d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|