hubbado-log 1.5.0 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84cb20198caa8a5346b0a4d50c702b57e77f53cedc2f90e221c05a90985260a0
4
- data.tar.gz: 1134a1d43db622684732e5550d76716c8be4dcb5ee0e23382c8aaaedf0743851
3
+ metadata.gz: 583b7b7da5c4402a941dc3383961ac3af980078123d1ea2618ee89dced2d431b
4
+ data.tar.gz: 8268fb5b20c2ba01a9b402cf6a6ea950139bb34e5573bf666e38a6689187acec
5
5
  SHA512:
6
- metadata.gz: ac99e3a37b28025006034cd5032a350db0c3e0bf63ee1c504c4af81a589879a5d1e3dd72180592b5d1632471b0f00efe9353cc5dfc6f1b040bf6855370a1f537
7
- data.tar.gz: 97fe613dccb476915983d4836ae3736e3fae54168872b20106a71da9e1ec70c40994a7ba79d2931f8fdfc5dd4b36defdc55bf52823de6194b2e552c2ff84f49c
6
+ metadata.gz: a8893a6eb651bfbecef02fb88d0b0c51c468f77ae946ddf57522858e5a82fe4aea7d3468d80e1023fd15fc0b11973e0f8bc6104eb113d774a29d941b0b5010f4
7
+ data.tar.gz: d448240a1a0f37894a8e79d1558406cf16d327c8d99a7b5222ba29dd1285dc35680c2311b8d31ea33af4d07e32ac2c905225562953c978611d91f55ff8a7fb4e
data/ChangeLog.md CHANGED
@@ -4,6 +4,115 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ # [2.0.0 - 2026-08-29]
8
+ ## Fixed
9
+ - `LOG_TAGS` and `LOG_LEVEL` no longer decide whether a failure is reported.
10
+ Both were applied in `Logger`, above the handler fan-out, so an operator
11
+ narrowing the log to the step they were debugging, or raising the level to cut
12
+ what an unattended log costs to keep, also stopped `NotifyRollbar` filing the
13
+ incident. A crash in any other step went unreported.
14
+
15
+ `StderrLogger` and `RailsLogger` now ask `Display` for themselves before
16
+ writing. `NotifyRollbar` does not ask, so it is reached whatever the settings
17
+ say, and declines anything below `warn` on its own as before.
18
+
19
+ - A failure written with a String severity is given a stacktrace, as one written
20
+ with a symbol always was. `#log` validates the severity as a symbol but
21
+ compared it raw when deciding whether to synthesise a caller stack, so
22
+ `Hubbado::Log.log('error', msg)` was accepted, logged, and arrived with no
23
+ stack under it.
24
+
25
+ Latent until now — every call site across Hubbado names its severity as a
26
+ symbol — and worth fixing here because such a message could previously be
27
+ filtered away entirely. Now that every warning and error reaches Rollbar, it
28
+ would instead be filed as an item nobody can act on.
29
+
30
+ ## Added
31
+ - `Hubbado::Log::Display`, which answers whether the operator asked to be shown
32
+ a message. A handler that writes where a person reads asks it; one that
33
+ reports an incident does not.
34
+
35
+ ```ruby
36
+ class MyHandler < Hubbado::Log::LogHandler
37
+ def log(subject, severity, message, data = nil, stacktrace = nil, tags = nil)
38
+ return unless Hubbado::Log::Display.shows?(severity, tags)
39
+
40
+ ...
41
+ end
42
+ end
43
+ ```
44
+
45
+ A printing handler that forgets to ask prints everything, whatever the
46
+ operator set.
47
+
48
+ - `LogHandler#traces?(severity, tags)`, asked before a stacktrace is made.
49
+ `Kernel.caller` costs more than the rest of a log call put together — around
50
+ 19µs at a Rails stack depth against 0.8µs — and it was being paid for messages
51
+ no handler would use.
52
+
53
+ It answers `true`, so a handler written before this existed is asked nothing
54
+ and keeps the stacktrace it always had. `StderrLogger` answers `false` below
55
+ `error`, which is where it stops printing one; `NotifyRollbar` answers `false`
56
+ below `warn`, which is where it stops reporting.
57
+
58
+ | | Was | Is now |
59
+ |---|---|---|
60
+ | an error the tag list left out | 29.2µs | 1.9µs |
61
+ | a warning on a terminal | 19µs | 1.6µs |
62
+
63
+ The second of those is older than this release: the gem synthesised a
64
+ stacktrace for every `warn`, and `StderrLogger` has always declined to print
65
+ one below `error`.
66
+
67
+ - The logger substitute names a line by its message and its tags, not only by
68
+ its severity. `logged?`, `messages` and `logged` all take the same criteria.
69
+
70
+ ```ruby
71
+ assert logger.logged?(:info, message: /handed back/, tags: %i[rescoring sweep])
72
+ ```
73
+
74
+ Named together rather than one at a time, because a run writes several lines
75
+ and a message and a tag list asserted apart can each be true of a different
76
+ one. Before this, a spec asking about a line's tags reached past the
77
+ substitute into `invocations.first.arguments.fetch(:tag)` — which bound the
78
+ assertion to which keyword the call site happened to use, so rewriting
79
+ `tag: :claim` to `tags: [:claim]` broke a spec without changing any behaviour.
80
+
81
+ `message:` matches a String in full or a Regexp in part. `tags:` takes one
82
+ symbol or a list, reads both keywords as the logger does, and names every tag
83
+ the line carries and no others, in any order.
84
+
85
+ Entries answered by `logged` carry `tags` alongside `severity`, `message` and
86
+ `data`.
87
+
88
+ ## Breaking
89
+ - **`LOG_LEVEL` and `LOG_TAGS` no longer hold Rollbar volume down.** That is the
90
+ point of the fix above, and it is a change to what an operator can do: a
91
+ process running `LOG_LEVEL=error`, or a narrowed list, to keep the incident
92
+ count low loses that lever on upgrade. Every `warn` and above now files,
93
+ whatever the settings say, and `NotifyRollbar`'s own `warn` floor is the only
94
+ one left. Rollbar's own rate limiting is where volume is held now.
95
+ - **`LogHandler#log` takes a sixth argument**, the tags the message was written
96
+ with, as symbols. A handler defining five parameters raises when called — and
97
+ raises from inside the logging call, so it takes down the operation being
98
+ logged rather than only the log line. A keyword with a default would not have
99
+ softened this: Ruby folds a trailing hash into a sixth positional for a method
100
+ that declares no keywords, and it raises identically.
101
+ - **The per-logger `level:` and `tags:` overrides are gone.** `Logger.new` takes
102
+ a subject and its handlers, and nothing else; a handler reads the process's
103
+ configuration. Nothing across Hubbado set either — only this gem's own
104
+ controls did. This is a deliberate divergence from Eventide's log gem, which
105
+ keeps that seam: restoring it would mean putting the effective level and list
106
+ back onto the handler contract that this release widened.
107
+ - **`Controls::LogHandler.attach` and `.logger` no longer take `level:` or
108
+ `tags:`.** The control records rather than displays, so nothing filters what a
109
+ spec attaching one can read.
110
+
111
+ ## Changed
112
+ - A message tagged `:*` now means only "display this whatever the list says",
113
+ which is what it means in Eventide's log gem. It is no longer what keeps an
114
+ incident alive, because nothing can silence one.
115
+
7
116
  # [1.5.0 - 2026-08-16]
