twiglet 2.0.0 → 2.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f7e6901994e7715bab639be94853dbbcce697e16da26f2f8340404ebe4c0211
4
- data.tar.gz: 1758572f62c689a68642cfb8887dc2ea380a3602332cf2321b06d51e80a7066d
3
+ metadata.gz: 9bd7e1ea9d6464abb4dcb96e8d2679ce6c82ebc289c55cb5989d8f8f657f065f
4
+ data.tar.gz: 2b2d8e963cf250c0b746dd9d56c40368e31f67b1deaf6cd63c9dee9a731ce804
5
5
  SHA512:
6
- metadata.gz: 808fb4ddbdaff2287331e022ec4a593469335a40476fa59cb0546facd126113c6b564f7538c3366984bb40ed178a131861b0a97e9ea571640caa68db0354a906
7
- data.tar.gz: 83913e66900495ee39ba12d8f38e16e83fbc3621c4caf7bdaa128c6b44568f82face4498f1b2081e798ff6265b79a7758dd97eff08bd6224e8b0b7d077e8cb81
6
+ metadata.gz: 8277fd64f609bb4edbf6c95e0f97dc56784a1ea63415c1b261dd9649a69fd6cfee54abbb0c91dfb2232f78f0a14b9c539592d4ca58d57d566350f2a0dbceb648
7
+ data.tar.gz: e9daae207235db6582162e7d2682cf69ea59e5dc09796b7d804bed6f31775ff07ceaff4b38518dd7c6d6a1ec40759c20dadcbebd388417187bcccb66c09fe793
@@ -3,8 +3,6 @@ name: Ruby CI
3
3
  on:
4
4
  push:
5
5
  branches:
6
- - '*' # matches every branch
7
- - '*/*' # matches every branch containing a single '/'
8
6
 
9
7
  jobs:
10
8
  build:
@@ -30,3 +28,6 @@ jobs:
30
28
  - name: Run all tests
31
29
  run: bundle exec rake test
32
30
  shell: bash
31
+ - name: Run example_app
32
+ run: bundle exec ruby example_app.rb
33
+ shell: bash
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  *.gem
2
+ Gemfile.lock
2
3
  *.rbc
3
4
  /.config
4
5
  /coverage/
