twiglet 3.0.2 → 3.0.8
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/.github/dependabot.yml +22 -0
- data/.github/workflows/ruby.yml +1 -1
- data/.github/workflows/version-forget-me-not.yml +1 -1
- data/.github/workflows/version-update.yml +15 -0
- data/README.md +5 -1
- data/example_app.rb +30 -24
- data/examples/rack/request_logger_test.rb +7 -5
- data/lib/twiglet/logger.rb +10 -8
- data/lib/twiglet/message.rb +3 -0
- data/lib/twiglet/version.rb +1 -1
- data/test/logger_test.rb +63 -45
- data/test/test_coverage.rb +6 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aadbe159aee695cfd454e0f006ab5eab2b3c06d01ecaa80d3f6b1b69c0d21d5e
|
4
|
+
data.tar.gz: 53907b03fb29af1fe41d8262619a6d5c84dab554b4e309d7336d969c11600a44
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5e68a4b343c861c7236e4a301365d738221bbe5eb0b23426e3cba84fad2358046d1b868bef5248c1c3398f4c2cfd132445259b7db976937024f73663699c2d4
|
7
|
+
data.tar.gz: 28b619fd981103d5baea53d07e032e52e21cf88e84b5229d1ca1ff61cedc284d6825ebf26fb977f060d3af4c34f276b8a69c733e66321952a726d452dc82164b
|
@@ -0,0 +1,22 @@
|
|
1
|
+
version: 2
|
2
|
+
updates:
|
3
|
+
- package-ecosystem: bundler
|
4
|
+
directory: "/"
|
5
|
+
schedule:
|
6
|
+
interval: daily
|
7
|
+
time: "07:00"
|
8
|
+
open-pull-requests-limit: 99
|
9
|
+
labels:
|
10
|
+
- dependencies
|
11
|
+
ignore:
|
12
|
+
- dependency-name: simplecov
|
13
|
+
versions:
|
14
|
+
- ">= 0.18"
|
15
|
+
- package-ecosystem: github-actions
|
16
|
+
directory: "/"
|
17
|
+
schedule:
|
18
|
+
interval: daily
|
19
|
+
time: "07:00"
|
20
|
+
open-pull-requests-limit: 99
|
21
|
+
labels:
|
22
|
+
- dependencies
|
data/.github/workflows/ruby.yml
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
name: "version update action"
|
2
|
+
on:
|
3
|
+
issue_comment:
|
4
|
+
types: [created]
|
5
|
+
jobs:
|
6
|
+
pr_commented:
|
7
|
+
runs-on: ubuntu-20.04
|
8
|
+
if: startsWith(github.event.comment.body, '/dobby')
|
9
|
+
|
10
|
+
steps:
|
11
|
+
- name: 'bump version'
|
12
|
+
uses: simplybusiness/dobby@v1.0.0
|
13
|
+
env:
|
14
|
+
ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
15
|
+
VERSION_FILE_PATH: lib/twiglet/version.rb
|
data/README.md
CHANGED
@@ -110,7 +110,7 @@ This writes:
|
|
110
110
|
It may be that when making a series of logs that write information about a single event, you may want to avoid duplication by creating an event specific logger that includes the context:
|
111
111
|
|
112
112
|
```ruby
|
113
|
-
|
113
|
+
request_logger = logger.with({ event: { action: 'HTTP request'}, trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' }})
|
114
114
|
```
|
115
115
|
|
116
116
|
This can be used like any other Logger instance:
|
@@ -140,6 +140,10 @@ To access the formatter:
|
|
140
140
|
logger.formatter
|
141
141
|
```
|
142
142
|
|
143
|
+
### HTTP Request Logging
|
144
|
+
Take a look at this sample [Rack application](examples/rack/example_rack_app.rb#L15) with an ECS compliant
|
145
|
+
[request logger](/examples/rack/request_logger.rb) as a template when configuring your own request logging middleware with Twiglet.
|
146
|
+
|
143
147
|
## Use of dotted keys (DEPRECATED)
|
144
148
|
|
145
149
|
Writing nested json objects could be confusing. This library has a built-in feature to convert dotted keys into nested objects, so if you log like this:
|
data/example_app.rb
CHANGED
@@ -7,28 +7,32 @@ PORT = 8080
|
|
7
7
|
logger = Twiglet::Logger.new('petshop')
|
8
8
|
|
9
9
|
# Start our petshop
|
10
|
-
logger.info(
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
logger.info(
|
11
|
+
{
|
12
|
+
event: {
|
13
|
+
action: 'startup'
|
14
|
+
},
|
15
|
+
message: "Ready to go, listening on port #{PORT}",
|
16
|
+
server: {
|
17
|
+
port: PORT
|
18
|
+
}
|
17
19
|
}
|
18
|
-
|
20
|
+
)
|
19
21
|
|
20
22
|
# Use text logging
|
21
23
|
logger.info("Ready to go, listening on port #{PORT}")
|
22
24
|
#
|
23
25
|
# We get a request
|
24
|
-
request_logger = logger.with(
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
request_logger = logger.with(
|
27
|
+
{
|
28
|
+
event: {
|
29
|
+
action: 'HTTP request'
|
30
|
+
},
|
31
|
+
trace: {
|
32
|
+
id: '126bb6fa-28a2-470f-b013-eefbf9182b2d'
|
33
|
+
}
|
30
34
|
}
|
31
|
-
|
35
|
+
)
|
32
36
|
|
33
37
|
# Oh noes!
|
34
38
|
db_err = StandardError.new('Connection timed-out')
|
@@ -36,17 +40,19 @@ db_err = StandardError.new('Connection timed-out')
|
|
36
40
|
request_logger.error({ message: 'DB connection failed.' }, db_err) if db_err
|
37
41
|
|
38
42
|
# We return an error to the requester
|
39
|
-
request_logger.info(
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
request_logger.info(
|
44
|
+
{
|
45
|
+
message: 'Internal Server Error',
|
46
|
+
http: {
|
47
|
+
request: {
|
48
|
+
method: 'get'
|
49
|
+
},
|
50
|
+
response: {
|
51
|
+
status_code: 500
|
52
|
+
}
|
47
53
|
}
|
48
54
|
}
|
49
|
-
|
55
|
+
)
|
50
56
|
|
51
57
|
# Logging with an empty message is an anti-pattern and is therefore forbidden
|
52
58
|
# Both of the following lines would throw an error
|
@@ -15,10 +15,12 @@ describe RequestLogger do
|
|
15
15
|
end
|
16
16
|
|
17
17
|
it 'logs the request data' do
|
18
|
-
request.get(
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
request.get(
|
19
|
+
"/some/path?some_var=1", 'HTTP_ACCEPT' => 'application/json',
|
20
|
+
'REMOTE_ADDR' => '0.0.0.0',
|
21
|
+
'HTTP_VERSION' => 'HTTP/1.1',
|
22
|
+
'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh)'
|
23
|
+
)
|
22
24
|
log = JSON.parse(output.string)
|
23
25
|
|
24
26
|
expected_log = {
|
@@ -55,7 +57,7 @@ describe RequestLogger do
|
|
55
57
|
end
|
56
58
|
|
57
59
|
it 'does not log PII' do
|
58
|
-
request.post("/user/info", input_data: {credit_card_no: '1234'})
|
60
|
+
request.post("/user/info", input_data: { credit_card_no: '1234' })
|
59
61
|
log = output.string
|
60
62
|
assert_includes log, "POST: /user/info"
|
61
63
|
refute_includes log, 'credit_card_no'
|
data/lib/twiglet/logger.rb
CHANGED
@@ -33,9 +33,9 @@ module Twiglet
|
|
33
33
|
def error(message = nil, error = nil, &block)
|
34
34
|
if error
|
35
35
|
error_fields = {
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
error: {
|
37
|
+
type: error.class,
|
38
|
+
message: error.message
|
39
39
|
}
|
40
40
|
}
|
41
41
|
add_stack_trace(error_fields, error)
|
@@ -46,11 +46,13 @@ module Twiglet
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def with(default_properties)
|
49
|
-
Logger.new(
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
Logger.new(
|
50
|
+
@service_name,
|
51
|
+
default_properties: default_properties,
|
52
|
+
now: @now,
|
53
|
+
output: @output,
|
54
|
+
level: @level
|
55
|
+
)
|
54
56
|
end
|
55
57
|
|
56
58
|
alias_method :warning, :warn
|
data/lib/twiglet/message.rb
CHANGED
data/lib/twiglet/version.rb
CHANGED
data/test/logger_test.rb
CHANGED
@@ -18,9 +18,11 @@ describe Twiglet::Logger do
|
|
18
18
|
before do
|
19
19
|
@now = -> { Time.utc(2020, 5, 11, 15, 1, 1) }
|
20
20
|
@buffer = StringIO.new
|
21
|
-
@logger = Twiglet::Logger.new(
|
22
|
-
|
23
|
-
|
21
|
+
@logger = Twiglet::Logger.new(
|
22
|
+
'petshop',
|
23
|
+
now: @now,
|
24
|
+
output: @buffer
|
25
|
+
)
|
24
26
|
end
|
25
27
|
|
26
28
|
it 'should throw an error with an empty service name' do
|
@@ -30,8 +32,10 @@ describe Twiglet::Logger do
|
|
30
32
|
end
|
31
33
|
|
32
34
|
it 'conforms to the standard Ruby Logger API' do
|
33
|
-
[
|
34
|
-
|
35
|
+
[
|
36
|
+
:debug, :debug?, :info, :info?, :warn, :warn?, :fatal, :fatal?, :error, :error?,
|
37
|
+
:level, :level=, :sev_threshold=
|
38
|
+
].each do |call|
|
35
39
|
assert @logger.respond_to?(call), "Logger does not respond to #{call}"
|
36
40
|
end
|
37
41
|
end
|
@@ -39,12 +43,12 @@ describe Twiglet::Logger do
|
|
39
43
|
describe 'JSON logging' do
|
40
44
|
it 'should throw an error with an empty message' do
|
41
45
|
assert_raises RuntimeError do
|
42
|
-
@logger.info({message: ''})
|
46
|
+
@logger.info({ message: '' })
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
46
50
|
it 'should log mandatory attributes' do
|
47
|
-
@logger.error({message: 'Out of pets exception'})
|
51
|
+
@logger.error({ message: 'Out of pets exception' })
|
48
52
|
actual_log = read_json(@buffer)
|
49
53
|
|
50
54
|
expected_log = {
|
@@ -65,9 +69,13 @@ describe Twiglet::Logger do
|
|
65
69
|
end
|
66
70
|
|
67
71
|
it 'should log the provided message' do
|
68
|
-
@logger.error(
|
69
|
-
|
70
|
-
|
72
|
+
@logger.error(
|
73
|
+
{
|
74
|
+
event:
|
75
|
+
{ action: 'exception' },
|
76
|
+
message: 'Emergency! Emergency!'
|
77
|
+
}
|
78
|
+
)
|
71
79
|
log = read_json(@buffer)
|
72
80
|
|
73
81
|
assert_equal 'exception', log[:event][:action]
|
@@ -82,17 +90,19 @@ describe Twiglet::Logger do
|
|
82
90
|
service: {
|
83
91
|
type: 'shop'
|
84
92
|
},
|
85
|
-
request: {method: 'get'},
|
86
|
-
response: {status_code: 200}
|
93
|
+
request: { method: 'get' },
|
94
|
+
response: { status_code: 200 }
|
87
95
|
}
|
88
96
|
|
89
97
|
output = StringIO.new
|
90
|
-
logger = Twiglet::Logger.new(
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
98
|
+
logger = Twiglet::Logger.new(
|
99
|
+
'petshop',
|
100
|
+
now: @now,
|
101
|
+
output: output,
|
102
|
+
default_properties: extra_properties
|
103
|
+
)
|
104
|
+
|
105
|
+
logger.error({ message: 'GET /cats' })
|
96
106
|
log = read_json output
|
97
107
|
|
98
108
|
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
@@ -104,17 +114,21 @@ describe Twiglet::Logger do
|
|
104
114
|
|
105
115
|
it "should be able to add properties with '.with'" do
|
106
116
|
# Let's add some context to this customer journey
|
107
|
-
purchase_logger = @logger.with(
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
117
|
+
purchase_logger = @logger.with(
|
118
|
+
{
|
119
|
+
trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' },
|
120
|
+
customer: { full_name: 'Freda Bloggs' },
|
121
|
+
event: { action: 'pet purchase' }
|
122
|
+
}
|
123
|
+
)
|
112
124
|
|
113
125
|
# do stuff
|
114
|
-
purchase_logger.info(
|
115
|
-
|
116
|
-
|
117
|
-
|
126
|
+
purchase_logger.info(
|
127
|
+
{
|
128
|
+
message: 'customer bought a dog',
|
129
|
+
pet: { name: 'Barker', species: 'dog', breed: 'Bitsa' }
|
130
|
+
}
|
131
|
+
)
|
118
132
|
|
119
133
|
log = read_json @buffer
|
120
134
|
|
@@ -135,8 +149,8 @@ describe Twiglet::Logger do
|
|
135
149
|
end
|
136
150
|
|
137
151
|
it "should log multiple messages properly" do
|
138
|
-
@logger.debug({message: 'hi'})
|
139
|
-
@logger.info({message: 'there'})
|
152
|
+
@logger.debug({ message: 'hi' })
|
153
|
+
@logger.info({ message: 'there' })
|
140
154
|
|
141
155
|
expected_output =
|
142
156
|
'{"ecs":{"version":"1.5.0"},"@timestamp":"2020-05-11T15:01:01.000Z",'\
|
@@ -174,7 +188,7 @@ describe Twiglet::Logger do
|
|
174
188
|
|
175
189
|
LEVELS.each do |attrs|
|
176
190
|
it "should correctly log level when calling #{attrs[:method]}" do
|
177
|
-
@logger.public_send(attrs[:method], {message: 'a log message'})
|
191
|
+
@logger.public_send(attrs[:method], { message: 'a log message' })
|
178
192
|
actual_log = read_json(@buffer)
|
179
193
|
|
180
194
|
assert_equal attrs[:level], actual_log[:log][:level]
|
@@ -188,7 +202,7 @@ describe Twiglet::Logger do
|
|
188
202
|
begin
|
189
203
|
1 / 0
|
190
204
|
rescue StandardError => e
|
191
|
-
@logger.error({message: 'Artificially raised exception'}, e)
|
205
|
+
@logger.error({ message: 'Artificially raised exception' }, e)
|
192
206
|
end
|
193
207
|
|
194
208
|
actual_log = read_json(@buffer)
|
@@ -201,7 +215,7 @@ describe Twiglet::Logger do
|
|
201
215
|
|
202
216
|
it 'should log an error without backtrace' do
|
203
217
|
e = StandardError.new('Connection timed-out')
|
204
|
-
@logger.error({message: 'Artificially raised exception'}, e)
|
218
|
+
@logger.error({ message: 'Artificially raised exception' }, e)
|
205
219
|
|
206
220
|
actual_log = read_json(@buffer)
|
207
221
|
|
@@ -284,13 +298,15 @@ describe Twiglet::Logger do
|
|
284
298
|
|
285
299
|
describe 'dotted keys' do
|
286
300
|
it 'should be able to convert dotted keys to nested objects' do
|
287
|
-
@logger.debug(
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
301
|
+
@logger.debug(
|
302
|
+
{
|
303
|
+
"trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
|
304
|
+
message: 'customer bought a dog',
|
305
|
+
"pet.name": 'Barker',
|
306
|
+
"pet.species": 'dog',
|
307
|
+
"pet.breed": 'Bitsa'
|
308
|
+
}
|
309
|
+
)
|
294
310
|
log = read_json(@buffer)
|
295
311
|
|
296
312
|
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
@@ -301,12 +317,14 @@ describe Twiglet::Logger do
|
|
301
317
|
end
|
302
318
|
|
303
319
|
it 'should be able to mix dotted keys and nested objects' do
|
304
|
-
@logger.debug(
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
320
|
+
@logger.debug(
|
321
|
+
{
|
322
|
+
"trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
|
323
|
+
message: 'customer bought a dog',
|
324
|
+
pet: { name: 'Barker', breed: 'Bitsa' },
|
325
|
+
"pet.species": 'dog'
|
326
|
+
}
|
327
|
+
)
|
310
328
|
log = read_json(@buffer)
|
311
329
|
|
312
330
|
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
data/test/test_coverage.rb
CHANGED
@@ -7,7 +7,11 @@ SimpleCov.start do
|
|
7
7
|
if ENV['CI']
|
8
8
|
formatter SimpleCov::Formatter::SimpleFormatter
|
9
9
|
else
|
10
|
-
formatter SimpleCov::Formatter::MultiFormatter.new(
|
11
|
-
|
10
|
+
formatter SimpleCov::Formatter::MultiFormatter.new(
|
11
|
+
[
|
12
|
+
SimpleCov::Formatter::SimpleFormatter,
|
13
|
+
SimpleCov::Formatter::HTMLFormatter
|
14
|
+
]
|
15
|
+
)
|
12
16
|
end
|
13
17
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: twiglet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.
|
4
|
+
version: 3.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simply Business
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-02-11 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Like a log, only smaller.
|
14
14
|
email:
|
@@ -18,9 +18,11 @@ extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
20
|
- ".github/CODEOWNERS"
|
21
|
+
- ".github/dependabot.yml"
|
21
22
|
- ".github/workflows/gem-publish.yml"
|
22
23
|
- ".github/workflows/ruby.yml"
|
23
24
|
- ".github/workflows/version-forget-me-not.yml"
|
25
|
+
- ".github/workflows/version-update.yml"
|
24
26
|
- ".gitignore"
|
25
27
|
- ".rubocop.yml"
|
26
28
|
- ".ruby-version"
|