8
117
  ## Added
9
118
  - Three handlers, replacing eight hand-rolled copies across four projects and
data/README.md CHANGED
@@ -65,7 +65,9 @@ the application's — a Rails logger, a Rollbar token. Write one by subclassing
65
65
 
66
66
  ```ruby
67
67
  class MyHandler < Hubbado::Log::LogHandler
68
- def log(subject, severity, message, data = nil, stacktrace = nil)
68
+ def log(subject, severity, message, data = nil, stacktrace = nil, tags = nil)
69
+ return unless Hubbado::Log::Display.shows?(severity, tags)
70
+
69
71
  ...
70
72
  end
71
73
  end
@@ -73,7 +75,21 @@ end
73
75
 
74
76
  `data` is whatever the call site passed as the second argument, and is an `Exception` when it
75
77
  logged one. `stacktrace` is the exception's `full_message` in that case, and otherwise the caller
76
- stack, synthesised for `warn`, `error`, `fatal` and `unknown` only.
78
+ stack, synthesised for `warn`, `error`, `fatal` and `unknown` only. `tags` is what the message was
79
+ tagged with, as symbols.
80
+
81
+ **A handler that writes where a person reads asks `Display.shows?` first**, and one that reports
82
+ an incident does not — see [Display and reporting](#display-and-reporting).
83
+
84
+ A handler is also asked whether it would use a stacktrace, before one is made:
85
+
86
+ ```ruby
87
+ def traces?(severity, tags = nil) = Hubbado::Log::Display.shows?(severity, tags)
88
+ ```
89
+
90
+ It answers `true` unless you say otherwise, so a handler that ignores it keeps what it always had.
91
+ Answering honestly is worth it — `Kernel.caller` costs more than the rest of a log call put
92
+ together, and a message no handler traces should not pay for it.
77
93
 
78
94
  ### `Hubbado::Log::StderrLogger`
79
95
 
@@ -180,7 +196,8 @@ back.
180
196
 
181
197
  ## Level
182
198
 
183
- A message below the level reaches no handler.
199
+ A message below the level is not displayed. It still reaches a handler that reports — see
200
+ [Display and reporting](#display-and-reporting).
184
201
 
185
202
  ```ruby