@@ -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: {
@@ -0,0 +1,69 @@
1
+ require 'logger'
2
+ require_relative '../hash_extensions'
3
+
4
+ module Twiglet
5
+ class Formatter < ::Logger::Formatter
6
+ Hash.include HashExtensions
7
+
8
+ def initialize(service_name,
9
+ default_properties: {},
10
+ now: -> { Time.now.utc })
11
+ @service_name = service_name
12
+ @now = now
13
+ @default_properties = default_properties
14
+
15
+ super()
16
+ end
17
+
18
+ def call(severity, _time, _progname, msg)
19
+ level = severity.downcase
20
+ log(level: level, message: msg)
21
+ end
22
+
23
+ private
24
+
25
+ def log(level:, message:)
26
+ case message
27
+ when String
28
+ log_text(level, message: message)
29
+ when Hash
30
+ log_object(level, message: message)
31
+ else
32
+ raise('Message must be String or Hash')
33
+ end
34
+ end
35
+
36
+ def log_text(level, message:)
37
+ raise('The \'message\' property of log object must not be empty') if message.strip.empty?
38
+
39
+ message = { message: message }
40
+ log_message(level, message: message)
41
+ end
42
+
43
+ def log_object(level, message:)
44
+ message = message.transform_keys(&:to_sym)
45
+ message.key?(:message) || raise('Log object must have a \'message\' property')
46
+ message[:message].strip.empty? && raise('The \'message\' property of log object must not be empty')
47
+
48
+ log_message(level, message: message)
49
+ end
50
+
51
+ def log_message(level, message:)
52
+ base_message = {
53
+ "@timestamp": @now.call.iso8601(3),
54
+ service: {
55
+ name: @service_name
56
+ },
57
+ log: {
58
+ level: level
59
+ }
60
+ }
61
+
62
+ base_message
63
+ .deep_merge(@default_properties.to_nested)
64
+ .deep_merge(message.to_nested)
65
+ .to_json
66
+ .concat("\n")
67
+ end
68
+ end
69
+ end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'logger'
3
4
  require 'time'
4
5
  require 'json'
6
+ require_relative 'formatter'
5
7
  require_relative '../hash_extensions'
6
8
 
7
9
  module Twiglet
8
- class Logger
10
+ class Logger < ::Logger
9
11
  Hash.include HashExtensions
10
12
 
11
13
  def initialize(
@@ -19,40 +21,24 @@ module Twiglet
19
21
  @output = output
20
22
 
21
23
  raise 'Service name is mandatory' \
22
- unless @service_name.is_a?(String) && !@service_name.strip.empty?
24
+ unless service_name.is_a?(String) && !service_name.strip.empty?
23
25
 
24
- @default_properties = default_properties
26
+ formatter = Twiglet::Formatter.new(service_name, default_properties: default_properties, now: now)
27
+ super(output, formatter: formatter)
25
28
  end
26
29
 
27
- def debug(message)
28
- log(level: 'debug', message: message)
29
- end
30
-
31
- def info(message)
32
- log(level: 'info', message: message)
33
- end
34
-
35
- def warning(message)
36
- log(level: 'warning', message: message)
37
- end
38
-
39
- alias_method :warn, :warning
40
-
41
- def error(message, error = nil)
30
+ def error(message = {}, error = nil, &block)
42
31
  if error
43
- message = message.merge({
44
- 'error': {
45
- 'message': error.message,
46
- 'stack_trace': error.backtrace.join("\n")
47
- }
48
- })
32
+ error_fields = {
33
+ 'error': {
34
+ 'message': error.message
35
+ }
36
+ }
37
+ add_stack_trace(error_fields, error)
38
+ message.is_a?(Hash) ? message.merge!(error_fields) : error_fields.merge!(message: message)
49
39
  end
50
40
 
51
- log(level: 'error', message: message)
52
- end
53
-
54
- def critical(message)
55
- log(level: 'critical', message: message)
41
+ super(message, &block)
56
42
  end
57
43
 
58
44
  def with(default_properties)
@@ -62,30 +48,13 @@ module Twiglet
62
48
  output: @output)
63
49
  end
64
50
 
65
- private
66
-
67
- def log(level:, message:)
68
- raise 'Message must be a Hash' unless message.is_a?(Hash)
51
+ alias_method :warning, :warn
52
+ alias_method :critical, :fatal
69
53
 
70
- message = message.transform_keys(&:to_sym)
71
- message.key?(:message) || raise('Log object must have a \'message\' property')
72
-
73
- message[:message].strip.empty? && raise('The \'message\' property of log object must not be empty')
74
-
75
- base_message = {
76
- service: {
77
- name: @service_name
78
- },
79
- "@timestamp": @now.call.iso8601(3),
80
- log: {
81
- level: level
82
- }
83
- }
54
+ private
84
55
 
85
- @output.puts base_message
86
- .deep_merge(@default_properties.to_nested)
87
- .deep_merge(message.to_nested)
88
- .to_json
56
+ def add_stack_trace(hash_to_add_to, error)
57
+ hash_to_add_to[:error][:stack_trace] = error.backtrace.join("\n") if error.backtrace
89
58
  end
90
59
  end
91
60
  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.2.2'
5
5
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'json'
5
+ require_relative '../lib/twiglet/formatter'
6
+
7
+ describe Twiglet::Formatter do
8
+ before do
9
+ @now = -> { Time.utc(2020, 5, 11, 15, 1, 1) }
10
+ @formatter = Twiglet::Formatter.new('petshop', now: @now)
11
+ end
12
+
13
+ it 'initializes an instance of a Ruby Logger Formatter' do
14
+ assert @formatter.is_a?(::Logger::Formatter)
15
+ end
16
+
17
+ it 'returns a formatted log from a string message' do
18
+ msg = @formatter.call('warn', nil, nil, 'shop is running low on dog food')
19
+ expected_log = {
20
+ "@timestamp" => '2020-05-11T15:01:01.000Z',
21
+ "service" => {
22
+ "name" => 'petshop'
23
+ },
24
+ "log" => {
25
+ "level" => 'warn'
26
+ },
27
+ "message" => 'shop is running low on dog food'
28
+ }
29
+ assert_equal JSON.parse(msg), expected_log
30
+ end
31
+ end
@@ -3,6 +3,7 @@
3
3
  require 'minitest/autorun'
4
4
  require_relative '../lib/twiglet/logger'
5
5
 
6
+ # rubocop:disable Metrics/BlockLength
6
7
  describe Twiglet::Logger do
7
8
  before do
8
9
  @now = -> { Time.utc(2020, 5, 11, 15, 1, 1) }
@@ -12,191 +13,297 @@ describe Twiglet::Logger do
12
13
  output: @buffer)
