hubbado-log 1.4.1 → 1.5.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/ChangeLog.md +51 -0
- data/README.md +131 -1
- data/hubbado-log.gemspec +5 -1
- data/lib/hubbado/log/controls/rails_logger.rb +24 -0
- data/lib/hubbado/log/controls/receiver.rb +19 -0
- data/lib/hubbado/log/controls/rollbar.rb +50 -0
- data/lib/hubbado/log/controls/stacktrace.rb +14 -0
- data/lib/hubbado/log/controls.rb +6 -0
- data/lib/hubbado/log/notify_rollbar.rb +70 -0
- data/lib/hubbado/log/rails_logger.rb +46 -0
- data/lib/hubbado/log/stderr_logger.rb +54 -0
- metadata +22 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 84cb20198caa8a5346b0a4d50c702b57e77f53cedc2f90e221c05a90985260a0
|
|
4
|
+
data.tar.gz: 1134a1d43db622684732e5550d76716c8be4dcb5ee0e23382c8aaaedf0743851
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ac99e3a37b28025006034cd5032a350db0c3e0bf63ee1c504c4af81a589879a5d1e3dd72180592b5d1632471b0f00efe9353cc5dfc6f1b040bf6855370a1f537
|
|
7
|
+
data.tar.gz: 97fe613dccb476915983d4836ae3736e3fae54168872b20106a71da9e1ec70c40994a7ba79d2931f8fdfc5dd4b36defdc55bf52823de6194b2e552c2ff84f49c
|
data/ChangeLog.md
CHANGED
|
@@ -4,6 +4,57 @@ 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
|
+
# [1.5.0 - 2026-08-16]
|
|
8
|
+
## Added
|
|
9
|
+
- Three handlers, replacing eight hand-rolled copies across four projects and
|
|
10
|
+
two applications. Each is behind its own require; `require "hubbado/log"`
|
|
11
|
+
loads none of them.
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
require "hubbado/log/stderr_logger"
|
|
15
|
+
require "hubbado/log/notify_rollbar"
|
|
16
|
+
|
|
17
|
+
Hubbado::Log.configuration do |config|
|
|
18
|
+
config.loggers = [Hubbado::Log::StderrLogger, Hubbado::Log::NotifyRollbar]
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Handler | Writes to | Needs |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `StderrLogger` | `$stderr` | nothing |
|
|
25
|
+
| `RailsLogger` | `Rails.logger` | Rails, already loaded wherever a handler is registered |
|
|
26
|
+
| `NotifyRollbar` | `Rollbar` | `rollbar` in the consumer's own Gemfile |
|
|
27
|
+
|
|
28
|
+
Neither Rails nor Rollbar is a dependency of this gem, so installing it
|
|
29
|
+
installs neither. `notify_rollbar` requires `rollbar` at the top of the file,
|
|
30
|
+
making an absent gem a `LoadError` at boot rather than a `NameError` raised
|
|
31
|
+
while a failure is being reported.
|
|
32
|
+
|
|
33
|
+
Every copy discarded the `stacktrace` argument, and the Rollbar ones
|
|
34
|
+
discarded more:
|
|
35
|
+
|
|
36
|
+
| | Was | Is now |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `StderrLogger`, an `error` carrying no exception | no stacktrace | prints it. A `warn` stays one line, and an exception's backtrace is never printed twice |
|
|
39
|
+
| `RailsLogger`, a `trace` line | `NoMethodError` in one of the two copies | written as `debug` |
|
|
40
|
+
| `NotifyRollbar`, the subject | dropped | a `subject` key, merged last so a line cannot claim another class |
|
|
41
|
+
| `NotifyRollbar`, a stacktrace with no exception | dropped | a `stacktrace` key |
|
|
42
|
+
| `NotifyRollbar`, data that is not a hash or exception | dropped | a `data` key holding its `inspect` |
|
|
43
|
+
|
|
44
|
+
Rollbar keeps the last hash it is handed and ignores anything that is not a
|
|
45
|
+
String, an Exception or a Hash, so `NotifyRollbar` merges everything into one
|
|
46
|
+
hash — built fresh per call, because Rollbar deletes a key from the hash it
|
|
47
|
+
is given.
|
|
48
|
+
|
|
49
|
+
- Controls for what a handler spec stands in for: `Controls::Rollbar`,
|
|
50
|
+
`Controls::RailsLogger`, `Controls::Receiver` and `Controls::Stacktrace`.
|
|
51
|
+
|
|
52
|
+
`Controls::Rollbar` reads a notification back the way Rollbar reads its
|
|
53
|
+
arguments, keeping the last hash — so a handler sending two would pass
|
|
54
|
+
against a merging control and lose data against the real thing.
|
|
55
|
+
`Controls::RailsLogger` does not answer to `trace`, so a handler passing it
|
|
56
|
+
straight through raises here as it would in Rails.
|
|
57
|
+
|
|
7
58
|
# [1.4.1 - 2026-08-16]
|
|
8
59
|
## Fixed
|
|
9
60
|
- A subclass of `Hubbado::Log` can be used as a dependency. It answered `nil`
|
data/README.md
CHANGED
|
@@ -48,13 +48,143 @@ end
|
|
|
48
48
|
Either way the logger writes through the handlers the process was configured with, at the level
|
|
49
49
|
and tags it was configured for.
|
|
50
50
|
|
|
51
|
+
## Handlers
|
|
52
|
+
|
|
53
|
+
A handler is what a log line is finally written *to*. The process names them, and the log system
|
|
54
|
+
builds each one itself with no arguments:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
Hubbado::Log.configuration do |config|
|
|
58
|
+
config.loggers = [Hubbado::Log::StderrLogger]
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Handlers mostly belong to the application rather than to this gem, because what they write to is
|
|
63
|
+
the application's — a Rails logger, a Rollbar token. Write one by subclassing
|
|
64
|
+
`Hubbado::Log::LogHandler` and implementing `log`:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
class MyHandler < Hubbado::Log::LogHandler
|
|
68
|
+
def log(subject, severity, message, data = nil, stacktrace = nil)
|
|
69
|
+
...
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`data` is whatever the call site passed as the second argument, and is an `Exception` when it
|
|
75
|
+
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.
|
|
77
|
+
|
|
78
|
+
### `Hubbado::Log::StderrLogger`
|
|
79
|
+
|
|
80
|
+
For a command-line tool, where the log is a person watching a run. It is not loaded by
|
|
81
|
+
`require 'hubbado/log'` — ask for it:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
require 'hubbado/log/stderr_logger'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
It prints the severity, the subject and the message on one line, and anything the line carried
|
|
88
|
+
below it:
|
|
89
|
+
|
|
90
|
+
WARN Scanning::Sweep: rec_1 skipped, no rate
|
|
91
|
+
ERROR Scanning::Sweep: rec_2 refused
|
|
92
|
+
/app/lib/scanning/sweep.rb:41:in 'post': HTTP 400 (RequestError)
|
|
93
|
+
from /app/lib/scanning/sweep.rb:12:in 'call'
|
|
94
|
+
|
|
95
|
+
A stacktrace prints at `error` and above, and only when the data is not an exception — an
|
|
96
|
+
exception already prints its own backtrace, so honouring both would print it twice. A `warn`
|
|
97
|
+
stays one line: it is a condition to examine rather than a failure to trace, and a sweep that
|
|
98
|
+
warns per row would otherwise bury itself in Ruby stack.
|
|
99
|
+
|
|
100
|
+
Pass `io:` to write somewhere else, which is mainly how a spec reads it back:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
Hubbado::Log::StderrLogger.new(io: StringIO.new)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `Hubbado::Log::RailsLogger`
|
|
107
|
+
|
|
108
|
+
Writes into a Rails application's own log.
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
require "hubbado/log/rails_logger"
|
|
112
|
+
|
|
113
|
+
Hubbado::Log.configuration do |config|
|
|
114
|
+
config.loggers = [Hubbado::Log::RailsLogger]
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Rails is not a dependency of this gem. The constant is read lazily, and the only place a handler
|
|
119
|
+
is ever registered is an environment file, where Rails is loaded by definition.
|
|
120
|
+
|
|
121
|
+
The subject and message go on one line, and the data and stacktrace on lines below it, all at the
|
|
122
|
+
severity of the line. An exception is one line rather than two — its backtrace already carries the
|
|
123
|
+
message that its `inspect` would repeat. `trace` is written as `debug`, because Rails' logger has
|
|
124
|
+
no method below it, and a handler passing `trace` straight through raises `NoMethodError` on the
|
|
125
|
+
first trace line it is handed.
|
|
126
|
+
|
|
127
|
+
Unlike the stderr handler, the stacktrace is written at every severity. A Rails log is read after
|
|
128
|
+
the fact, and by machine as often as by a person, so there is nothing to keep readable by
|
|
129
|
+
withholding frames.
|
|
130
|
+
|
|
131
|
+
Pass `rails_logger:` to write somewhere else, which is mainly how a spec reads it back. Left
|
|
132
|
+
unset, `Rails.logger` is read on every line rather than held, so a handler built before Rails
|
|
133
|
+
replaced its logger still writes to the current one.
|
|
134
|
+
|
|
135
|
+
### `Hubbado::Log::NotifyRollbar`
|
|
136
|
+
|
|
137
|
+
Forwards a line worth an incident to Rollbar: `warn` as a warning, `error`, `fatal` and
|
|
138
|
+
`unknown` as errors, and anything below a warning not at all.
|
|
139
|
+
|
|
140
|
+
**Rollbar is not a dependency of this gem.** It is a development dependency, so nothing about
|
|
141
|
+
installing `hubbado-log` installs Rollbar. A consumer that wants this handler puts `rollbar` in
|
|
142
|
+
its own Gemfile and requires the handler itself:
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
require "hubbado/log/notify_rollbar"
|
|
146
|
+
|
|
147
|
+
Hubbado::Log.configuration do |config|
|
|
148
|
+
config.loggers = [Hubbado::Log::StderrLogger, Hubbado::Log::NotifyRollbar]
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The require raises `LoadError` at boot if Rollbar is absent, which is deliberate — the
|
|
153
|
+
alternative is a `NameError` raised inside `log`, at the moment something is trying to report a
|
|
154
|
+
failure, replacing the error being reported with a worse one.
|
|
155
|
+
|
|
156
|
+
The handler configures nothing. Access token, environment and scrubbing are Rollbar's own
|
|
157
|
+
`Rollbar.configure`, which the application owns.
|
|
158
|
+
|
|
159
|
+
Everything the line carried travels as Rollbar's extra data, in **one** hash — Rollbar scans its
|
|
160
|
+
arguments by type and keeps the last hash it is given, so a second one would silently displace
|
|
161
|
+
the first:
|
|
162
|
+
|
|
163
|
+
| The line has | Reaches the item as |
|
|
164
|
+
|---|---|
|
|
165
|
+
| an exception as its data | the exception itself, which Rollbar groups and traces on |
|
|
166
|
+
| a hash as its data | those keys, stringified |
|
|
167
|
+
| anything else as its data | a `data` key holding its `inspect` |
|
|
168
|
+
| — | a `subject` key naming the class that logged it |
|
|
169
|
+
| a stacktrace, with no exception | a `stacktrace` key |
|
|
170
|
+
|
|
171
|
+
The handler's own keys are merged last, so a line cannot tell an item it came from a different
|
|
172
|
+
class. A stacktrace is sent only when there is no exception, because Rollbar reads the backtrace
|
|
173
|
+
off an exception itself.
|
|
174
|
+
|
|
175
|
+
The message is sent as a String whatever it was — Rollbar matches its title by type, so anything
|
|
176
|
+
else would leave the item with no title at all.
|
|
177
|
+
|
|
178
|
+
Pass `notifier:` to record what was sent rather than send it, which is mainly how a spec reads it
|
|
179
|
+
back.
|
|
180
|
+
|
|
51
181
|
## Level
|
|
52
182
|
|
|
53
183
|
A message below the level reaches no handler.
|
|
54
184
|
|
|
55
185
|
```ruby
|
|
56
186
|
Hubbado::Log.configuration do |config|
|
|
57
|
-
config.loggers = [
|
|
187
|
+
config.loggers = [Hubbado::Log::StderrLogger]
|
|
58
188
|
config.level = :debug
|
|
59
189
|
end
|
|
60
190
|
```
|
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.
|
|
3
|
+
s.version = "1.5.0"
|
|
4
4
|
s.summary = "Lightweight pluggable logging system"
|
|
5
5
|
|
|
6
6
|
s.authors = ["Hubbado Devs"]
|
|
@@ -30,4 +30,8 @@ Gem::Specification.new do |s|
|
|
|
30
30
|
s.add_development_dependency "debug"
|
|
31
31
|
s.add_development_dependency "hubbado-style"
|
|
32
32
|
s.add_development_dependency "test_bench"
|
|
33
|
+
|
|
34
|
+
# Development only, deliberately. `hubbado/log/notify_rollbar` requires it, and nothing else
|
|
35
|
+
# in the gem does — so a consumer that never asks for that handler never installs Rollbar.
|
|
36
|
+
s.add_development_dependency "rollbar"
|
|
33
37
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Hubbado
|
|
2
|
+
class Log
|
|
3
|
+
module Controls
|
|
4
|
+
# Stands in for `Rails.logger`, recording the lines it was asked to write.
|
|
5
|
+
#
|
|
6
|
+
# It answers to the severities Rails' own logger answers to, and deliberately not to
|
|
7
|
+
# `trace`, which Rails has no method for. A handler passing the gem's lowest severity
|
|
8
|
+
# straight through raises here exactly as it would in a Rails application, rather than
|
|
9
|
+
# being quietly recorded and passing a spec.
|
|
10
|
+
class RailsLogger
|
|
11
|
+
SEVERITIES = %i[debug info warn error fatal unknown].freeze
|
|
12
|
+
|
|
13
|
+
def self.example = new
|
|
14
|
+
|
|
15
|
+
# Each entry is the severity it was written at and the text of it.
|
|
16
|
+
def lines = @lines ||= []
|
|
17
|
+
|
|
18
|
+
SEVERITIES.each do |severity|
|
|
19
|
+
define_method(severity) { |line| lines << [severity, line] }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Hubbado
|
|
2
|
+
class Log
|
|
3
|
+
module Controls
|
|
4
|
+
# A class that takes a logger, which is what the handler controls attach to.
|
|
5
|
+
class Receiver
|
|
6
|
+
include Log::Dependency
|
|
7
|
+
|
|
8
|
+
def self.example = new
|
|
9
|
+
|
|
10
|
+
# One that has the attribute but no logger behind it, for the guard that refuses to
|
|
11
|
+
# attach to it. The Dependency module cannot produce this — it builds a logger on first
|
|
12
|
+
# read — so the shape has to be written out.
|
|
13
|
+
def self.without_logger
|
|
14
|
+
Class.new { attr_accessor :logger }.new
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Hubbado
|
|
2
|
+
class Log
|
|
3
|
+
module Controls
|
|
4
|
+
# Stands in for the Rollbar module, recording what it was handed rather than sending it.
|
|
5
|
+
# Every level Rollbar answers to is defined, so a handler notifying at a level it should
|
|
6
|
+
# have stayed quiet at is recorded and asserted against rather than raising.
|
|
7
|
+
class Rollbar
|
|
8
|
+
# Rollbar takes its arguments positionally and untyped, assigning each one it recognises
|
|
9
|
+
# over any earlier one of the same kind. So every reader here takes the **last** match,
|
|
10
|
+
# not the first: a notification is asserted on for what Rollbar would make of it, and a
|
|
11
|
+
# handler passing two of anything must lose the same one here that it loses there.
|
|
12
|
+
Notification = Struct.new(:level, :arguments) do
|
|
13
|
+
def message = arguments.grep(String).last
|
|
14
|
+
|
|
15
|
+
# `::Exception`, not `Exception` — this module has a control of that name, and a bare
|
|
16
|
+
# constant here resolves to it and matches nothing.
|
|
17
|
+
def exception = arguments.grep(::Exception).last
|
|
18
|
+
|
|
19
|
+
def extra = arguments.grep(Hash).last
|
|
20
|
+
|
|
21
|
+
def hashes = arguments.grep(Hash).length
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
LEVELS = %i[debug info warning warn error critical].freeze
|
|
25
|
+
|
|
26
|
+
def self.example = new
|
|
27
|
+
|
|
28
|
+
def notifications = @notifications ||= []
|
|
29
|
+
|
|
30
|
+
LEVELS.each do |level|
|
|
31
|
+
define_method(level) { |*arguments| notifications << Notification.new(level, arguments) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Without a level, whether anything was sent at all — which a spec asserting silence
|
|
35
|
+
# would otherwise have to infer from an attribute never having been set.
|
|
36
|
+
def notified?(level = nil)
|
|
37
|
+
return !notifications.empty? if level.nil?
|
|
38
|
+
|
|
39
|
+
notifications.any? { |notification| notification.level == level.to_s.to_sym }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The most recent notification. Derived rather than assigned alongside `notifications`,
|
|
43
|
+
# so the two cannot disagree about which is the latest.
|
|
44
|
+
%i[level message exception extra].each do |field|
|
|
45
|
+
define_method(field) { notifications.last&.public_send(field) }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Hubbado
|
|
2
|
+
class Log
|
|
3
|
+
module Controls
|
|
4
|
+
# What the logger synthesises for a line carrying no exception: `Kernel.caller`, joined
|
|
5
|
+
# by newlines. Recognisably a stack, and recognisably not any other string a spec asserts
|
|
6
|
+
# on.
|
|
7
|
+
module Stacktrace
|
|
8
|
+
def self.example
|
|
9
|
+
"lib/scanning.rb:14:in 'sweep'\nlib/cli.rb:3:in 'call'"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/hubbado/log/controls.rb
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
require_relative 'controls/data'
|
|
2
2
|
require_relative 'controls/exception'
|
|
3
3
|
require_relative 'controls/message'
|
|
4
|
+
require_relative 'controls/stacktrace'
|
|
4
5
|
require_relative 'controls/subject'
|
|
5
6
|
|
|
7
|
+
require_relative 'controls/receiver'
|
|
8
|
+
|
|
6
9
|
require_relative 'controls/log_handler'
|
|
7
10
|
require_relative 'controls/logger'
|
|
11
|
+
|
|
12
|
+
require_relative 'controls/rails_logger'
|
|
13
|
+
require_relative 'controls/rollbar'
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'rollbar'
|
|
2
|
+
|
|
3
|
+
require_relative '../log'
|
|
4
|
+
|
|
5
|
+
module Hubbado
|
|
6
|
+
class Log
|
|
7
|
+
# Forwards a log line worth an incident to Rollbar.
|
|
8
|
+
class NotifyRollbar < LogHandler
|
|
9
|
+
# Rollbar's own levels. It has none above error, so the two severities above `warn` share
|
|
10
|
+
# it, and anything below a warning is running commentary that reaches the handlers which
|
|
11
|
+
# print rather than this one.
|
|
12
|
+
LEVELS = { warn: :warn, error: :error, fatal: :error, unknown: :error }.freeze
|
|
13
|
+
|
|
14
|
+
def initialize(notifier: nil)
|
|
15
|
+
super()
|
|
16
|
+
|
|
17
|
+
@notifier = notifier
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def log(subject, severity, message, data = nil, stacktrace = nil)
|
|
21
|
+
level = LEVELS[severity.to_sym]
|
|
22
|
+
return if level.nil?
|
|
23
|
+
|
|
24
|
+
error = data if data.is_a?(::Exception)
|
|
25
|
+
|
|
26
|
+
notifier.public_send(
|
|
27
|
+
level, *[error, title(message), extra(subject, data, stacktrace)].compact
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# Read rather than stored, so a process that reconfigures Rollbar after the log system
|
|
34
|
+
# built its handlers is notified through what it configured.
|
|
35
|
+
def notifier = @notifier || ::Rollbar
|
|
36
|
+
|
|
37
|
+
# Rollbar matches its title by type: a message that is not a String is ignored, and the
|
|
38
|
+
# item arrives with no title at all rather than with a bad one.
|
|
39
|
+
def title(message) = message.to_s
|
|
40
|
+
|
|
41
|
+
# One hash, never two. Rollbar scans its arguments by type and keeps the last hash it
|
|
42
|
+
# finds, discarding any earlier one — so a line's own data and the fields added here have
|
|
43
|
+
# to arrive merged, or one of them silently never reaches the item.
|
|
44
|
+
#
|
|
45
|
+
# Built fresh every call for the same reason it is merged: Rollbar deletes a key from the
|
|
46
|
+
# hash it is given, and the caller still holds theirs.
|
|
47
|
+
#
|
|
48
|
+
# The handler's fields merge last. A call site names the record it is about, and an item
|
|
49
|
+
# that could be told its subject was something else would file under a class that never
|
|
50
|
+
# logged it.
|
|
51
|
+
def extra(subject, data, stacktrace)
|
|
52
|
+
extra = context(data).merge("subject" => subject)
|
|
53
|
+
|
|
54
|
+
return extra if stacktrace.nil? || data.is_a?(Exception)
|
|
55
|
+
|
|
56
|
+
extra.merge("stacktrace" => stacktrace)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Rollbar matches a String to the message and an Exception to the exception, and ignores
|
|
60
|
+
# anything else it is handed. Data that is neither is named here rather than dropped.
|
|
61
|
+
def context(data)
|
|
62
|
+
case data
|
|
63
|
+
when nil, Exception then {}
|
|
64
|
+
when Hash then data.transform_keys(&:to_s)
|
|
65
|
+
else { "data" => data.inspect }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require_relative '../log'
|
|
2
|
+
|
|
3
|
+
module Hubbado
|
|
4
|
+
class Log
|
|
5
|
+
# Writes a log line into a Rails application's own log.
|
|
6
|
+
class RailsLogger < LogHandler
|
|
7
|
+
# Rails has no logger method below debug, so the gem's trace is written as one. A handler
|
|
8
|
+
# passing it straight through raises NoMethodError on the first trace line it is handed.
|
|
9
|
+
RAILS_SEVERITIES = { trace: :debug }.freeze
|
|
10
|
+
|
|
11
|
+
def initialize(rails_logger: nil)
|
|
12
|
+
super()
|
|
13
|
+
|
|
14
|
+
@rails_logger = rails_logger
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def log(subject, severity, message, data = nil, stacktrace = nil)
|
|
18
|
+
rails_severity = RAILS_SEVERITIES.fetch(severity.to_sym, severity)
|
|
19
|
+
|
|
20
|
+
rails_logger.send(rails_severity, "#{subject}: #{message}")
|
|
21
|
+
|
|
22
|
+
details(data, stacktrace).each do |detail|
|
|
23
|
+
rails_logger.send(rails_severity, detail)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# Read rather than stored, because Rails replaces its logger during boot and a handler
|
|
30
|
+
# built before that would keep writing to the one it replaced.
|
|
31
|
+
def rails_logger = @rails_logger || ::Rails.logger
|
|
32
|
+
|
|
33
|
+
# A Rails log is read after the fact, and by machine as often as by a person, so the
|
|
34
|
+
# stacktrace is written whatever the severity — where a terminal withholds it below error
|
|
35
|
+
# to stay readable.
|
|
36
|
+
#
|
|
37
|
+
# An exception is one entry rather than two: its `full_message` carries the message that
|
|
38
|
+
# `inspect` would print, so writing both writes the message twice.
|
|
39
|
+
def details(data, stacktrace)
|
|
40
|
+
return [stacktrace || data.full_message] if data.is_a?(::Exception)
|
|
41
|
+
|
|
42
|
+
[data&.inspect, stacktrace].compact
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require_relative '../log'
|
|
2
|
+
|
|
3
|
+
module Hubbado
|
|
4
|
+
class Log
|
|
5
|
+
# Prints a log line where a person watching a run can see it.
|
|
6
|
+
class StderrLogger < LogHandler
|
|
7
|
+
# Where a stacktrace earns its space on a terminal. The logger synthesises one for `warn`
|
|
8
|
+
# too, but a warning is a condition to examine rather than a failure to trace, and its
|
|
9
|
+
# message already names the field and value — so a sweep that warns per row would bury
|
|
10
|
+
# itself in Ruby stack to solve a problem stderr does not have.
|
|
11
|
+
FAILURE_SEVERITIES = %i[error fatal unknown].freeze
|
|
12
|
+
|
|
13
|
+
def initialize(io: nil)
|
|
14
|
+
super()
|
|
15
|
+
|
|
16
|
+
@io = io
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# One write, because a line and the detail under it belong together. Three writes let a
|
|
20
|
+
# second thread put its own line between them, and a stacktrace filed under the wrong
|
|
21
|
+
# message is worse than no stacktrace.
|
|
22
|
+
def log(subject, severity, message, data = nil, stacktrace = nil)
|
|
23
|
+
lines = ["#{severity.to_s.upcase} #{subject}: #{message}"]
|
|
24
|
+
lines << detail(data, stacktrace) unless data.nil?
|
|
25
|
+
lines << stacktrace if print_stacktrace?(severity, data, stacktrace)
|
|
26
|
+
|
|
27
|
+
io.puts(lines.join("\n"))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Read rather than stored, so a process that rebinds $stderr after the log system built
|
|
33
|
+
# its handlers is written to rather than the stream it replaced.
|
|
34
|
+
def io = @io || $stderr
|
|
35
|
+
|
|
36
|
+
# An exception carries its own backtrace, and the logger has already rendered it into the
|
|
37
|
+
# stacktrace argument — so that string is reused rather than rendered a second time.
|
|
38
|
+
def detail(data, stacktrace)
|
|
39
|
+
return data.inspect unless data.is_a?(::Exception)
|
|
40
|
+
|
|
41
|
+
stacktrace || data.full_message
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# An exception is excluded because its `full_message` is printed from the data above, so
|
|
45
|
+
# honouring the argument as well would print the backtrace twice.
|
|
46
|
+
def print_stacktrace?(severity, data, stacktrace)
|
|
47
|
+
return false if stacktrace.nil?
|
|
48
|
+
return false if data.is_a?(::Exception)
|
|
49
|
+
|
|
50
|
+
FAILURE_SEVERITIES.include?(severity.to_sym)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
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.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hubbado Devs
|
|
@@ -79,6 +79,20 @@ dependencies:
|
|
|
79
79
|
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rollbar
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
82
96
|
email:
|
|
83
97
|
- devs@hubbado.com
|
|
84
98
|
executables: []
|
|
@@ -97,11 +111,18 @@ files:
|
|
|
97
111
|
- lib/hubbado/log/controls/log_handler.rb
|
|
98
112
|
- lib/hubbado/log/controls/logger.rb
|
|
99
113
|
- lib/hubbado/log/controls/message.rb
|
|
114
|
+
- lib/hubbado/log/controls/rails_logger.rb
|
|
115
|
+
- lib/hubbado/log/controls/receiver.rb
|
|
116
|
+
- lib/hubbado/log/controls/rollbar.rb
|
|
117
|
+
- lib/hubbado/log/controls/stacktrace.rb
|
|
100
118
|
- lib/hubbado/log/controls/subject.rb
|
|
101
119
|
- lib/hubbado/log/log.rb
|
|
102
120
|
- lib/hubbado/log/log_handler.rb
|
|
103
121
|
- lib/hubbado/log/logger.rb
|
|
104
122
|
- lib/hubbado/log/logger/substitute.rb
|
|
123
|
+
- lib/hubbado/log/notify_rollbar.rb
|
|
124
|
+
- lib/hubbado/log/rails_logger.rb
|
|
125
|
+
- lib/hubbado/log/stderr_logger.rb
|
|
105
126
|
- lib/hubbado/log/tags.rb
|
|
106
127
|
homepage: https://www.github.com/hubbado/hubbado-log
|
|
107
128
|
licenses:
|