186
203
  Hubbado::Log.configuration do |config|
@@ -206,9 +223,6 @@ Levels, lowest first:
206
223
  | `error` | Message logged just prior to raising an error |
207
224
  | `fatal` | Message recorded, when possible, as the process is terminating due to an error |
208
225
 
209
- A single logger can name its own with
210
- `Hubbado::Log::Logger.new(subject, handlers, level: :debug)`.
211
-
212
226
  `LOG_LEVEL` is shared with Eventide's log gem, which writes names this gem does not
213
227
  know. A name that is not one of `debug`, `info`, `warn`, `error`, `fatal` or
214
228
  `unknown` leaves the level at `info` rather than raising.
@@ -228,7 +242,7 @@ logger.trace('Row read', tag: :data)
228
242
 
229
243
  ### `LOG_TAGS`
230
244
 
231
- Which tagged messages are written is decided by `LOG_TAGS`, a comma-separated list:
245
+ Which tagged messages are displayed is decided by `LOG_TAGS`, a comma-separated list:
232
246
 
233
247
  $ LOG_TAGS='_untagged,-data,billing,invoicing' ./my-command
234
248
 
@@ -247,20 +261,14 @@ Eventide's behaviour, kept deliberately so one `LOG_TAGS` means the same thing t
247
261
 
248
262
  Tags compose with the level rather than replacing it: both filters have to pass, so a tag
249
263
  cannot raise a message above the level and the level cannot rescue one the list leaves out.
250
-
251
- A single logger can name its own list, as it can name its own level, so one component can be
252
- read without turning up everything around it:
253
-
254
- ```ruby
255
- Hubbado::Log::Logger.new(subject, handlers, level: :trace, tags: '_all')
256
- ```
264
+ Both decide what is displayed, and neither decides what is reported.
257
265
 
258
266
  The syntax and its behaviour are Eventide's log gem, copied deliberately so that a string an