13
14
  end
14
15
 
16
+ LEVELS = [
17
+ { method: :debug, level: 'debug' },
18
+ { method: :info, level: 'info' },
19
+ { method: :warning, level: 'warn' },
20
+ { method: :warn, level: 'warn' },
21
+ { method: :critical, level: 'fatal' },
22
+ { method: :fatal, level: 'fatal' },
23
+ { method: :error, level: 'error' }
24
+ ].freeze
25
+
15
26
  it 'should throw an error with an empty service name' do
16
27
  assert_raises RuntimeError do
17
28
  Twiglet::Logger.new(' ')
18
29
  end
19
30
  end
20
31
 
21
- it 'should throw an error with an empty message' do
22
- assert_raises RuntimeError do
23
- @logger.info('')
32
+ it 'conforms to the standard Ruby Logger API' do
33
+ [:debug, :debug?, :info, :info?, :warn, :warn?, :fatal, :fatal?, :error, :error?,
34
+ :level, :level=, :sev_threshold=].each do |call|
35
+ assert @logger.respond_to?(call), "Logger does not respond to #{call}"
24
36
  end
25
37
  end
26
38
 
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'
39
+ describe 'JSON logging' do
40
+ it 'should throw an error with an empty message' do
41
+ assert_raises RuntimeError do
42
+ @logger.info({message: ''})
43
+ end
44
+ end
45
+
46
+ it 'should log mandatory attributes' do
47
+ @logger.error({message: 'Out of pets exception'})
48
+ actual_log = read_json(@buffer)
49
+
50
+ expected_log = {
51
+ message: 'Out of pets exception',
52
+ "@timestamp": '2020-05-11T15:01:01.000Z',
53
+ service: {
54
+ name: 'petshop'
55
+ },
56
+ log: {
57
+ level: 'error'
58
+ }
39
59
  }
40
- }
41
60
 
42
- assert_equal expected_log, actual_log
43
- end
61
+ assert_equal expected_log, actual_log
62
+ end
44
63
 
45
- it 'should log the provided message' do
46
- @logger.error({event:
47
- {action: 'exception'},
48
- message: 'Emergency! Emergency!'})
49
- log = read_json(@buffer)
64
+ it 'should log the provided message' do
65
+ @logger.error({event:
66
+ {action: 'exception'},
67
+ message: 'Emergency! Emergency!'})
68
+ log = read_json(@buffer)
50
69
 
51
- assert_equal 'exception', log[:event][:action]
52
- assert_equal 'Emergency! Emergency!', log[:message]
53
- end
70
+ assert_equal 'exception', log[:event][:action]
71
+ assert_equal 'Emergency! Emergency!', log[:message]
72
+ end
54
73
 
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
74
+ it 'should log scoped properties defined at creation' do
75
+ extra_properties = {
76
+ trace: {
77
+ id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'
78
+ },
79
+ service: {
80
+ type: 'shop'
81
+ },
82
+ request: {method: 'get'},
83
+ response: {status_code: 200}
84
+ }
82
85
 
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
86
+ output = StringIO.new
87
+ logger = Twiglet::Logger.new('petshop',
88
+ now: @now,
89
+ output: output,
90
+ default_properties: extra_properties)
105
91
 
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)
92
+ logger.error({message: 'GET /cats'})
93
+ log = read_json output
111
94
 
