hubbado-log 1.3.0 → 1.4.1
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 +73 -0
- data/README.md +91 -0
- data/hubbado-log.gemspec +2 -1
- data/lib/hubbado/log/configuration.rb +31 -2
- data/lib/hubbado/log/controls/log_handler.rb +2 -2
- data/lib/hubbado/log/controls/logger.rb +16 -0
- data/lib/hubbado/log/controls.rb +1 -0
- data/lib/hubbado/log/log.rb +6 -12
- data/lib/hubbado/log/logger/substitute.rb +50 -0
- data/lib/hubbado/log.rb +1 -0
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 99367c1895c07697aa0e83dc1e068f35cf2052d75dc6c471610b47099f27fd7f
|
|
4
|
+
data.tar.gz: 58a148cfa36ea3709a2c36fbb228f089d3b176476d27ede4a74ec26a651bdf12
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5a53b16af1babaa5c90125bfddec34a02c5256661004833a63b5b54066a914bf764d2c3c738d0a0d2e556d4bd9ef86d96d7e40419e7acd810f2fb7ca4fe58788
|
|
7
|
+
data.tar.gz: 6f77c77d30c8a1ef7f89b2b73c8e09585aa3f39d6dd5d9046796220f699f2926c31debd5fb5b8f514e06c7a2fb66e2b5e20be6cf7f86b95e56bba6218a2ef5e5
|
data/ChangeLog.md
CHANGED
|
@@ -4,6 +4,79 @@ 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.4.1 - 2026-08-16]
|
|
8
|
+
## Fixed
|
|
9
|
+
- A subclass of `Hubbado::Log` can be used as a dependency. It answered `nil`
|
|
10
|
+
from `config` and raised `undefined method 'loggers' for nil` on its first
|
|
11
|
+
line, because the configuration was held in a class-level instance variable
|
|
12
|
+
and those are not inherited.
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
module Messaging
|
|
16
|
+
class Log < Hubbado::Log; end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class Handler
|
|
20
|
+
include Messaging::Log::Dependency
|
|
21
|
+
end
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The configuration is the process's now, and holds the handlers built from it,
|
|
25
|
+
so every class that writes reads the one a command configured and one
|
|
26
|
+
reconfiguration reaches all of them.
|
|
27
|
+
|
|
28
|
+
# [1.4.0 - 2026-08-16]
|
|
29
|
+
## Added
|
|
30
|
+
- A substitute for a logger. `Controls::Logger.example` returns one, and a spec
|
|
31
|
+
assigns it where a class's logger goes:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
instance.logger = Hubbado::Log::Controls::Logger.example
|
|
35
|
+
|
|
36
|
+
instance.()
|
|
37
|
+
|
|
38
|
+
assert instance.logger.logged?(:error)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
It records what it was told rather than writing, so no handler is involved and
|
|
42
|
+
neither the level nor the tag list decides what a spec can read back.
|
|
43
|
+
|
|
44
|
+
Three questions, each taking an optional severity, and each answering in the
|
|
45
|
+
terms its name promises:
|
|
46
|
+
|
|
47
|
+
| Call | Answers |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `logged?(:warn)` | whether anything was written at that severity |
|
|
50
|
+
| `messages(:warn)` | what it said — the message strings |
|
|
51
|
+
| `logged(:warn)` | everything about what it said — `severity`, `message`, `data` |
|
|
52
|
+
|
|
53
|
+
`messages` and `logged?` are both derived from `logged`, so the three cannot
|
|
54
|
+
disagree about what counts as written at a severity.
|
|
55
|
+
|
|
56
|
+
A severity reaches a logger two ways — as the generated method, or as `#log`'s
|
|
57
|
+
first argument — and both answer the same question, compared as symbols.
|
|
58
|
+
|
|
59
|
+
- `evt-subst_attr` as a runtime dependency. The substitute is a mimic of `Logger`
|
|
60
|
+
extended with `Logger::Substitute`, so it answers `is_a?(Logger)` for a class
|
|
61
|
+
that checks, and gains any method `Logger` gains.
|
|
62
|
+
|
|
63
|
+
## Deprecated
|
|
64
|
+
- `Controls::LogHandler` as the way a consumer reads back what a class logged.
|
|
65
|
+
It builds a real `Logger` and then passes `level: :trace` and `tags: Tags::ALL`
|
|
66
|
+
to switch off the filtering it just built — which is a substitute, reached the
|
|
67
|
+
long way round. Use `Controls::Logger.example`. The handler control stays for
|
|
68
|
+
what it is good at: specs where a handler receiving, or not receiving, a
|
|
69
|
+
message is the subject, as `'A message the filter left out'` is.
|
|
70
|
+
|
|
71
|
+
## Changed
|
|
72
|
+
- `Controls::LogHandler` names `Log::Logger` where it said `Logger`. With
|
|
73
|
+
`Controls::Logger` defined, a bare `Logger` inside `Controls` resolves to the
|
|
74
|
+
control rather than to the class.
|
|
75
|
+
|
|
76
|
+
## Compatibility
|
|
77
|
+
Nothing that exists breaks. `Controls::LogHandler` keeps `.attach`, `.logger`,
|
|
78
|
+
`messages`, `logged?`, `reset` and the attributes, unchanged.
|
|
79
|
+
|
|
7
80
|
# [1.3.0 - 2026-08-15]
|
|
8
81
|
## Added
|
|
9
82
|
- Tags, a second filtering axis beside the level. A message names its concern
|
data/README.md
CHANGED
|
@@ -18,6 +18,36 @@ Or install it yourself as:
|
|
|
18
18
|
|
|
19
19
|
$ gem install hubbado-log
|
|
20
20
|
|
|
21
|
+
## A class's own logger
|
|
22
|
+
|
|
23
|
+
A class that includes the dependency module gets a `logger` writing under its own name:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
class MyService
|
|
27
|
+
include Hubbado::Log::Dependency
|
|
28
|
+
|
|
29
|
+
def call
|
|
30
|
+
logger.info('Did the thing')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
A subclass carries a dependency module of its own, so a library or a component can name where
|
|
36
|
+
its classes take their logger from:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
module Messaging
|
|
40
|
+
class Log < Hubbado::Log; end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
class Handler
|
|
44
|
+
include Messaging::Log::Dependency
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Either way the logger writes through the handlers the process was configured with, at the level
|
|
49
|
+
and tags it was configured for.
|
|
50
|
+
|
|
21
51
|
## Level
|
|
22
52
|
|
|
23
53
|
A message below the level reaches no handler.
|
|
@@ -111,6 +141,67 @@ knowing before adopting tags:
|
|
|
111
141
|
list decides for both, and an application whose messages are all untagged goes silent unless the
|
|
112
142
|
list contains `_untagged`.
|
|
113
143
|
|
|
144
|
+
## Reading back what a class logged
|
|
145
|
+
|
|
146
|
+
A spec assigns a substitute where the class's logger goes, and then asks what the class said:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
require 'hubbado/log/controls'
|
|
150
|
+
|
|
151
|
+
instance.logger = Hubbado::Log::Controls::Logger.example
|
|
152
|
+
|
|
153
|
+
instance.()
|
|
154
|
+
|
|
155
|
+
assert instance.logger.logged?(:error)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
For a class handed a logger rather than carrying one — the shape a CLI usually takes — it is the
|
|
159
|
+
same object, passed in:
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
logger = Hubbado::Log::Controls::Logger.example
|
|
163
|
+
|
|
164
|
+
CLI.run(argv, logger: logger)
|
|
165
|
+
|
|
166
|
+
assert logger.logged?(:error)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
It records what it was told rather than writing, so no handler is involved and neither the
|
|
170
|
+
configured level nor `LOG_TAGS` decides what can be read back.
|
|
171
|
+
|
|
172
|
+
Three questions, each taking an optional severity:
|
|
173
|
+
|
|
174
|
+
| Call | Answers |
|
|
175
|
+
|---|---|
|
|
176
|
+
| `logged?` / `logged?(:warn)` | whether anything was written, at all or at that severity |
|
|
177
|
+
| `messages` / `messages(:warn)` | what it said — the message strings, in order |
|
|
178
|
+
| `logged` / `logged(:warn)` | everything about what it said |
|
|
179
|
+
|
|
180
|
+
`logged` answers with entries carrying `severity`, `message` and `data`, for the assertion that
|
|
181
|
+
needs more than the text:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
assert logger.logged(:error).first.data.equal?(exception)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`messages` and `logged?` are both derived from `logged`, so the three cannot disagree about what
|
|
188
|
+
counts as written at a severity.
|
|
189
|
+
|
|
190
|
+
A severity reaches a logger two ways — `logger.warn('…')` names it as the method,
|
|
191
|
+
`logger.log(:warn, '…')` as an argument — and both answer the same question, compared as symbols.
|
|
192
|
+
|
|
193
|
+
The substitute is a mimic of `Logger`, so it answers `is_a?(Hubbado::Log::Logger)` for a class
|
|
194
|
+
that checks, and gains any method `Logger` gains.
|
|
195
|
+
|
|
196
|
+
Prefer `logged?` to reaching into `messages` where it will do. That a failure was reported is
|
|
197
|
+
usually the contract; the wording of the line usually is not.
|
|
198
|
+
|
|
199
|
+
### Testing a handler
|
|
200
|
+
|
|
201
|
+
`Controls::LogHandler` is for specs where a handler receiving — or not receiving — a message is
|
|
202
|
+
itself the subject, which in practice means this gem's own tests of level and tag filtering. A
|
|
203
|
+
consumer asserting that its class logged something wants the substitute above.
|
|
204
|
+
|
|
114
205
|
## Development
|
|
115
206
|
|
|
116
207
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
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.4.1"
|
|
4
4
|
s.summary = "Lightweight pluggable logging system"
|
|
5
5
|
|
|
6
6
|
s.authors = ["Hubbado Devs"]
|
|
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
|
|
|
25
25
|
s.required_ruby_version = '>= 3.2'
|
|
26
26
|
|
|
27
27
|
s.add_runtime_dependency 'evt-dependency'
|
|
28
|
+
s.add_runtime_dependency 'evt-subst_attr'
|
|
28
29
|
|
|
29
30
|
s.add_development_dependency "debug"
|
|
30
31
|
s.add_development_dependency "hubbado-style"
|
|
@@ -5,16 +5,45 @@ module Hubbado
|
|
|
5
5
|
# for a human watching one run without it also reaching every unattended log.
|
|
6
6
|
DEFAULT_LEVEL = :info
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
attr_reader :loggers
|
|
9
9
|
attr_reader :level
|
|
10
10
|
attr_reader :tags
|
|
11
11
|
|
|
12
|
+
# The configuration is the process's rather than a class's, so a subclass of Log reads the
|
|
13
|
+
# one a command wrote and there is a single set of handlers to build from it.
|
|
14
|
+
#
|
|
15
|
+
# The one place the environment is read. A command names its level and tags here or not at
|
|
16
|
+
# all, and everything downstream is handed the value rather than the variable.
|
|
17
|
+
def self.instance
|
|
18
|
+
@instance ||= new(level: ENV[LEVEL_VARIABLE], tags: ENV[TAGS_VARIABLE])
|
|
19
|
+
end
|
|
20
|
+
|
|
12
21
|
def initialize(level: nil, tags: nil)
|
|
13
|
-
|
|
22
|
+
self.loggers = []
|
|
14
23
|
self.level = level || DEFAULT_LEVEL
|
|
15
24
|
self.tags = tags
|
|
16
25
|
end
|
|
17
26
|
|
|
27
|
+
# Whatever the block leaves behind is what the process is configured for, and the handlers
|
|
28
|
+
# are built again from it. Named here rather than left to the caller, because a block is
|
|
29
|
+
# free to mutate the list of loggers in place and never reach the writer below.
|
|
30
|
+
def change
|
|
31
|
+
yield self
|
|
32
|
+
|
|
33
|
+
@log_handlers = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The handlers themselves, one set for the process. A handler holds what it has been told,
|
|
37
|
+
# so a second set built somewhere else would be a second place to read it back from.
|
|
38
|
+
def log_handlers
|
|
39
|
+
@log_handlers ||= loggers.map(&:new)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def loggers=(value)
|
|
43
|
+
@log_handlers = nil
|
|
44
|
+
@loggers = value
|
|
45
|
+
end
|
|
46
|
+
|
|
18
47
|
# Takes the LOG_TAGS string as readily as a list, so nothing upstream has to know the
|
|
19
48
|
# syntax in order to hand a value in.
|
|
20
49
|
def tags=(value)
|
|
@@ -20,7 +20,7 @@ module Hubbado
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
new.tap do |handler|
|
|
23
|
-
instance.logger = Logger.new(logger.subject, [handler], level: level, tags: tags)
|
|
23
|
+
instance.logger = Log::Logger.new(logger.subject, [handler], level: level, tags: tags)
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -28,7 +28,7 @@ module Hubbado
|
|
|
28
28
|
def self.logger(subject = Subject.example, level: :trace, tags: Tags::ALL)
|
|
29
29
|
handler = new
|
|
30
30
|
|
|
31
|
-
[handler, Logger.new(subject, [handler], level: level, tags: tags)]
|
|
31
|
+
[handler, Log::Logger.new(subject, [handler], level: level, tags: tags)]
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def log(subject, severity, message, data = nil, stacktrace = nil)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'subst_attr'
|
|
2
|
+
|
|
3
|
+
module Hubbado
|
|
4
|
+
class Log
|
|
5
|
+
module Controls
|
|
6
|
+
module Logger
|
|
7
|
+
# A logger a spec assigns in place of a class's own, and then asks what the class said.
|
|
8
|
+
# Named here because the substitute is reached by building a mimic of Log::Logger, which
|
|
9
|
+
# is machinery a spec should not have to name.
|
|
10
|
+
def self.example
|
|
11
|
+
SubstAttr::Substitute.build(Log::Logger)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/hubbado/log/controls.rb
CHANGED
data/lib/hubbado/log/log.rb
CHANGED
|
@@ -11,22 +11,16 @@ module Hubbado
|
|
|
11
11
|
LEVEL_VARIABLE = "LOG_LEVEL".freeze
|
|
12
12
|
TAGS_VARIABLE = "LOG_TAGS".freeze
|
|
13
13
|
|
|
14
|
-
# The one place the environment is read. A command names its level and tags here or not at
|
|
15
|
-
# all, and everything downstream is handed the value rather than the variable.
|
|
16
|
-
@config = Configuration.new(level: ENV[LEVEL_VARIABLE], tags: ENV[TAGS_VARIABLE])
|
|
17
|
-
|
|
18
14
|
class << self
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def
|
|
22
|
-
yield @config
|
|
15
|
+
# Held by the configuration itself rather than here, because a class-level instance
|
|
16
|
+
# variable is not inherited: a subclass asking this class would answer nil.
|
|
17
|
+
def config = Configuration.instance
|
|
23
18
|
|
|
24
|
-
|
|
19
|
+
def configuration(&block)
|
|
20
|
+
config.change(&block)
|
|
25
21
|
end
|
|
26
22
|
|
|
27
|
-
def loggers
|
|
28
|
-
@loggers ||= config.loggers.map(&:new)
|
|
29
|
-
end
|
|
23
|
+
def loggers = config.log_handlers
|
|
30
24
|
|
|
31
25
|
def logger
|
|
32
26
|
@logger = Logger.new('', loggers)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Hubbado
|
|
2
|
+
class Log
|
|
3
|
+
class Logger
|
|
4
|
+
# What a logger was told rather than what it wrote. Extended onto a mimic of Logger, so a
|
|
5
|
+
# class under test is handed something that answers as a logger and keeps what it was given.
|
|
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)
|
|
16
|
+
entries = invocations.map { |invocation| entry(invocation) }
|
|
17
|
+
|
|
18
|
+
return entries if severity.nil?
|
|
19
|
+
|
|
20
|
+
entries.select { |entry| entry.severity == severity.to_s.to_sym }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# What a class said, where #logged is everything about it.
|
|
24
|
+
def messages(severity = nil) = logged(severity).map(&:message)
|
|
25
|
+
|
|
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?
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def entry(invocation)
|
|
33
|
+
arguments = invocation.arguments
|
|
34
|
+
|
|
35
|
+
Entry.new(
|
|
36
|
+
severity: severity(invocation).to_s.to_sym,
|
|
37
|
+
message: arguments[:msg],
|
|
38
|
+
data: arguments[:data]
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def severity(invocation)
|
|
43
|
+
return invocation.arguments.fetch(:severity) if invocation.method_name == :log
|
|
44
|
+
|
|
45
|
+
invocation.method_name
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/hubbado/log.rb
CHANGED
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.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hubbado Devs
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: evt-subst_attr
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: debug
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -81,11 +95,13 @@ files:
|
|
|
81
95
|
- lib/hubbado/log/controls/data.rb
|
|
82
96
|
- lib/hubbado/log/controls/exception.rb
|
|
83
97
|
- lib/hubbado/log/controls/log_handler.rb
|
|
98
|
+
- lib/hubbado/log/controls/logger.rb
|
|
84
99
|
- lib/hubbado/log/controls/message.rb
|
|
85
100
|
- lib/hubbado/log/controls/subject.rb
|
|
86
101
|
- lib/hubbado/log/log.rb
|
|
87
102
|
- lib/hubbado/log/log_handler.rb
|
|
88
103
|
- lib/hubbado/log/logger.rb
|
|
104
|
+
- lib/hubbado/log/logger/substitute.rb
|
|
89
105
|
- lib/hubbado/log/tags.rb
|
|
90
106
|
homepage: https://www.github.com/hubbado/hubbado-log
|
|
91
107
|
licenses:
|