259
267
  operator writes means the same thing in both codebases. Two consequences of that are worth
260
268
  knowing before adopting tags:
261
269
 
262
- - **`LOG_TAGS` is an allow-list.** A tagged message is written only if the list names it. Adding
263
- a tag to a call site therefore *silences* that message everywhere `LOG_TAGS` has not been
270
+ - **`LOG_TAGS` is an allow-list.** A tagged message is displayed only if the list names it. Adding
271
+ a tag to a call site therefore *hides* that message everywhere `LOG_TAGS` has not been
264
272
  updated — cron, CI and production included. Ship the variable with the tag.
265
273
  - **There is no way to mute one concern and keep the rest.** `-name` subtracts only from
266
274
  messages an include has already matched, and `_all` is answered before any exclusion, so
@@ -271,6 +279,27 @@ knowing before adopting tags:
271
279
  list decides for both, and an application whose messages are all untagged goes silent unless the
272
280
  list contains `_untagged`.
273
281
 
282
+ ## Display and reporting
283
+
284
+ `LOG_LEVEL` and `LOG_TAGS` decide what is displayed, not what is reported: an operator narrowing
285
+ to the step they are debugging is asking to be shown less, not for a crash elsewhere to go
286
+ unreported.
287
+
288
+ The logger fans every message out to every handler. One that writes where a person reads asks
289
+ first; one that reports does not:
290
+
291
+ ```ruby
292
+ Hubbado::Log::Display.shows?(severity, tags) # => true if the level and the list both admit it
293
+ ```
294
+
295
+ `StderrLogger` and `RailsLogger` ask. `NotifyRollbar` does not, and declines below `warn` itself.
296
+
297
+ **A printing handler that forgets to ask prints everything.** That is the thing to remember when
298
+ writing one.
299
+
300
+ Tagging a `warn` no longer hides it from Rollbar, only from the terminal, and `:*` now means
301
+ "always display" — what it means in Eventide's log gem.
302
+
274
303
  ## Reading back what a class logged
275
304
 
276
305
  A spec assigns a substitute where the class's logger goes, and then asks what the class said:
@@ -299,23 +328,40 @@ assert logger.logged?(:error)
299
328
  It records what it was told rather than writing, so no handler is involved and neither the
300
329
  configured level nor `LOG_TAGS` decides what can be read back.
301
330
 
302
- Three questions, each taking an optional severity:
331
+ Three questions, each taking the same criteria:
303
332
 
304
333
  | Call | Answers |
305
334
  |---|---|
306
- | `logged?` / `logged?(:warn)` | whether anything was written, at all or at that severity |
307
- | `messages` / `messages(:warn)` | what it said — the message strings, in order |
308
- | `logged` / `logged(:warn)` | everything about what it said |
335
+ | `logged?` | whether a line matching was written |
336
+ | `messages` | what those lines said — the message strings, in order |
337
+ | `logged` | everything about them |
338
+
339
+ | Criterion | Names a line by |
340
+ |---|---|
341
+ | a severity, positionally | `logged?(:warn)` |
342
+ | `message:` | a String matching in full, or a Regexp matching part |
343
+ | `tags:` | the tags it carries, compared as a set — one symbol or a list |
344
+
345
+ **Name them together rather than one at a time.** A run writes several lines, and a message and a
346
+ tag list asserted apart can each be true of a different one:
347
+
348
+ ```ruby
349
+ assert logger.logged?(:info, message: /handed back/, tags: %i[rescoring sweep])
350
+ ```
351
+
352
+ `tags:` names every tag the line carries and no others, in any order. It reads both keywords as
353
+ the logger does, so a call site rewritten from `tag: :claim` to `tags: [:claim]` — a change with
354
+ no behaviour in it — does not break the spec.
309
355
 