112
- assert_equal 'Guinea pigs arrived', log[:message]
113
- end
95
+ assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
96
+ assert_equal 'petshop', log[:service][:name]
97
+ assert_equal 'shop', log[:service][:type]
98
+ assert_equal 'get', log[:request][:method]
99
+ assert_equal 200, log[:response][:status_code]
100
+ end
114
101
 
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
102
+ it "should be able to add properties with '.with'" do
103
+ # Let's add some context to this customer journey
104
+ purchase_logger = @logger.with({
105
+ trace: {id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'},
106
+ customer: {full_name: 'Freda Bloggs'},
107
+ event: {action: 'pet purchase'}
108
+ })
109
+
110
+ # do stuff
111
+ purchase_logger.info({
112
+ message: 'customer bought a dog',
113
+ pet: {name: 'Barker', species: 'dog', breed: 'Bitsa'}
114
+ })
115
+
116
+ log = read_json @buffer
117
+
118
+ assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
119
+ assert_equal 'Freda Bloggs', log[:customer][:full_name]
120
+ assert_equal 'pet purchase', log[:event][:action]
121
+ assert_equal 'customer bought a dog', log[:message]
122
+ assert_equal 'Barker', log[:pet][:name]
123
+ end
131
124
 
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
125
+ it "should log 'message' string property" do
126
+ message = {}
127
+ message['message'] = 'Guinea pigs arrived'
128
+ @logger.debug(message)
129
+ log = read_json(@buffer)
147
130
 
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
131
+ assert_equal 'Guinea pigs arrived', log[:message]
132
+ end
133
+
134
+ it "should log multiple messages properly" do
135
+ @logger.debug({message: 'hi'})
136
+ @logger.info({message: 'there'})
137
+
138
+ expected_output =
139
+ '{"@timestamp":"2020-05-11T15:01:01.000Z",'\
140
+ '"service":{"name":"petshop"},"log":{"level":"debug"},"message":"hi"}'\
141
+ "\n"\
142
+ '{"@timestamp":"2020-05-11T15:01:01.000Z",'\
143
+ '"service":{"name":"petshop"},"log":{"level":"info"},"message":"there"}'\
144
+ "\n"\
145
+
146
+ assert_equal expected_output, @buffer.string
147
+ end
148
+
149
+ it 'should be able to convert dotted keys to nested objects' do
150
+ @logger.debug({
151
+ "trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
152
+ message: 'customer bought a dog',
153
+ "pet.name": 'Barker',
154
+ "pet.species": 'dog',
155
+ "pet.breed": 'Bitsa'
156
+ })
157
+ log = read_json(@buffer)
158
+
159
+ assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
160
+ assert_equal 'customer bought a dog', log[:message]
161
+ assert_equal 'Barker', log[:pet][:name]
162
+ assert_equal 'dog', log[:pet][:species]
163
+ assert_equal 'Bitsa', log[:pet][:breed]
164
+ end
165
+
166
+ it 'should be able to mix dotted keys and nested objects' do
167
+ @logger.debug({
168
+ "trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
169
+ message: 'customer bought a dog',
170
+ pet: {name: 'Barker', breed: 'Bitsa'},
171
+ "pet.species": 'dog'
172
+ })
173
+ log = read_json(@buffer)
174
+
175
+ assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
176
+ assert_equal 'customer bought a dog', log[:message]
177
+ assert_equal 'Barker', log[:pet][:name]
178
+ assert_equal 'dog', log[:pet][:species]
179
+ assert_equal 'Bitsa', log[:pet][:breed]
180
+ end
181
+
182
+ it 'should work with mixed string and symbol properties' do
183
+ log = {
184
+ "trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'
185
+ }
186
+ event = {}
187
+ log['event'] = event
188
+ log['message'] = 'customer bought a dog'
189
+ pet = {}
190
+ pet['name'] = 'Barker'
191
+ pet['breed'] = 'Bitsa'
192
+ pet[:species] = 'dog'
193
+ log[:pet] = pet
194
+
195
+ @logger.debug(log)
196
+ actual_log = read_json(@buffer)
197
+
198
+ assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', actual_log[:trace][:id]
199
+ assert_equal 'customer bought a dog', actual_log[:message]
200
+ assert_equal 'Barker', actual_log[:pet][:name]
201
+ assert_equal 'dog', actual_log[:pet][:species]
202
+ assert_equal 'Bitsa', actual_log[:pet][:breed]
203
+ end
204
+
205
+ it 'should log an error with backtrace' do
206
+ begin
207
+ 1 / 0
208
+ rescue StandardError => e
209
+ @logger.error({message: 'Artificially raised exception'}, e)
210
+ end
211
+
212
+ actual_log = read_json(@buffer)
213
+
214
+ assert_equal 'Artificially raised exception', actual_log[:message]
215
+ assert_equal 'divided by 0', actual_log[:error][:message]
216
+ assert_match 'logger_test.rb', actual_log[:error][:stack_trace].lines.first
217
+ end
170
218
 
171
- it 'should log an error with backtrace' do
172
- begin
173
- 1 / 0
174
- rescue StandardError => e
219
+ it 'should log an error without backtrace' do
220
+ e = StandardError.new('Connection timed-out')
175
221
  @logger.error({message: 'Artificially raised exception'}, e)
222
+
223
+ actual_log = read_json(@buffer)
224
+
225
+ assert_equal 'Artificially raised exception', actual_log[:message]
226
+ assert_equal 'Connection timed-out', actual_log[:error][:message]
227
+ refute actual_log[:error].key?(:stack_trace)
176
228
  end
177
229
 
178
- actual_log = read_json(@buffer)
230
+ LEVELS.each do |attrs|
231
+ it "should correctly log level when calling #{attrs[:method]}" do
232
+ @logger.public_send(attrs[:method], {message: 'a log message'})
233
+ actual_log = read_json(@buffer)
179
234
 
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
235
+ assert_equal attrs[:level], actual_log[:log][:level]
236
+ assert_equal 'a log message', actual_log[:message]
237
+ end
238
+ end
183
239
  end
184
240
 
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
- ]
241
+ describe 'text logging' do
242
+ it 'should throw an error with an empty message' do
243
+ assert_raises RuntimeError do
244
+ @logger.info('')
245
+ end
246
+ end
192
247
 
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'})
248
+ it 'should log mandatory attributes' do
249
+ @logger.error('Out of pets exception')
196
250
  actual_log = read_json(@buffer)
