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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f7e6901994e7715bab639be94853dbbcce697e16da26f2f8340404ebe4c0211
4
- data.tar.gz: 1758572f62c689a68642cfb8887dc2ea380a3602332cf2321b06d51e80a7066d
3
+ metadata.gz: 9a94a60f7f04b8a19fd27f483e9f8e29992a237fa03491ad417a385032b98401
4
+ data.tar.gz: 976a134b1c69aa19956b5979affa35fc0141cb5164a8fa67a0aa91da1f490b2a
5
5
  SHA512:
6
- metadata.gz: 808fb4ddbdaff2287331e022ec4a593469335a40476fa59cb0546facd126113c6b564f7538c3366984bb40ed178a131861b0a97e9ea571640caa68db0354a906
7
- data.tar.gz: 83913e66900495ee39ba12d8f38e16e83fbc3621c4caf7bdaa128c6b44568f82face4498f1b2081e798ff6265b79a7758dd97eff08bd6224e8b0b7d077e8cb81
6
+ metadata.gz: 93c304efe8eeccbe2f6ed826499a288eee8d07897c79e1c0aca1f16e3908ea136a1731db7318228e5fa819b3cdafe6b196926a0fd1f9d387f94f7a315b0e868b
7
+ data.tar.gz: 74b4d3ec698510d3a236a159bf92fe1b0d07d6caf60fce91e46d24d89680ea5c93ffaee042606c5560d72709148756912d011405c113f51213f959c449d9e0ba
@@ -8,7 +8,7 @@ AllCops:
8
8
  Documentation:
9
9
  Enabled: false
10
10
  Metrics/BlockLength:
11
- Max: 200
11
+ Max: 250
12
12
  Metrics/AbcSize:
13
13
  Max: 20
14
14
  Metrics/MethodLength:
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
@@ -17,6 +17,9 @@ logger.info({
17
17
  }
18
18
  })
19
19
 
20
+ # Use text logging
21
+ logger.info("Ready to go, listening on port #{PORT}")
22
+ #
20
23
  # We get a request
21
24
  request_logger = logger.with({
22
25
  event: {
@@ -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
- raise 'Message must be a Hash' unless message.is_a?(Hash)
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
- .deep_merge(@default_properties.to_nested)
87
- .deep_merge(message.to_nested)
88
- .to_json
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Twiglet
4
- VERSION = '2.0.0'
4
+ VERSION = '2.1.0'
5
5
  end
@@ -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
- it 'should throw an error with an empty message' do
22
- assert_raises RuntimeError do
23
- @logger.info('')
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
- it 'should log mandatory attributes' do
28
- @logger.error({message: 'Out of pets exception'})
29
- actual_log = read_json(@buffer)
30
-
31
- expected_log = {
32
- message: 'Out of pets exception',
33
- "@timestamp": '2020-05-11T15:01:01.000Z',
34
- service: {
35
- name: 'petshop'
36
- },
37
- log: {
38
- level: 'error'
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
- assert_equal expected_log, actual_log
43
- end
53
+ assert_equal expected_log, actual_log
54
+ end
44
55
 
45
- it 'should log the provided message' do
46
- @logger.error({event:
47
- {action: 'exception'},
48
- message: 'Emergency! Emergency!'})
49
- log = read_json(@buffer)
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
- assert_equal 'exception', log[:event][:action]
52
- assert_equal 'Emergency! Emergency!', log[:message]
53
- end
62
+ assert_equal 'exception', log[:event][:action]
63
+ assert_equal 'Emergency! Emergency!', log[:message]
64
+ end
54
65
 
55
- it 'should log scoped properties defined at creation' do
56
- extra_properties = {
57
- trace: {
58
- id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'
59
- },
60
- service: {
61
- type: 'shop'
62
- },
63
- request: {method: 'get'},
64
- response: {status_code: 200}
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
- it "should be able to add properties with '.with'" do
84
- # Let's add some context to this customer journey
85
- purchase_logger = @logger.with({
86
- trace: {id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'},
87
- customer: {full_name: 'Freda Bloggs'},
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
- it "should log 'message' string property" do
107
- message = {}
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
- assert_equal 'Guinea pigs arrived', log[:message]
113
- end
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
- it 'should be able to convert dotted keys to nested objects' do
116
- @logger.debug({
117
- "trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
118
- message: 'customer bought a dog',
119
- "pet.name": 'Barker',
120
- "pet.species": 'dog',
121
- "pet.breed": 'Bitsa'
122
- })
123
- log = read_json(@buffer)
124
-
125
- assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
126
- assert_equal 'customer bought a dog', log[:message]
127
- assert_equal 'Barker', log[:pet][:name]
128
- assert_equal 'dog', log[:pet][:species]
129
- assert_equal 'Bitsa', log[:pet][:breed]
130
- end
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
- it 'should be able to mix dotted keys and nested objects' do
133
- @logger.debug({
134
- "trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
135
- message: 'customer bought a dog',
136
- pet: {name: 'Barker', breed: 'Bitsa'},
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
- it 'should work with mixed string and symbol properties' do
149
- log = {
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
- it 'should log an error with backtrace' do
172
- begin
173
- 1 / 0
174
- rescue StandardError => e
175
- @logger.error({message: 'Artificially raised exception'}, e)
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
- actual_log = read_json(@buffer)
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
- assert_equal 'Artificially raised exception', actual_log[:message]
181
- assert_equal 'divided by 0', actual_log[:error][:message]
182
- assert_match 'logger_test.rb', actual_log[:error][:stack_trace].lines.first
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
- levels = [
186
- { method: :debug, level: 'debug' },
187
- { method: :info, level: 'info' },
188
- { method: :warning, level: 'warning' },
189
- { method: :warn, level: 'warning' },
190
- { method: :error, level: 'error' }
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
- levels.each do |attrs|
194
- it "should correctly log level when calling #{attrs[:method]}" do
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
- assert_equal attrs[:level], actual_log[:log][:level]
199
- assert_equal 'a log message', actual_log[:message]
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.0.0
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-05 00:00:00.000000000 Z
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.1.2
58
+ rubygems_version: 3.0.3
59
59
  signing_key:
60
60
  specification_version: 4
61
61
  summary: Twiglet