310
- `logged` answers with entries carrying `severity`, `message` and `data`, for the assertion that
311
- needs more than the text:
356
+ `logged` answers with entries carrying `severity`, `message`, `data` and `tags`, for the assertion
357
+ that needs more than a yes:
312
358
 
313
359
  ```ruby
314
360
  assert logger.logged(:error).first.data.equal?(exception)
315
361
  ```
316
362
 
317
- `messages` and `logged?` are both derived from `logged`, so the three cannot disagree about what
318
- counts as written at a severity.
363
+ `messages` and `logged?` are both derived from `logged`, so the three cannot disagree about which
364
+ lines are being talked about.
319
365
 
320
366
  A severity reaches a logger two ways — `logger.warn('…')` names it as the method,
321
367
  `logger.log(:warn, '…')` as an argument — and both answer the same question, compared as symbols.
data/hubbado-log.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "hubbado-log"
3
- s.version = "1.5.0"
3
+ s.version = "2.0.0"
4
4
  s.summary = "Lightweight pluggable logging system"
5
5
 
6
6
  s.authors = ["Hubbado Devs"]
@@ -8,10 +8,11 @@ module Hubbado
8
8
  @messages ||= []
9
9
  end
10
10
 
11
- # Replaces a class's logger with one writing here, keeping its subject. Neither the
12
- # configured level nor the configured tags decide what a spec attaching one of these can
13
- # read: it is asking what the class said, and the process's filters are not its subject.
14
- def self.attach(instance, level: :trace, tags: Tags::ALL)
11
+ # Replaces a class's logger with one writing here, keeping its subject. This handler
12
+ # records rather than displays, so the operator's settings do not decide what a spec
13
+ # attaching one can read: it is asking what the class said, and the process's filters are
14
+ # not its subject.
15
+ def self.attach(instance)
15
16
  logger = instance.logger
16
17
 
17
18
  if logger.nil?
@@ -20,24 +21,28 @@ module Hubbado
20
21
  end
21
22
 
22
23
  new.tap do |handler|
23
- instance.logger = Log::Logger.new(logger.subject, [handler], level: level, tags: tags)
24
+ instance.logger = Log::Logger.new(logger.subject, [handler])
24
25
  end
25
26
  end
26
27
 
27
28
  # For a class handed a logger rather than carrying one, and for a spec that wants both.
28
- def self.logger(subject = Subject.example, level: :trace, tags: Tags::ALL)
29
+ def self.logger(subject = nil)
30
+ subject ||= Subject.example
29
31
  handler = new
30
32
 
31
- [handler, Log::Logger.new(subject, [handler], level: level, tags: tags)]
33
+ [handler, Log::Logger.new(subject, [handler])]
32
34
  end
33
35
 
34
- def log(subject, severity, message, data = nil, stacktrace = nil)
36
+ def log(subject, severity, message, data = nil, stacktrace = nil, tags = nil)
37
+ tags ||= []
38
+
35
39
  messages << {
36
40
  subject: subject,
37
41
  severity: severity,
38
42
  message: message,
39
43
  data: data,
40
- stacktrace: stacktrace
44
+ stacktrace: stacktrace,
45
+ tags: tags
41
46
  }
42
47
  end
43
48
 
@@ -57,7 +62,7 @@ module Hubbado
57
62
 
58
63
  # The most recent message. Derived rather than assigned alongside `messages`, so the two
59
64
  # cannot disagree about which message is the latest.
60
- %i[subject severity message data stacktrace].each do |field|
65
+ %i[subject severity message data stacktrace tags].each do |field|
61
66
  define_method(field) { messages.last&.fetch(field) }
62
67
  end
63
68
  end