197
251
 
198
- assert_equal attrs[:level], actual_log[:log][:level]
199
- assert_equal 'a log message', actual_log[:message]
252
+ expected_log = {
253
+ message: 'Out of pets exception',
254
+ "@timestamp": '2020-05-11T15:01:01.000Z',
255
+ service: {
256
+ name: 'petshop'
257
+ },
258
+ log: {
259
+ level: 'error'
260
+ }
261
+ }
262
+
263
+ assert_equal expected_log, actual_log
264
+ end
265
+
266
+ it 'should log the provided message' do
267
+ @logger.error('Emergency! Emergency!')
268
+ log = read_json(@buffer)
269
+
270
+ assert_equal 'Emergency! Emergency!', log[:message]
271
+ end
272
+
273
+ LEVELS.each do |attrs|
274
+ it "should correctly log level when calling #{attrs[:method]}" do
275
+ @logger.public_send(attrs[:method], 'a log message')
276
+ actual_log = read_json(@buffer)
277
+
278
+ assert_equal attrs[:level], actual_log[:log][:level]
279
+ assert_equal 'a log message', actual_log[:message]
280
+ end
281
+ end
282
+ end
283
+
284
+ describe 'logging with a block' do
285
+ LEVELS.each do |attrs|
286
+ it "should correctly log the block when calling #{attrs[:method]}" do
287
+ block = proc { 'a block log message' }
288
+ @logger.public_send(attrs[:method], &block)
289
+ actual_log = read_json(@buffer)
290
+
291
+ assert_equal attrs[:level], actual_log[:log][:level]
292
+ assert_equal 'a block log message', actual_log[:message]
293
+ end
294
+ end
295
+ end
296
+
297
+ describe 'logger level' do
298
+ [
299
+ { expression: :info, level: 1 },
300
+ { expression: 'warn', level: 2 },
301
+ { expression: Logger::DEBUG, level: 0 }
302
+ ].each do |args|
303
+ it "sets the severity threshold to level #{args[:level]}" do
304
+ @logger.level = args[:expression]
305
+ assert_equal args[:level], @logger.level
306
+ end
200
307
  end
