twiglet 2.0.0 → 2.1.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/.rubocop.yml +1 -1
- data/README.md +23 -0
- data/example_app.rb +3 -0
- data/lib/twiglet/logger.rb +27 -6
- data/lib/twiglet/version.rb +1 -1
- data/test/logger_test.rb +204 -157
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9a94a60f7f04b8a19fd27f483e9f8e29992a237fa03491ad417a385032b98401
|
4
|
+
data.tar.gz: 976a134b1c69aa19956b5979affa35fc0141cb5164a8fa67a0aa91da1f490b2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 93c304efe8eeccbe2f6ed826499a288eee8d07897c79e1c0aca1f16e3908ea136a1731db7318228e5fa819b3cdafe6b196926a0fd1f9d387f94f7a315b0e868b
|
7
|
+
data.tar.gz: 74b4d3ec698510d3a236a159bf92fe1b0d07d6caf60fce91e46d24d89680ea5c93ffaee042606c5560d72709148756912d011405c113f51213f959c449d9e0ba
|
data/.rubocop.yml
CHANGED
data/README.md
CHANGED
@@ -40,6 +40,17 @@ This will write to STDOUT a JSON string:
|
|
40
40
|
|
41
41
|
Obviously the timestamp will be different.
|
42
42
|
|
43
|
+
Alternatively, if you just want to log some error message in text format
|
44
|
+
```ruby
|
45
|
+
logger.error( "Emergency! There's an Emergency going on")
|
46
|
+
```
|
47
|
+
|
48
|
+
This will write to STDOUT a JSON string:
|
49
|
+
|
50
|
+
```json
|
51
|
+
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:54:59.164+01:00","log":{"level":"error"}, "message":"Emergency! There's an Emergency going on"}
|
52
|
+
```
|
53
|
+
|
43
54
|
Errors can be logged as well, and this will log the error message and backtrace in the relevant ECS compliant fields:
|
44
55
|
|
45
56
|
```ruby
|
@@ -68,6 +79,18 @@ This writes:
|
|
68
79
|
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:56:49.527+01:00","log":{"level":"info"},"event":{"action":"HTTP request"},"message":"GET /pets success","trace":{"id":"1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb"},"http":{"request":{"method":"get"},"response":{"status_code":200}},"url":{"path":"/pets"}}
|
69
80
|
```
|
70
81
|
|
82
|
+
Similar to error you can use text logging here as:
|
83
|
+
|
84
|
+
```
|
85
|
+
logger.info('GET /pets success')
|
86
|
+
```
|
87
|
+
This writes:
|
88
|
+
|
89
|
+
```json
|
90
|
+
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:56:49.527+01:00","log":{"level":"info"}}
|
91
|
+
```
|
92
|
+
|
93
|
+
|
71
94
|
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:
|
72
95
|
|
73
96
|
```ruby
|
data/example_app.rb
CHANGED
data/lib/twiglet/logger.rb
CHANGED
@@ -55,6 +55,8 @@ module Twiglet
|
|
55
55
|
log(level: 'critical', message: message)
|
56
56
|
end
|
57
57
|
|
58
|
+
alias_method :fatal, :critical
|
59
|
+
|
58
60
|
def with(default_properties)
|
59
61
|
Logger.new(@service_name,
|
60
62
|
default_properties: default_properties,
|
@@ -65,27 +67,46 @@ module Twiglet
|
|
65
67
|
private
|
66
68
|
|
67
69
|
def log(level:, message:)
|
68
|
-
|
70
|
+
case message
|
71
|
+
when String
|
72
|
+
log_text(level, message: message)
|
73
|
+
when Hash
|
74
|
+
log_object(level, message: message)
|
75
|
+
else
|
76
|
+
raise('Message must be String or Hash')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def log_text(level, message:)
|
81
|
+
raise('The \'message\' property of log object must not be empty') if message.strip.empty?
|
82
|
+
|
83
|
+
message = { message: message }
|
84
|
+
log_message(level, message: message)
|
85
|
+
end
|
69
86
|
|
87
|
+
def log_object(level, message:)
|
70
88
|
message = message.transform_keys(&:to_sym)
|
71
89
|
message.key?(:message) || raise('Log object must have a \'message\' property')
|
72
|
-
|
73
90
|
message[:message].strip.empty? && raise('The \'message\' property of log object must not be empty')
|
74
91
|
|
92
|
+
log_message(level, message: message)
|
93
|
+
end
|
94
|
+
|
95
|
+
def log_message(level, message:)
|
75
96
|
base_message = {
|
97
|
+
"@timestamp": @now.call.iso8601(3),
|
76
98
|
service: {
|
77
99
|
name: @service_name
|
78
100
|
},
|
79
|
-
"@timestamp": @now.call.iso8601(3),
|
80
101
|
log: {
|
81
102
|
level: level
|
82
103
|
}
|
83
104
|
}
|
84
105
|
|
85
106
|
@output.puts base_message
|
86
|
-
|
87
|
-
|
88
|
-
|
107
|
+
.deep_merge(@default_properties.to_nested)
|
108
|
+
.deep_merge(message.to_nested)
|
109
|
+
.to_json
|
89
110
|
end
|
90
111
|
end
|
91
112
|
end
|
data/lib/twiglet/version.rb
CHANGED
data/test/logger_test.rb
CHANGED
@@ -12,191 +12,238 @@ describe Twiglet::Logger do
|
|
12
12
|
output: @buffer)
|
13
13
|
end
|
14
14
|
|
15
|
+
LEVELS = [
|
16
|
+
{ method: :debug, level: 'debug' },
|
17
|
+
{ method: :info, level: 'info' },
|
18
|
+
{ method: :warning, level: 'warning' },
|
19
|
+
{ method: :warn, level: 'warning' },
|
20
|
+
{ method: :critical, level: 'critical' },
|
21
|
+
{ method: :fatal, level: 'critical' },
|
22
|
+
{ method: :error, level: 'error' }
|
23
|
+
].freeze
|
24
|
+
|
15
25
|
it 'should throw an error with an empty service name' do
|
16
26
|
assert_raises RuntimeError do
|
17
27
|
Twiglet::Logger.new(' ')
|
18
28
|
end
|
19
29
|
end
|
20
30
|
|
21
|
-
|
22
|
-
|
23
|
-
|
31
|
+
describe 'JSON logging' do
|
32
|
+
it 'should throw an error with an empty message' do
|
33
|
+
assert_raises RuntimeError do
|
34
|
+
@logger.info({message: ''})
|
35
|
+
end
|
24
36
|
end
|
25
|
-
end
|
26
37
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
38
|
+
it 'should log mandatory attributes' do
|
39
|
+
@logger.error({message: 'Out of pets exception'})
|
40
|
+
actual_log = read_json(@buffer)
|
41
|
+
|
42
|
+
expected_log = {
|
43
|
+
message: 'Out of pets exception',
|
44
|
+
"@timestamp": '2020-05-11T15:01:01.000Z',
|
45
|
+
service: {
|
46
|
+
name: 'petshop'
|
47
|
+
},
|
48
|
+
log: {
|
49
|
+
level: 'error'
|
50
|
+
}
|
39
51
|
}
|
40
|
-
}
|
41
52
|
|
42
|
-
|
43
|
-
|
53
|
+
assert_equal expected_log, actual_log
|
54
|
+
end
|
44
55
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
56
|
+
it 'should log the provided message' do
|
57
|
+
@logger.error({event:
|
58
|
+
{action: 'exception'},
|
59
|
+
message: 'Emergency! Emergency!'})
|
60
|
+
log = read_json(@buffer)
|
50
61
|
|
51
|
-
|
52
|
-
|
53
|
-
|
62
|
+
assert_equal 'exception', log[:event][:action]
|
63
|
+
assert_equal 'Emergency! Emergency!', log[:message]
|
64
|
+
end
|
54
65
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
output = StringIO.new
|
68
|
-
logger = Twiglet::Logger.new('petshop',
|
69
|
-
now: @now,
|
70
|
-
output: output,
|
71
|
-
default_properties: extra_properties)
|
72
|
-
|
73
|
-
logger.error({message: 'GET /cats'})
|
74
|
-
log = read_json output
|
75
|
-
|
76
|
-
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
77
|
-
assert_equal 'petshop', log[:service][:name]
|
78
|
-
assert_equal 'shop', log[:service][:type]
|
79
|
-
assert_equal 'get', log[:request][:method]
|
80
|
-
assert_equal 200, log[:response][:status_code]
|
81
|
-
end
|
66
|
+
it 'should log scoped properties defined at creation' do
|
67
|
+
extra_properties = {
|
68
|
+
trace: {
|
69
|
+
id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'
|
70
|
+
},
|
71
|
+
service: {
|
72
|
+
type: 'shop'
|
73
|
+
},
|
74
|
+
request: {method: 'get'},
|
75
|
+
response: {status_code: 200}
|
76
|
+
}
|
82
77
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
event: {action: 'pet purchase'}
|
89
|
-
})
|
90
|
-
|
91
|
-
# do stuff
|
92
|
-
purchase_logger.info({
|
93
|
-
message: 'customer bought a dog',
|
94
|
-
pet: {name: 'Barker', species: 'dog', breed: 'Bitsa'}
|
95
|
-
})
|
96
|
-
|
97
|
-
log = read_json @buffer
|
98
|
-
|
99
|
-
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
100
|
-
assert_equal 'Freda Bloggs', log[:customer][:full_name]
|
101
|
-
assert_equal 'pet purchase', log[:event][:action]
|
102
|
-
assert_equal 'customer bought a dog', log[:message]
|
103
|
-
assert_equal 'Barker', log[:pet][:name]
|
104
|
-
end
|
78
|
+
output = StringIO.new
|
79
|
+
logger = Twiglet::Logger.new('petshop',
|
80
|
+
now: @now,
|
81
|
+
output: output,
|
82
|
+
default_properties: extra_properties)
|
105
83
|
|
106
|
-
|
107
|
-
|
108
|
-
message['message'] = 'Guinea pigs arrived'
|
109
|
-
@logger.debug(message)
|
110
|
-
log = read_json(@buffer)
|
84
|
+
logger.error({message: 'GET /cats'})
|
85
|
+
log = read_json output
|
111
86
|
|
112
|
-
|
113
|
-
|
87
|
+
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
88
|
+
assert_equal 'petshop', log[:service][:name]
|
89
|
+
assert_equal 'shop', log[:service][:type]
|
90
|
+
assert_equal 'get', log[:request][:method]
|
91
|
+
assert_equal 200, log[:response][:status_code]
|
92
|
+
end
|
114
93
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
94
|
+
it "should be able to add properties with '.with'" do
|
95
|
+
# Let's add some context to this customer journey
|
96
|
+
purchase_logger = @logger.with({
|
97
|
+
trace: {id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'},
|
98
|
+
customer: {full_name: 'Freda Bloggs'},
|
99
|
+
event: {action: 'pet purchase'}
|
100
|
+
})
|
101
|
+
|
102
|
+
# do stuff
|
103
|
+
purchase_logger.info({
|
104
|
+
message: 'customer bought a dog',
|
105
|
+
pet: {name: 'Barker', species: 'dog', breed: 'Bitsa'}
|
106
|
+
})
|
107
|
+
|
108
|
+
log = read_json @buffer
|
109
|
+
|
110
|
+
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
111
|
+
assert_equal 'Freda Bloggs', log[:customer][:full_name]
|
112
|
+
assert_equal 'pet purchase', log[:event][:action]
|
113
|
+
assert_equal 'customer bought a dog', log[:message]
|
114
|
+
assert_equal 'Barker', log[:pet][:name]
|
115
|
+
end
|
131
116
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
"pet.species": 'dog'
|
138
|
-
})
|
139
|
-
log = read_json(@buffer)
|
140
|
-
|
141
|
-
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
142
|
-
assert_equal 'customer bought a dog', log[:message]
|
143
|
-
assert_equal 'Barker', log[:pet][:name]
|
144
|
-
assert_equal 'dog', log[:pet][:species]
|
145
|
-
assert_equal 'Bitsa', log[:pet][:breed]
|
146
|
-
end
|
117
|
+
it "should log 'message' string property" do
|
118
|
+
message = {}
|
119
|
+
message['message'] = 'Guinea pigs arrived'
|
120
|
+
@logger.debug(message)
|
121
|
+
log = read_json(@buffer)
|
147
122
|
|
148
|
-
|
149
|
-
|
150
|
-
"trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'
|
151
|
-
}
|
152
|
-
event = {}
|
153
|
-
log['event'] = event
|
154
|
-
log['message'] = 'customer bought a dog'
|
155
|
-
pet = {}
|
156
|
-
pet['name'] = 'Barker'
|
157
|
-
pet['breed'] = 'Bitsa'
|
158
|
-
pet[:species] = 'dog'
|
159
|
-
log[:pet] = pet
|
160
|
-
|
161
|
-
@logger.debug(log)
|
162
|
-
actual_log = read_json(@buffer)
|
163
|
-
|
164
|
-
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', actual_log[:trace][:id]
|
165
|
-
assert_equal 'customer bought a dog', actual_log[:message]
|
166
|
-
assert_equal 'Barker', actual_log[:pet][:name]
|
167
|
-
assert_equal 'dog', actual_log[:pet][:species]
|
168
|
-
assert_equal 'Bitsa', actual_log[:pet][:breed]
|
169
|
-
end
|
123
|
+
assert_equal 'Guinea pigs arrived', log[:message]
|
124
|
+
end
|
170
125
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
126
|
+
it 'should be able to convert dotted keys to nested objects' do
|
127
|
+
@logger.debug({
|
128
|
+
"trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
|
129
|
+
message: 'customer bought a dog',
|
130
|
+
"pet.name": 'Barker',
|
131
|
+
"pet.species": 'dog',
|
132
|
+
"pet.breed": 'Bitsa'
|
133
|
+
})
|
134
|
+
log = read_json(@buffer)
|
135
|
+
|
136
|
+
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
137
|
+
assert_equal 'customer bought a dog', log[:message]
|
138
|
+
assert_equal 'Barker', log[:pet][:name]
|
139
|
+
assert_equal 'dog', log[:pet][:species]
|
140
|
+
assert_equal 'Bitsa', log[:pet][:breed]
|
176
141
|
end
|
177
142
|
|
178
|
-
|
143
|
+
it 'should be able to mix dotted keys and nested objects' do
|
144
|
+
@logger.debug({
|
145
|
+
"trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
|
146
|
+
message: 'customer bought a dog',
|
147
|
+
pet: {name: 'Barker', breed: 'Bitsa'},
|
148
|
+
"pet.species": 'dog'
|
149
|
+
})
|
150
|
+
log = read_json(@buffer)
|
151
|
+
|
152
|
+
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
153
|
+
assert_equal 'customer bought a dog', log[:message]
|
154
|
+
assert_equal 'Barker', log[:pet][:name]
|
155
|
+
assert_equal 'dog', log[:pet][:species]
|
156
|
+
assert_equal 'Bitsa', log[:pet][:breed]
|
157
|
+
end
|
179
158
|
|
180
|
-
|
181
|
-
|
182
|
-
|
159
|
+
it 'should work with mixed string and symbol properties' do
|
160
|
+
log = {
|
161
|
+
"trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'
|
162
|
+
}
|
163
|
+
event = {}
|
164
|
+
log['event'] = event
|
165
|
+
log['message'] = 'customer bought a dog'
|
166
|
+
pet = {}
|
167
|
+
pet['name'] = 'Barker'
|
168
|
+
pet['breed'] = 'Bitsa'
|
169
|
+
pet[:species] = 'dog'
|
170
|
+
log[:pet] = pet
|
171
|
+
|
172
|
+
@logger.debug(log)
|
173
|
+
actual_log = read_json(@buffer)
|
174
|
+
|
175
|
+
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', actual_log[:trace][:id]
|
176
|
+
assert_equal 'customer bought a dog', actual_log[:message]
|
177
|
+
assert_equal 'Barker', actual_log[:pet][:name]
|
178
|
+
assert_equal 'dog', actual_log[:pet][:species]
|
179
|
+
assert_equal 'Bitsa', actual_log[:pet][:breed]
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'should log an error with backtrace' do
|
183
|
+
begin
|
184
|
+
1 / 0
|
185
|
+
rescue StandardError => e
|
186
|
+
@logger.error({message: 'Artificially raised exception'}, e)
|
187
|
+
end
|
188
|
+
|
189
|
+
actual_log = read_json(@buffer)
|
190
|
+
|
191
|
+
assert_equal 'Artificially raised exception', actual_log[:message]
|
192
|
+
assert_equal 'divided by 0', actual_log[:error][:message]
|
193
|
+
assert_match 'logger_test.rb', actual_log[:error][:stack_trace].lines.first
|
194
|
+
end
|
195
|
+
|
196
|
+
LEVELS.each do |attrs|
|
197
|
+
it "should correctly log level when calling #{attrs[:method]}" do
|
198
|
+
@logger.public_send(attrs[:method], {message: 'a log message'})
|
199
|
+
actual_log = read_json(@buffer)
|
200
|
+
|
201
|
+
assert_equal attrs[:level], actual_log[:log][:level]
|
202
|
+
assert_equal 'a log message', actual_log[:message]
|
203
|
+
end
|
204
|
+
end
|
183
205
|
end
|
184
206
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
]
|
207
|
+
describe 'text logging' do
|
208
|
+
it 'should throw an error with an empty message' do
|
209
|
+
assert_raises RuntimeError do
|
210
|
+
@logger.info('')
|
211
|
+
end
|
212
|
+
end
|
192
213
|
|
193
|
-
|
194
|
-
|
195
|
-
@logger.public_send(attrs[:method], {message: 'a log message'})
|
214
|
+
it 'should log mandatory attributes' do
|
215
|
+
@logger.error('Out of pets exception')
|
196
216
|
actual_log = read_json(@buffer)
|
197
217
|
|
198
|
-
|
199
|
-
|
218
|
+
expected_log = {
|
219
|
+
message: 'Out of pets exception',
|
220
|
+
"@timestamp": '2020-05-11T15:01:01.000Z',
|
221
|
+
service: {
|
222
|
+
name: 'petshop'
|
223
|
+
},
|
224
|
+
log: {
|
225
|
+
level: 'error'
|
226
|
+
}
|
227
|
+
}
|
228
|
+
|
229
|
+
assert_equal expected_log, actual_log
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'should log the provided message' do
|
233
|
+
@logger.error('Emergency! Emergency!')
|
234
|
+
log = read_json(@buffer)
|
235
|
+
|
236
|
+
assert_equal 'Emergency! Emergency!', log[:message]
|
237
|
+
end
|
238
|
+
|
239
|
+
LEVELS.each do |attrs|
|
240
|
+
it "should correctly log level when calling #{attrs[:method]}" do
|
241
|
+
@logger.public_send(attrs[:method], 'a log message')
|
242
|
+
actual_log = read_json(@buffer)
|
243
|
+
|
244
|
+
assert_equal attrs[:level], actual_log[:log][:level]
|
245
|
+
assert_equal 'a log message', actual_log[:message]
|
246
|
+
end
|
200
247
|
end
|
201
248
|
end
|
202
249
|
|
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: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simply Business
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-06-
|
11
|
+
date: 2020-06-10 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Like a log, only smaller.
|
14
14
|
email:
|
@@ -55,7 +55,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
55
|
- !ruby/object:Gem::Version
|
56
56
|
version: '0'
|
57
57
|
requirements: []
|
58
|
-
rubygems_version: 3.
|
58
|
+
rubygems_version: 3.0.3
|
59
59
|
signing_key:
|
60
60
|
specification_version: 4
|
61
61
|
summary: Twiglet
|