@@ -0,0 +1,17 @@
1
+ module Hubbado
2
+ class Log
3
+ # Whether the operator asked to be shown a message. A handler that prints asks this; one that
4
+ # reports an incident does not, so narrowing a log cannot silence a failure.
5
+ class Display
6
+ # Both have to pass: a tag cannot raise a message above the level, nor the level rescue one
7
+ # the list leaves out.
8
+ def self.shows?(severity, tags = nil)
9
+ config = Log.config
10
+
11
+ return false if SEVERITIES.fetch(severity.to_sym) < SEVERITIES.fetch(config.level)
12
+
13
+ config.tags.write?(tags)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,7 +1,12 @@
1
1
  module Hubbado
2
2
  class Log
3
3
  class LogHandler
4
- def log(_subject, _severity, _msg, _data = nil, _stacktrace = nil)
4
+ # Whether this handler would use a synthesised stacktrace. Answered before one is made,
5
+ # because Kernel.caller costs more than the rest of a log call put together. True here, so a
6
+ # handler written before this existed is asked nothing and keeps what it always had.
7
+ def traces?(_severity, _tags = nil) = true
8
+
9
+ def log(_subject, _severity, _msg, _data = nil, _stacktrace = nil, _tags = nil)
5
10
  raise NotImplementedError
6
11
  end
7
12
  end
@@ -4,46 +4,65 @@ module Hubbado
4
4
  # What a logger was told rather than what it wrote. Extended onto a mimic of Logger, so a
5
5
  # class under test is handed something that answers as a logger and keeps what it was given.
6
6
  module Substitute
7
- Entry = Data.define(:severity, :message, :data)
8
-
9
- # Everything about what a class said, in order. Named with a severity, only what it said
10
- # at that one.
11
- #
12
- # A severity reaches a logger two ways: as the method, from the generated severity
13
- # methods, or as #log's first argument. Both are compared as symbols, because #log takes
14
- # a String as readily and passes on what it was given.
15
- def logged(severity = nil)
7
+ # One line, as the logger read it rather than as the call site typed it.
8
+ Entry = Data.define(:severity, :message, :data, :tags) do
9
+ # As a symbol, because #log takes a String as readily.
10
+ def at?(name) = severity == name.to_s.to_sym
11
+
12
+ # A String names the line in full; a Regexp names enough of it to tell it from the
13
+ # others, without writing a record id into the spec.
14
+ def says?(pattern)
15
+ return pattern.match?(message.to_s) if pattern.is_a?(Regexp)
16
+
17
+ message == pattern
18
+ end
19
+
20
+ # Every tag the line carries, and no others. Order is not compared: a call site writes
21
+ # them in whatever order reads well, and the allow-list never sees one either.
22
+ def tagged?(names) = tags.uniq.sort == names.uniq.sort
23
+ end
24
+
25
+ # Everything about what a class said, in order, narrowed by whichever criteria a spec
26
+ # names. Named together, because a run writes several lines and a message and a tag list
27
+ # asserted apart can each be true of a different one.
28
+ def logged(severity = nil, message: nil, tags: nil)
16
29
  entries = invocations.map { |invocation| entry(invocation) }
17
30
 
18
- return entries if severity.nil?
31
+ named = symbols(tags)
19
32
 
20
- entries.select { |entry| entry.severity == severity.to_s.to_sym }
33
+ entries = entries.select { |entry| entry.at?(severity) } unless severity.nil?
34
+ entries = entries.select { |entry| entry.says?(message) } unless message.nil?
35
+ entries = entries.select { |entry| entry.tagged?(named) } unless tags.nil?
36
+
37
+ entries
21
38
  end
22
39
 
23
40
  # What a class said, where #logged is everything about it.
24
- def messages(severity = nil) = logged(severity).map(&:message)
41
+ def messages(...) = logged(...).map(&:message)
25
42
 
26
- # Whether, where #logged and #messages ask what. Without a severity, whether anything was
27
- # written at all.
28
- def logged?(severity = nil) = !logged(severity).empty?
43
+ # Whether, where the two above ask what. Named nothing, whether anything was written.
44
+ def logged?(...) = !logged(...).empty?
29
45
 
30
46
  private