201
308
  end
202
309
 
@@ -207,3 +314,4 @@ describe Twiglet::Logger do
207
314
  JSON.parse(buffer.read, symbolize_names: true)
208
315
  end
209
316
  end
317
+ # rubocop:enable Metrics/BlockLength
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.2.2
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-25 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Like a log, only smaller.
14
14
  email:
@@ -24,15 +24,16 @@ files:
24
24
  - ".ruby-version"
25
25
  - CODE_OF_CONDUCT.md
26
26
  - Gemfile
27
- - Gemfile.lock
28
27
  - LICENSE
29
28
  - RATIONALE.md
30
29
  - README.md
31
30
  - Rakefile
32
31
  - example_app.rb
33
32
  - lib/hash_extensions.rb
33
+ - lib/twiglet/formatter.rb
34
34
  - lib/twiglet/logger.rb
35
35
  - lib/twiglet/version.rb
36
+ - test/formatter_test.rb
36
37
  - test/hash_extensions_test.rb
37
38
  - test/logger_test.rb
38
39
  - twiglet.gemspec
@@ -1,62 +0,0 @@
1
- GIT
2
- remote: https://github.com/simplybusiness/simplycop.git
3
- revision: 02b417277e3ff9eeab34d6b7c30a95a17d876731
4
- specs:
5
- simplycop (0.5.4)
6
- rubocop (~> 0.80.0)
7
- rubocop-rails
8
- rubocop-rspec
9
-
10
- GEM
11
- remote: https://rubygems.org/
12
- specs:
13
- activesupport (6.0.3.1)
14
- concurrent-ruby (~> 1.0, >= 1.0.2)
15
- i18n (>= 0.7, < 2)
16
- minitest (~> 5.1)
17
- tzinfo (~> 1.1)
18
- zeitwerk (~> 2.2, >= 2.2.2)
19
- ast (2.4.0)
20
- concurrent-ruby (1.1.6)
21
- i18n (1.8.2)
22
- concurrent-ruby (~> 1.0)
23
- jaro_winkler (1.5.4)
24
- minitest (5.14.0)
25
- parallel (1.19.1)
26
- parser (2.7.1.3)
27
- ast (~> 2.4.0)
28
- rack (2.2.2)
29
- rainbow (3.0.0)
30
- rake (13.0.1)
31
- rexml (3.2.4)
32
- rubocop (0.80.1)
33
- jaro_winkler (~> 1.5.1)
34
- parallel (~> 1.10)
35
- parser (>= 2.7.0.1)
36
- rainbow (>= 2.2.2, < 4.0)
37
- rexml
38
- ruby-progressbar (~> 1.7)
39
- unicode-display_width (>= 1.4.0, < 1.7)
40
- rubocop-rails (2.5.2)
41
- activesupport
42
- rack (>= 1.1)
43
- rubocop (>= 0.72.0)
44
- rubocop-rspec (1.39.0)
45
- rubocop (>= 0.68.1)
46
- ruby-progressbar (1.10.1)
47
- thread_safe (0.3.6)
48
- tzinfo (1.2.7)
49
- thread_safe (~> 0.1)
50
- unicode-display_width (1.6.1)
51
- zeitwerk (2.3.0)
52
-
53
- PLATFORMS
54
- ruby
55
-
56
- DEPENDENCIES
57
- minitest
58
- rake
59
- simplycop!
60
-
61
- BUNDLED WITH
62
- 2.1.4