twiglet 3.14.2 → 3.15.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/lib/twiglet/version.rb +1 -1
- metadata +42 -38
- data/.github/CODEOWNERS +0 -4
- data/.github/dependabot.yml +0 -22
- data/.github/workflows/codeql-analysis.yml +0 -70
- data/.github/workflows/dobby-actions.yml +0 -28
- data/.github/workflows/gem-publish.yml +0 -46
- data/.github/workflows/ruby.yml +0 -38
- data/.github/workflows/version-forget-me-not.yml +0 -19
- data/.gitignore +0 -58
- data/.rubocop.yml +0 -14
- data/.ruby-version +0 -1
- data/Gemfile +0 -5
- data/Rakefile +0 -7
- data/catalog-info.yaml +0 -15
- data/docs/CODE_OF_CONDUCT.md +0 -76
- data/docs/RATIONALE.md +0 -90
- data/docs/index.md +0 -256
- data/example_app.rb +0 -60
- data/examples/rack/example_rack_app.rb +0 -17
- data/examples/rack/request_logger.rb +0 -66
- data/examples/rack/request_logger_test.rb +0 -91
- data/test/error_serialiser_test.rb +0 -18
- data/test/formatter_test.rb +0 -89
- data/test/hash_extensions_test.rb +0 -133
- data/test/logger_test.rb +0 -751
- data/test/message_test.rb +0 -13
- data/test/test_coverage.rb +0 -17
- data/test/validator_test.rb +0 -38
- data/twiglet.gemspec +0 -29
data/test/logger_test.rb
DELETED
|
@@ -1,751 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'minitest/autorun'
|
|
4
|
-
require 'minitest/mock'
|
|
5
|
-
require_relative '../lib/twiglet/logger'
|
|
6
|
-
require 'active_support'
|
|
7
|
-
|
|
8
|
-
LEVELS = [
|
|
9
|
-
{ method: :debug, level: 'debug' },
|
|
10
|
-
{ method: :info, level: 'info' },
|
|
11
|
-
{ method: :warning, level: 'warn' },
|
|
12
|
-
{ method: :warn, level: 'warn' },
|
|
13
|
-
{ method: :critical, level: 'fatal' },
|
|
14
|
-
{ method: :fatal, level: 'fatal' },
|
|
15
|
-
{ method: :error, level: 'error' }
|
|
16
|
-
].freeze
|
|
17
|
-
|
|
18
|
-
# rubocop:disable Metrics/BlockLength
|
|
19
|
-
describe Twiglet::Logger do
|
|
20
|
-
before do
|
|
21
|
-
@now = -> { Time.utc(2020, 5, 11, 15, 1, 1) }
|
|
22
|
-
@buffer = StringIO.new
|
|
23
|
-
@logger = Twiglet::Logger.new(
|
|
24
|
-
'petshop',
|
|
25
|
-
now: @now,
|
|
26
|
-
output: @buffer,
|
|
27
|
-
level: Logger::DEBUG
|
|
28
|
-
)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
it 'should throw an error with an empty service name' do
|
|
32
|
-
assert_raises RuntimeError do
|
|
33
|
-
Twiglet::Logger.new(' ')
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
it 'conforms to the standard Ruby Logger API' do
|
|
38
|
-
[
|
|
39
|
-
:debug,
|
|
40
|
-
:debug?,
|
|
41
|
-
:info,
|
|
42
|
-
:info?,
|
|
43
|
-
:warn,
|
|
44
|
-
:warn?,
|
|
45
|
-
:fatal,
|
|
46
|
-
:fatal?,
|
|
47
|
-
:error,
|
|
48
|
-
:error?,
|
|
49
|
-
:level,
|
|
50
|
-
:level=,
|
|
51
|
-
:sev_threshold=
|
|
52
|
-
].each do |call|
|
|
53
|
-
assert_respond_to @logger, call, "Logger does not respond to #{call}"
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
describe 'JSON logging' do
|
|
58
|
-
it 'should throw an error with an empty message' do
|
|
59
|
-
assert_raises JSON::Schema::ValidationError, "The property '#/message' was not of a minimum string length of 1" do
|
|
60
|
-
@logger.info({ message: '' })
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
it 'should throw an error if message is missing' do
|
|
65
|
-
assert_raises JSON::Schema::ValidationError, "The property '#/message' was not of a minimum string length of 1" do
|
|
66
|
-
@logger.info({ foo: 'bar' })
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
it 'should log mandatory attributes' do
|
|
71
|
-
@logger.error({ message: 'Out of pets exception' })
|
|
72
|
-
actual_log = read_json(@buffer)
|
|
73
|
-
|
|
74
|
-
expected_log = {
|
|
75
|
-
message: 'Out of pets exception',
|
|
76
|
-
ecs: {
|
|
77
|
-
version: '1.5.0'
|
|
78
|
-
},
|
|
79
|
-
'@timestamp': '2020-05-11T15:01:01.000Z',
|
|
80
|
-
service: {
|
|
81
|
-
name: 'petshop'
|
|
82
|
-
},
|
|
83
|
-
log: {
|
|
84
|
-
level: 'error'
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
assert_equal expected_log, actual_log
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
it 'should log the provided message' do
|
|
92
|
-
@logger.error(
|
|
93
|
-
{
|
|
94
|
-
event:
|
|
95
|
-
{ action: 'exception' },
|
|
96
|
-
message: 'Emergency! Emergency!'
|
|
97
|
-
}
|
|
98
|
-
)
|
|
99
|
-
log = read_json(@buffer)
|
|
100
|
-
|
|
101
|
-
assert_equal 'exception', log[:event][:action]
|
|
102
|
-
assert_equal 'Emergency! Emergency!', log[:message]
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
it 'should log scoped properties defined at creation' do
|
|
106
|
-
extra_properties = {
|
|
107
|
-
trace: {
|
|
108
|
-
id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'
|
|
109
|
-
},
|
|
110
|
-
service: {
|
|
111
|
-
type: 'shop'
|
|
112
|
-
},
|
|
113
|
-
request: { method: 'get' },
|
|
114
|
-
response: { status_code: 200 }
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
output = StringIO.new
|
|
118
|
-
logger = Twiglet::Logger.new(
|
|
119
|
-
'petshop',
|
|
120
|
-
now: @now,
|
|
121
|
-
output: output,
|
|
122
|
-
default_properties: extra_properties
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
logger.error({ message: 'GET /cats' })
|
|
126
|
-
log = read_json output
|
|
127
|
-
|
|
128
|
-
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
|
129
|
-
assert_equal 'petshop', log[:service][:name]
|
|
130
|
-
assert_equal 'shop', log[:service][:type]
|
|
131
|
-
assert_equal 'get', log[:request][:method]
|
|
132
|
-
assert_equal 200, log[:response][:status_code]
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
it "should be able to add properties with '.with'" do
|
|
136
|
-
# Let's add some context to this customer journey
|
|
137
|
-
purchase_logger = @logger.with(
|
|
138
|
-
{
|
|
139
|
-
trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' },
|
|
140
|
-
customer: { full_name: 'Freda Bloggs' },
|
|
141
|
-
event: { action: 'pet purchase' }
|
|
142
|
-
}
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
# do stuff
|
|
146
|
-
purchase_logger.info(
|
|
147
|
-
{
|
|
148
|
-
message: 'customer bought a dog',
|
|
149
|
-
pet: { name: 'Barker', species: 'dog', breed: 'Bitsa' }
|
|
150
|
-
}
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
log = read_json @buffer
|
|
154
|
-
|
|
155
|
-
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
|
156
|
-
assert_equal 'Freda Bloggs', log[:customer][:full_name]
|
|
157
|
-
assert_equal 'pet purchase', log[:event][:action]
|
|
158
|
-
assert_equal 'customer bought a dog', log[:message]
|
|
159
|
-
assert_equal 'Barker', log[:pet][:name]
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
it "isn't possible to chain .with methods to gradually add messages" do
|
|
163
|
-
# Let's add some context to this customer journey
|
|
164
|
-
purchase_logger = @logger.with(
|
|
165
|
-
{
|
|
166
|
-
trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' }
|
|
167
|
-
}
|
|
168
|
-
).with(
|
|
169
|
-
{
|
|
170
|
-
customer: { full_name: 'Freda Bloggs' },
|
|
171
|
-
event: { action: 'pet purchase' }
|
|
172
|
-
}
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
# do stuff
|
|
176
|
-
purchase_logger.info(
|
|
177
|
-
{
|
|
178
|
-
message: 'customer bought a dog',
|
|
179
|
-
pet: { name: 'Barker', species: 'dog', breed: 'Bitsa' }
|
|
180
|
-
}
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
log = read_json @buffer
|
|
184
|
-
|
|
185
|
-
assert_nil log[:trace]
|
|
186
|
-
assert_equal 'Freda Bloggs', log[:customer][:full_name]
|
|
187
|
-
assert_equal 'pet purchase', log[:event][:action]
|
|
188
|
-
assert_equal 'customer bought a dog', log[:message]
|
|
189
|
-
assert_equal 'Barker', log[:pet][:name]
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
it "should be able to add contextual information to events with the context_provider" do
|
|
193
|
-
provider = -> { { 'context' => { 'id' => 'my-context-id' } } }
|
|
194
|
-
purchase_logger = Twiglet::Logger.new(
|
|
195
|
-
'petshop',
|
|
196
|
-
now: @now,
|
|
197
|
-
output: @buffer,
|
|
198
|
-
context_provider: provider
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
# do stuff
|
|
202
|
-
purchase_logger.info(
|
|
203
|
-
{
|
|
204
|
-
message: 'customer bought a dog',
|
|
205
|
-
pet: { name: 'Barker', species: 'dog', breed: 'Bitsa' }
|
|
206
|
-
}
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
log = read_json @buffer
|
|
210
|
-
|
|
211
|
-
assert_equal 'customer bought a dog', log[:message]
|
|
212
|
-
assert_equal 'my-context-id', log[:context][:id]
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
it "should be able to add contextual information to events with multiple context providers" do
|
|
216
|
-
provider_1 = -> { { 'context' => { 'id' => 'my-context-id' } } }
|
|
217
|
-
provider_2 = -> { { 'context' => { 'type' => 'test' } } }
|
|
218
|
-
purchase_logger = Twiglet::Logger.new(
|
|
219
|
-
'petshop',
|
|
220
|
-
now: @now,
|
|
221
|
-
output: @buffer,
|
|
222
|
-
context_providers: [provider_1, provider_2]
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
# do stuff
|
|
226
|
-
purchase_logger.info(
|
|
227
|
-
{
|
|
228
|
-
message: 'customer bought a dog',
|
|
229
|
-
pet: { name: 'Barker', species: 'dog', breed: 'Bitsa' }
|
|
230
|
-
}
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
log = read_json @buffer
|
|
234
|
-
|
|
235
|
-
assert_equal 'customer bought a dog', log[:message]
|
|
236
|
-
assert_equal 'my-context-id', log[:context][:id]
|
|
237
|
-
assert_equal 'test', log[:context][:type]
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
it "chaining .with and .context_provider is possible" do
|
|
241
|
-
# Let's add some context to this customer journey
|
|
242
|
-
purchase_logger = @logger.with(
|
|
243
|
-
{
|
|
244
|
-
trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' },
|
|
245
|
-
customer: { full_name: 'Freda Bloggs' },
|
|
246
|
-
event: { action: 'pet purchase' }
|
|
247
|
-
}
|
|
248
|
-
).context_provider do
|
|
249
|
-
{ 'context' => { 'id' => 'my-context-id' } }
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
# do stuff
|
|
253
|
-
purchase_logger.info(
|
|
254
|
-
{
|
|
255
|
-
message: 'customer bought a dog',
|
|
256
|
-
pet: { name: 'Barker', species: 'dog', breed: 'Bitsa' }
|
|
257
|
-
}
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
log = read_json @buffer
|
|
261
|
-
|
|
262
|
-
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
|
263
|
-
assert_equal 'Freda Bloggs', log[:customer][:full_name]
|
|
264
|
-
assert_equal 'pet purchase', log[:event][:action]
|
|
265
|
-
assert_equal 'customer bought a dog', log[:message]
|
|
266
|
-
assert_equal 'Barker', log[:pet][:name]
|
|
267
|
-
assert_equal 'my-context-id', log[:context][:id]
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
it "chaining .context_provider and .with is possible" do
|
|
271
|
-
# Let's add some context to this customer journey
|
|
272
|
-
purchase_logger = @logger
|
|
273
|
-
.context_provider do
|
|
274
|
-
{ 'context' => { 'id' => 'my-context-id' } }
|
|
275
|
-
end.with(
|
|
276
|
-
{
|
|
277
|
-
trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' },
|
|
278
|
-
customer: { full_name: 'Freda Bloggs' },
|
|
279
|
-
event: { action: 'pet purchase' }
|
|
280
|
-
}
|
|
281
|
-
)
|
|
282
|
-
# do stuff
|
|
283
|
-
purchase_logger.info(
|
|
284
|
-
{
|
|
285
|
-
message: 'customer bought a dog',
|
|
286
|
-
pet: { name: 'Barker', species: 'dog', breed: 'Bitsa' }
|
|
287
|
-
}
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
log = read_json @buffer
|
|
291
|
-
|
|
292
|
-
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
|
293
|
-
assert_equal 'Freda Bloggs', log[:customer][:full_name]
|
|
294
|
-
assert_equal 'pet purchase', log[:event][:action]
|
|
295
|
-
assert_equal 'customer bought a dog', log[:message]
|
|
296
|
-
assert_equal 'Barker', log[:pet][:name]
|
|
297
|
-
assert_equal 'my-context-id', log[:context][:id]
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
it "previously supplied context providers should be preserved" do
|
|
301
|
-
# Let's add some context to this customer journey
|
|
302
|
-
purchase_logger = @logger
|
|
303
|
-
.context_provider { { 'first-context' => { 'first-id' => 'my-first-context-id' } } }
|
|
304
|
-
.context_provider { { 'second-context' => { 'second-id' => 'my-second-context-id' } } }
|
|
305
|
-
# do stuff
|
|
306
|
-
purchase_logger.info(
|
|
307
|
-
{
|
|
308
|
-
message: 'customer bought a dog',
|
|
309
|
-
pet: { name: 'Barker', species: 'dog', breed: 'Bitsa' }
|
|
310
|
-
}
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
log = read_json @buffer
|
|
314
|
-
|
|
315
|
-
assert_equal 'customer bought a dog', log[:message]
|
|
316
|
-
assert_equal 'Barker', log[:pet][:name]
|
|
317
|
-
assert_equal 'my-first-context-id', log[:'first-context'][:'first-id']
|
|
318
|
-
assert_equal 'my-second-context-id', log[:'second-context'][:'second-id']
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
it "should log 'message' string property" do
|
|
322
|
-
message = {}
|
|
323
|
-
message['message'] = 'Guinea pigs arrived'
|
|
324
|
-
@logger.debug(message)
|
|
325
|
-
log = read_json(@buffer)
|
|
326
|
-
|
|
327
|
-
assert_equal 'Guinea pigs arrived', log[:message]
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
it "should log multiple messages properly" do
|
|
331
|
-
@logger.debug({ message: 'hi' })
|
|
332
|
-
@logger.info({ message: 'there' })
|
|
333
|
-
|
|
334
|
-
expected_output =
|
|
335
|
-
'{"ecs":{"version":"1.5.0"},"@timestamp":"2020-05-11T15:01:01.000Z",' \
|
|
336
|
-
'"service":{"name":"petshop"},"log":{"level":"debug"},"message":"hi"}' \
|
|
337
|
-
"\n" \
|
|
338
|
-
'{"ecs":{"version":"1.5.0"},"@timestamp":"2020-05-11T15:01:01.000Z",' \
|
|
339
|
-
'"service":{"name":"petshop"},"log":{"level":"info"},"message":"there"}' \
|
|
340
|
-
"\n" \
|
|
341
|
-
|
|
342
|
-
assert_equal expected_output, @buffer.string
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
it 'should work with mixed string and symbol properties' do
|
|
346
|
-
log = {
|
|
347
|
-
'trace.id': '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'
|
|
348
|
-
}
|
|
349
|
-
event = {}
|
|
350
|
-
log['event'] = event
|
|
351
|
-
log['message'] = 'customer bought a dog'
|
|
352
|
-
pet = {}
|
|
353
|
-
pet['name'] = 'Barker'
|
|
354
|
-
pet['breed'] = 'Bitsa'
|
|
355
|
-
pet[:species] = 'dog'
|
|
356
|
-
log[:pet] = pet
|
|
357
|
-
|
|
358
|
-
@logger.debug(log)
|
|
359
|
-
actual_log = read_json(@buffer)
|
|
360
|
-
|
|
361
|
-
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', actual_log[:trace][:id]
|
|
362
|
-
assert_equal 'customer bought a dog', actual_log[:message]
|
|
363
|
-
assert_equal 'Barker', actual_log[:pet][:name]
|
|
364
|
-
assert_equal 'dog', actual_log[:pet][:species]
|
|
365
|
-
assert_equal 'Bitsa', actual_log[:pet][:breed]
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
LEVELS.each do |attrs|
|
|
369
|
-
it "should correctly log level when calling #{attrs[:method]}" do
|
|
370
|
-
@logger.public_send(attrs[:method], { message: 'a log message' })
|
|
371
|
-
actual_log = read_json(@buffer)
|
|
372
|
-
|
|
373
|
-
assert_equal attrs[:level], actual_log[:log][:level]
|
|
374
|
-
assert_equal 'a log message', actual_log[:message]
|
|
375
|
-
end
|
|
376
|
-
end
|
|
377
|
-
end
|
|
378
|
-
|
|
379
|
-
describe 'logging an exception' do
|
|
380
|
-
it 'should log an error with backtrace' do
|
|
381
|
-
begin
|
|
382
|
-
1 / 0
|
|
383
|
-
rescue StandardError => e
|
|
384
|
-
@logger.error({ message: 'Artificially raised exception' }, e)
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
actual_log = read_json(@buffer)
|
|
388
|
-
|
|
389
|
-
assert_equal 'Artificially raised exception', actual_log[:message]
|
|
390
|
-
assert_equal 'ZeroDivisionError', actual_log[:error][:type]
|
|
391
|
-
assert_equal 'divided by 0', actual_log[:error][:message]
|
|
392
|
-
assert_match 'test/logger_test.rb', actual_log[:error][:stack_trace].first
|
|
393
|
-
end
|
|
394
|
-
|
|
395
|
-
it 'should log an error without backtrace' do
|
|
396
|
-
e = StandardError.new('Connection timed-out')
|
|
397
|
-
@logger.error({ message: 'Artificially raised exception' }, e)
|
|
398
|
-
|
|
399
|
-
actual_log = read_json(@buffer)
|
|
400
|
-
|
|
401
|
-
assert_equal 'Artificially raised exception', actual_log[:message]
|
|
402
|
-
assert_equal 'StandardError', actual_log[:error][:type]
|
|
403
|
-
assert_equal 'Connection timed-out', actual_log[:error][:message]
|
|
404
|
-
refute actual_log[:error].key?(:stack_trace)
|
|
405
|
-
end
|
|
406
|
-
|
|
407
|
-
it 'should log an error with string message' do
|
|
408
|
-
e = StandardError.new('Some error')
|
|
409
|
-
@logger.error('Artificially raised exception with string message', e)
|
|
410
|
-
|
|
411
|
-
actual_log = read_json(@buffer)
|
|
412
|
-
|
|
413
|
-
assert_equal 'Artificially raised exception with string message', actual_log[:message]
|
|
414
|
-
assert_equal 'StandardError', actual_log[:error][:type]
|
|
415
|
-
assert_equal 'Some error', actual_log[:error][:message]
|
|
416
|
-
end
|
|
417
|
-
|
|
418
|
-
it 'should log an error if no message is given' do
|
|
419
|
-
e = StandardError.new('Some error')
|
|
420
|
-
@logger.error(e)
|
|
421
|
-
|
|
422
|
-
actual_log = read_json(@buffer)
|
|
423
|
-
|
|
424
|
-
assert_equal 'Some error', actual_log[:message]
|
|
425
|
-
assert_equal 'StandardError', actual_log[:error][:type]
|
|
426
|
-
assert_equal 'Some error', actual_log[:error][:message]
|
|
427
|
-
end
|
|
428
|
-
|
|
429
|
-
it 'should log an error if nil message is given' do
|
|
430
|
-
e = StandardError.new('Some error')
|
|
431
|
-
@logger.error(nil, e)
|
|
432
|
-
|
|
433
|
-
actual_log = read_json(@buffer)
|
|
434
|
-
|
|
435
|
-
assert_equal 'Some error', actual_log[:message]
|
|
436
|
-
assert_equal 'StandardError', actual_log[:error][:type]
|
|
437
|
-
assert_equal 'Some error', actual_log[:error][:message]
|
|
438
|
-
end
|
|
439
|
-
|
|
440
|
-
it 'should log a string if no error is given' do
|
|
441
|
-
@logger.error('Some error')
|
|
442
|
-
|
|
443
|
-
actual_log = read_json(@buffer)
|
|
444
|
-
|
|
445
|
-
assert_equal 'Some error', actual_log[:message]
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
it 'should log error type properly even when active_support is required' do
|
|
449
|
-
e = StandardError.new('Unknown error')
|
|
450
|
-
@logger.error('Artificially raised exception with string message', e)
|
|
451
|
-
|
|
452
|
-
actual_log = read_json(@buffer)
|
|
453
|
-
|
|
454
|
-
assert_equal 'StandardError', actual_log[:error][:type]
|
|
455
|
-
end
|
|
456
|
-
|
|
457
|
-
[:debug, :info, :warn].each do |level|
|
|
458
|
-
it "can log an error with type, error message etc.. as '#{level}'" do
|
|
459
|
-
error_message = "error to be logged as #{level}"
|
|
460
|
-
e = StandardError.new(error_message)
|
|
461
|
-
@logger.public_send(level, e)
|
|
462
|
-
|
|
463
|
-
actual_log = read_json(@buffer)
|
|
464
|
-
|
|
465
|
-
assert_equal error_message, actual_log[:message]
|
|
466
|
-
assert_equal 'StandardError', actual_log[:error][:type]
|
|
467
|
-
assert_equal error_message, actual_log[:error][:message]
|
|
468
|
-
end
|
|
469
|
-
end
|
|
470
|
-
end
|
|
471
|
-
|
|
472
|
-
describe 'text logging' do
|
|
473
|
-
it 'should throw an error with an empty message' do
|
|
474
|
-
assert_raises JSON::Schema::ValidationError, "The property '#/message' was not of a minimum string length of 1" do
|
|
475
|
-
@logger.info('')
|
|
476
|
-
end
|
|
477
|
-
end
|
|
478
|
-
|
|
479
|
-
it 'should log mandatory attributes' do
|
|
480
|
-
@logger.error('Out of pets exception')
|
|
481
|
-
actual_log = read_json(@buffer)
|
|
482
|
-
|
|
483
|
-
expected_log = {
|
|
484
|
-
message: 'Out of pets exception',
|
|
485
|
-
ecs: {
|
|
486
|
-
version: '1.5.0'
|
|
487
|
-
},
|
|
488
|
-
'@timestamp': '2020-05-11T15:01:01.000Z',
|
|
489
|
-
service: {
|
|
490
|
-
name: 'petshop'
|
|
491
|
-
},
|
|
492
|
-
log: {
|
|
493
|
-
level: 'error'
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
assert_equal expected_log, actual_log
|
|
498
|
-
end
|
|
499
|
-
|
|
500
|
-
it 'should log the provided message' do
|
|
501
|
-
@logger.error('Emergency! Emergency!')
|
|
502
|
-
log = read_json(@buffer)
|
|
503
|
-
|
|
504
|
-
assert_equal 'Emergency! Emergency!', log[:message]
|
|
505
|
-
end
|
|
506
|
-
|
|
507
|
-
LEVELS.each do |attrs|
|
|
508
|
-
it "should correctly log level when calling #{attrs[:method]}" do
|
|
509
|
-
@logger.public_send(attrs[:method], 'a log message')
|
|
510
|
-
actual_log = read_json(@buffer)
|
|
511
|
-
|
|
512
|
-
assert_equal attrs[:level], actual_log[:log][:level]
|
|
513
|
-
assert_equal 'a log message', actual_log[:message]
|
|
514
|
-
end
|
|
515
|
-
end
|
|
516
|
-
end
|
|
517
|
-
|
|
518
|
-
describe 'logging with a block' do
|
|
519
|
-
LEVELS.each do |attrs|
|
|
520
|
-
it "should correctly log the block when calling #{attrs[:method]}" do
|
|
521
|
-
block = proc { 'a block log message' }
|
|
522
|
-
@logger.public_send(attrs[:method], &block)
|
|
523
|
-
actual_log = read_json(@buffer)
|
|
524
|
-
|
|
525
|
-
assert_equal attrs[:level], actual_log[:log][:level]
|
|
526
|
-
assert_equal 'a block log message', actual_log[:message]
|
|
527
|
-
end
|
|
528
|
-
end
|
|
529
|
-
|
|
530
|
-
it 'should ignore the given progname if a block is also given' do
|
|
531
|
-
block = proc { 'a block log message' }
|
|
532
|
-
@logger.info('my-program-name', &block)
|
|
533
|
-
actual_log = read_json(@buffer)
|
|
534
|
-
|
|
535
|
-
assert_equal 'info', actual_log[:log][:level]
|
|
536
|
-
assert_equal 'a block log message', actual_log[:message]
|
|
537
|
-
end
|
|
538
|
-
end
|
|
539
|
-
|
|
540
|
-
describe 'dotted keys' do
|
|
541
|
-
it 'should be able to convert dotted keys to nested objects' do
|
|
542
|
-
@logger.debug(
|
|
543
|
-
{
|
|
544
|
-
'trace.id': '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
|
|
545
|
-
message: 'customer bought a dog',
|
|
546
|
-
'pet.name': 'Barker',
|
|
547
|
-
'pet.species': 'dog',
|
|
548
|
-
'pet.breed': 'Bitsa'
|
|
549
|
-
}
|
|
550
|
-
)
|
|
551
|
-
log = read_json(@buffer)
|
|
552
|
-
|
|
553
|
-
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
|
554
|
-
assert_equal 'customer bought a dog', log[:message]
|
|
555
|
-
assert_equal 'Barker', log[:pet][:name]
|
|
556
|
-
assert_equal 'dog', log[:pet][:species]
|
|
557
|
-
assert_equal 'Bitsa', log[:pet][:breed]
|
|
558
|
-
end
|
|
559
|
-
|
|
560
|
-
it 'should be able to mix dotted keys and nested objects' do
|
|
561
|
-
@logger.debug(
|
|
562
|
-
{
|
|
563
|
-
'trace.id': '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
|
|
564
|
-
message: 'customer bought a dog',
|
|
565
|
-
pet: { name: 'Barker', breed: 'Bitsa' },
|
|
566
|
-
'pet.species': 'dog'
|
|
567
|
-
}
|
|
568
|
-
)
|
|
569
|
-
log = read_json(@buffer)
|
|
570
|
-
|
|
571
|
-
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
|
572
|
-
assert_equal 'customer bought a dog', log[:message]
|
|
573
|
-
assert_equal 'Barker', log[:pet][:name]
|
|
574
|
-
assert_equal 'dog', log[:pet][:species]
|
|
575
|
-
assert_equal 'Bitsa', log[:pet][:breed]
|
|
576
|
-
end
|
|
577
|
-
end
|
|
578
|
-
|
|
579
|
-
describe 'logger level' do
|
|
580
|
-
[
|
|
581
|
-
{ expression: :info, level: 1 },
|
|
582
|
-
{ expression: 'warn', level: 2 },
|
|
583
|
-
{ expression: Logger::DEBUG, level: 0 }
|
|
584
|
-
].each do |args|
|
|
585
|
-
it "sets the severity threshold to level #{args[:level]}" do
|
|
586
|
-
@logger.level = args[:expression]
|
|
587
|
-
assert_equal args[:level], @logger.level
|
|
588
|
-
end
|
|
589
|
-
end
|
|
590
|
-
|
|
591
|
-
it 'initializes the logger with the provided level' do
|
|
592
|
-
assert_equal Logger::WARN, Twiglet::Logger.new('petshop', level: :warn).level
|
|
593
|
-
end
|
|
594
|
-
|
|
595
|
-
it 'does not log lower level' do
|
|
596
|
-
logger = Twiglet::Logger.new(
|
|
597
|
-
'petshop',
|
|
598
|
-
now: @now,
|
|
599
|
-
output: @buffer,
|
|
600
|
-
level: Logger::INFO
|
|
601
|
-
)
|
|
602
|
-
logger.debug({ name: 'Davis', best_boy_or_girl?: true, species: 'dog' })
|
|
603
|
-
assert_empty @buffer.read
|
|
604
|
-
end
|
|
605
|
-
end
|
|
606
|
-
|
|
607
|
-
describe 'configuring error response' do
|
|
608
|
-
it 'blows up by default' do
|
|
609
|
-
assert_raises JSON::Schema::ValidationError,
|
|
610
|
-
"The property '#/message' of type boolean did not match the following type: string" do
|
|
611
|
-
@logger.debug(message: true)
|
|
612
|
-
end
|
|
613
|
-
end
|
|
614
|
-
|
|
615
|
-
it 'silently swallows errors when configured to do so' do
|
|
616
|
-
mock = Minitest::Mock.new
|
|
617
|
-
|
|
618
|
-
@logger.configure_validation_error_response do |_e|
|
|
619
|
-
mock.notify_error("Logging schema validation error")
|
|
620
|
-
end
|
|
621
|
-
|
|
622
|
-
mock.expect(:notify_error, nil, ["Logging schema validation error"])
|
|
623
|
-
nonconformant_log = { message: true }
|
|
624
|
-
@logger.debug(nonconformant_log)
|
|
625
|
-
end
|
|
626
|
-
end
|
|
627
|
-
|
|
628
|
-
describe 'validation schema' do
|
|
629
|
-
before do
|
|
630
|
-
validation_schema = <<-JSON
|
|
631
|
-
{
|
|
632
|
-
"type": "object",
|
|
633
|
-
"required": ["pet"],
|
|
634
|
-
"properties": {
|
|
635
|
-
"pet": {
|
|
636
|
-
"type": "object",
|
|
637
|
-
"required": ["name", "best_boy_or_girl?"],
|
|
638
|
-
"properties": {
|
|
639
|
-
"name": {
|
|
640
|
-
"type": "string",
|
|
641
|
-
"minLength": 1
|
|
642
|
-
},
|
|
643
|
-
"best_boy_or_girl?": {
|
|
644
|
-
"type": "boolean"
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
JSON
|
|
651
|
-
|
|
652
|
-
@logger = Twiglet::Logger.new(
|
|
653
|
-
'petshop',
|
|
654
|
-
now: @now,
|
|
655
|
-
output: @buffer,
|
|
656
|
-
validation_schema: validation_schema,
|
|
657
|
-
level: Logger::DEBUG
|
|
658
|
-
)
|
|
659
|
-
end
|
|
660
|
-
|
|
661
|
-
it 'allows for the configuration of custom validation rules' do
|
|
662
|
-
@logger.debug(
|
|
663
|
-
{
|
|
664
|
-
pet: { name: 'Davis', best_boy_or_girl?: true, species: 'dog' }
|
|
665
|
-
}
|
|
666
|
-
)
|
|
667
|
-
log = read_json(@buffer)
|
|
668
|
-
|
|
669
|
-
assert_equal true, log[:pet][:best_boy_or_girl?]
|
|
670
|
-
end
|
|
671
|
-
|
|
672
|
-
it 'raises when custom validation rules are broken' do
|
|
673
|
-
nonconformant = {
|
|
674
|
-
pet: { name: 'Davis' }
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
assert_raises JSON::Schema::ValidationError,
|
|
678
|
-
"The property '#/pet' did not contain a required property of 'best_boy_or_girl?'" do
|
|
679
|
-
@logger.debug(nonconformant)
|
|
680
|
-
end
|
|
681
|
-
end
|
|
682
|
-
end
|
|
683
|
-
|
|
684
|
-
describe '#validation_schema' do
|
|
685
|
-
it 'allows for reconfiguring the validation_schema on new logger instances' do
|
|
686
|
-
validation_schema = <<-JSON
|
|
687
|
-
{
|
|
688
|
-
"type": "object",
|
|
689
|
-
"required": ["pet"],
|
|
690
|
-
"properties": {
|
|
691
|
-
"pet": {
|
|
692
|
-
"type": "object",
|
|
693
|
-
"required": ["name", "best_boy_or_girl?"],
|
|
694
|
-
"properties": {
|
|
695
|
-
"name": {
|
|
696
|
-
"type": "string",
|
|
697
|
-
"minLength": 1
|
|
698
|
-
},
|
|
699
|
-
"best_boy_or_girl?": {
|
|
700
|
-
"type": "boolean"
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
JSON
|
|
707
|
-
|
|
708
|
-
logger = Twiglet::Logger.new(
|
|
709
|
-
'petshop',
|
|
710
|
-
now: @now,
|
|
711
|
-
output: @buffer
|
|
712
|
-
)
|
|
713
|
-
|
|
714
|
-
message = { message: 'hi' }
|
|
715
|
-
|
|
716
|
-
pet_message = {
|
|
717
|
-
pet: { name: 'Davis', best_boy_or_girl?: true, species: 'dog' }
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
logger.info(message)
|
|
721
|
-
log = read_json(@buffer)
|
|
722
|
-
assert_equal 'hi', log[:message]
|
|
723
|
-
|
|
724
|
-
error = assert_raises JSON::Schema::ValidationError do
|
|
725
|
-
logger.info(pet_message)
|
|
726
|
-
end
|
|
727
|
-
assert_equal "The property '#/' did not contain a required property of 'message'", error.message
|
|
728
|
-
|
|
729
|
-
logger = logger.validation_schema(validation_schema)
|
|
730
|
-
|
|
731
|
-
error = assert_raises JSON::Schema::ValidationError do
|
|
732
|
-
logger.info(message)
|
|
733
|
-
end
|
|
734
|
-
assert_equal "The property '#/' did not contain a required property of 'pet'", error.message
|
|
735
|
-
|
|
736
|
-
logger.info(pet_message)
|
|
737
|
-
log = read_json(@buffer)
|
|
738
|
-
assert_equal 'Davis', log[:pet][:name]
|
|
739
|
-
end
|
|
740
|
-
end
|
|
741
|
-
|
|
742
|
-
private
|
|
743
|
-
|
|
744
|
-
def read_json(buffer)
|
|
745
|
-
buffer.rewind
|
|
746
|
-
string = buffer.read
|
|
747
|
-
buffer.rewind
|
|
748
|
-
JSON.parse(string, symbolize_names: true)
|
|
749
|
-
end
|
|
750
|
-
end
|
|
751
|
-
# rubocop:enable Metrics/BlockLength
|