31
47
 
48
+ # A severity arrives as the method name, from the generated severity methods, or as #log's
49
+ # first argument. Both tags keywords land in one list, as the logger lands them, so a spec
50
+ # does not break when a call site moves between the two.
32
51
  def entry(invocation)
33
52
  arguments = invocation.arguments
34
53
 
54
+ severity = arguments.fetch(:severity, invocation.method_name)
55
+
35
56
  Entry.new(
36
- severity: severity(invocation).to_s.to_sym,
57
+ severity: severity.to_s.to_sym,
37
58
  message: arguments[:msg],
38
- data: arguments[:data]
59
+ data: arguments[:data],
60
+ tags: symbols(arguments[:tags]) + symbols(arguments[:tag])
39
61
  )
40
62
  end
41
63
 
42
- def severity(invocation)
43
- return invocation.arguments.fetch(:severity) if invocation.method_name == :log
44
-
45
- invocation.method_name
46
- end
64
+ # One name or a list of them, however a call site or a spec wrote it.
65
+ def symbols(names) = Array(names).map { |name| name.to_s.to_sym }
47
66
  end
48
67
  end
49
68
  end
@@ -4,31 +4,16 @@ module Hubbado
4
4
  attr_accessor :log_handlers
5
5
  attr_accessor :subject
6
6
 
7
- def initialize(subject, log_handlers = [], level: nil, tags: nil)
7
+ def initialize(subject, log_handlers = nil)
8
8
  self.subject = subject
9
9
  self.log_handlers = Array(log_handlers)
10
- @level = level
11
- @tags = tags
12
10
  end
13
11
 
14
- # A logger built without one follows the configuration, which is every logger the gem
15
- # builds itself: `Log.configure` names neither, so a class using the Dependency module
16
- # takes whatever the process was configured for.
17
- def level = @level || Log.config.level
18
-
19
- # Named per logger as well as per process, as the level is and as Eventide's log gem has
20
- # it, so one component can be read without turning up everything around it.
21
- def tags = @tags.nil? ? Log.config.tags : Tags.parse(@tags)
22
-
23
12
  def log(severity, msg, data = nil, tag: nil, tags: nil)
24
13
  unless SEVERITIES.keys.include? severity.to_sym
25
14
  raise ArgumentError, "Unknown serverity #{severity}"
26
15
  end
27
16
 
28
- # Read after the severity is checked, never before: the level decides what is printed,
29
- # not what may be said, so quietening a logger must not turn a typo into silence.
30
- return if SEVERITIES.fetch(severity.to_sym) < SEVERITIES.fetch(level)
31
-
32
17
  # Singular and plural are both accepted and both kept, following Eventide's log gem,
33
18
  # where real call sites use either and occasionally hand an array to the singular one.
34
19
  #
@@ -36,20 +21,14 @@ module Hubbado
36
21
  # match nothing and its message would go missing with nothing said about it.
37
22
  message_tags = (Array(tags) + Array(tag)).map { |name| name.to_s.to_sym }
38
23
 
39
- # Both filters have to pass. A tag cannot raise a message above the level, and the level
40
- # cannot rescue one the list leaves out.
41
- #
42
- # `self.` because the `tags:` keyword above shadows the reader.
43
- return unless self.tags.write?(message_tags)
44
-
45
24
  stacktrace = if data.is_a?(Exception)
46
25
  data.full_message
47
- elsif STACKTRACE_SEVERITIES.include?(severity)
26
+ elsif traced?(severity, message_tags)
48
27
  format_stacktrace Kernel.caller
49
28
  end
50
29
 
51
30
  log_handlers.each do |handler|
52
- handler.log(subject, severity, msg, data, stacktrace)
31
+ handler.log(subject, severity, msg, data, stacktrace, message_tags)
53
32
  end
54
33
  end
55
34
 
@@ -61,6 +40,15 @@ module Hubbado
61
40
 
62
41
  private
63
42
 
43
+ # Kernel.caller is the most expensive thing in a log call, so it is not paid for a message
44
+ # every handler would drop — or for a warning, which this gem synthesises one for and the
45
+ # terminal then declines to print.
46
+ def traced?(severity, message_tags)
47
+ return false unless STACKTRACE_SEVERITIES.include?(severity.to_sym)
48
+
49
+ log_handlers.any? { |handler| handler.traces?(severity, message_tags) }
50
+ end
51
+
64
52
  def format_stacktrace(stacktrace)
65
53
  stacktrace.join("\n")
66
54
  end
@@ -17,7 +17,9 @@ module Hubbado
17
17
  @notifier = notifier
18
18
  end
19
19
 
20
- def log(subject, severity, message, data = nil, stacktrace = nil)
20
+ def traces?(severity, _tags = nil) = LEVELS.key?(severity.to_sym)
21
+
22
+ def log(subject, severity, message, data = nil, stacktrace = nil, _tags = nil)
21
23
  level = LEVELS[severity.to_sym]
22
24
  return if level.nil?
23
25
 
@@ -14,7 +14,11 @@ module Hubbado
14
14
  @rails_logger = rails_logger
15
15
  end
16
16
 
17
- def log(subject, severity, message, data = nil, stacktrace = nil)
17
+ def traces?(severity, tags = nil) = Display.shows?(severity, tags)
18
+
19
+ def log(subject, severity, message, data = nil, stacktrace = nil, tags = nil)
20
+ return unless Display.shows?(severity, tags)
21
+
18
22
  rails_severity = RAILS_SEVERITIES.fetch(severity.to_sym, severity)
19
23
 
20
24
  rails_logger.send(rails_severity, "#{subject}: #{message}")
@@ -19,7 +19,14 @@ module Hubbado
19
19
  # One write, because a line and the detail under it belong together. Three writes let a
20
20
  # second thread put its own line between them, and a stacktrace filed under the wrong
21
21
  # message is worse than no stacktrace.
22
- def log(subject, severity, message, data = nil, stacktrace = nil)
22
+ # Only a failure earns one on a terminal, and only if the operator is being shown it.
23
+ def traces?(severity, tags = nil)
24
+ FAILURE_SEVERITIES.include?(severity.to_sym) && Display.shows?(severity, tags)
25
+ end
26
+
27
+ def log(subject, severity, message, data = nil, stacktrace = nil, tags = nil)
28
+ return unless Display.shows?(severity, tags)
29
+
23
30
  lines = ["#{severity.to_s.upcase} #{subject}: #{message}"]
24
31
  lines << detail(data, stacktrace) unless data.nil?
25
32
  lines << stacktrace if print_stacktrace?(severity, data, stacktrace)
@@ -27,7 +27,7 @@ module Hubbado
27
27
 
28
28
  attr_reader :tags
29
29
 
30
- def initialize(tags = [])
30
+ def initialize(tags = nil)
31
31
  @tags = Array(tags).map { |tag| tag.to_s.to_sym }
32
32
  end
33
33
 
data/lib/hubbado/log.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'dependency'
2
2
  require "hubbado/log/tags"
3
+ require "hubbado/log/display"
3
4
  require "hubbado/log/configuration"
4
5
  require "hubbado/log/log"
5
6
  require "hubbado/log/logger"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hubbado-log
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hubbado Devs
@@ -116,6 +116,7 @@ files:
116
116
  - lib/hubbado/log/controls/rollbar.rb
117
117
  - lib/hubbado/log/controls/stacktrace.rb
118
118
  - lib/hubbado/log/controls/subject.rb
119
+ - lib/hubbado/log/display.rb
119
120
  - lib/hubbado/log/log.rb
120
121
  - lib/hubbado/log/log_handler.rb
121
122
  - lib/hubbado/log/logger.rb