twiglet 2.0.0.pre.alpha.1 → 2.2.1

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: f2bb718963cc1602d548f4f80bbf03a932feac4ceaa5b11bc3b6d19a1ee58e10
4
- data.tar.gz: 6ee69671edd4a487a905c45dec2833141fc6a4a7b8702bdffdc2dc0b81e6e9e4
3
+ metadata.gz: 3cb9302ac1ae427b94de457a976ace7c30d30b875676f43e91b4e206056cedf9
4
+ data.tar.gz: e436939ff209613f1a4f0c64c33f06a0cb3dcf36c9faa20b25bfc8c829946a26
5
5
  SHA512:
6
- metadata.gz: 3ccecc55b8320608406a0dfec87f617e12501dbdebbe62de7b74881afde44439ba789c59b80c1530c9e4325dc424a202c1ea71958adf32ce1bda020103b38a48
7
- data.tar.gz: 2762f0a75ac22ae69ef30de4afa022dc91e1504e578ce0fcf3198291e0f4963adbda2ab57e7e5b8a852ff2bfe6f058f3478d23c622a09a0eef98cc2821881c4b
6
+ metadata.gz: 06f15914e02513b777e473c6472186f1d189dae59df2e8e59a5f00efad3d7a45d4cf053b96e862c0b9337b70c2c672de12fe73d9e766ceaa047d483005f5000b
7
+ data.tar.gz: 35fbe15d0b18895fdb5443e2493d212c66989fea77eea204b2cb900535da78133e001c638981e856deacca3c905427222fa2a7073f895c1b26dbb270b63397c8
@@ -1,4 +1,3 @@
1
1
  # Add your project owners info here
2
2
  # More information: https://help.github.com/articles/about-codeowners/
3
3
  * @simplybusiness/application-tooling
4
- *
@@ -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
@@ -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:
@@ -25,7 +25,7 @@ GEM
25
25
  parallel (1.19.1)
26
26
  parser (2.7.1.3)
27
27
  ast (~> 2.4.0)
28
- rack (2.2.2)
28
+ rack (2.2.3)
29
29
  rainbow (3.0.0)
30
30
  rake (13.0.1)
31
31
  rexml (3.2.4)
data/README.md CHANGED
@@ -40,6 +40,24 @@ 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
+
54
+ Errors can be logged as well, and this will log the error message and backtrace in the relevant ECS compliant fields:
55
+
56
+ ```ruby
57
+ db_err = StandardError.new('Connection timed-out')
58
+ logger.error({ message: 'DB connection failed.' }, db_err)
59
+ ```
60
+
43
61
  Add log event specific information simply as attributes in a hash:
44
62
 
45
63
  ```ruby
@@ -61,6 +79,18 @@ This writes:
61
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"}}
62
80
  ```
63
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
+
64
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:
65
95
 
66
96
  ```ruby
@@ -126,7 +156,7 @@ First: Please read our project [Code of Conduct](../CODE_OF_CONDUCT.md).
126
156
  Second: run the tests and make sure your changes don't break anything:
127
157
 
128
158
  ```bash
129
- for file in test/*test.rb; do ruby $file; done
159
+ bundle exec rake test
130
160
  ```
131
161
 
132
162
  Then please feel free to submit a PR.
@@ -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,36 +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
- def error(message, error = nil)
30
+ def error(message = {}, error = nil, &block)
40
31
  if error
41
- message = message.merge({
42
- error_name: error.message,
43
- backtrace: error.backtrace
44
- })
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)
45
39
  end
46
40
 
47
- log(level: 'error', message: message)
48
- end
49
-
50
- def critical(message)
51
- log(level: 'critical', message: message)
41
+ super(message, &block)
52
42
  end
53
43
 
54
44
  def with(default_properties)
@@ -58,30 +48,13 @@ module Twiglet
58
48
  output: @output)
59
49
  end
60
50
 
61
- private
62
-
63
- def log(level:, message:)
64
- raise 'Message must be a Hash' unless message.is_a?(Hash)
65
-
66
- message = message.transform_keys(&:to_sym)
67
- message.key?(:message) || raise('Log object must have a \'message\' property')
51
+ alias_method :warning, :warn
52
+ alias_method :critical, :fatal
68
53
 
69
- message[:message].strip.empty? && raise('The \'message\' property of log object must not be empty')
70
-
71
- base_message = {
72
- service: {
73
- name: @service_name
74
- },
75
- "@timestamp": @now.call.iso8601(3),
76
- log: {
77
- level: level
78
- }
79
- }
54
+ private
80
55
 
81
- @output.puts base_message
82
- .deep_merge(@default_properties.to_nested)
83
- .deep_merge(message.to_nested)
84
- .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
85
58
  end
86
59
  end
87
60
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Twiglet
4
- VERSION = '2.0.0-alpha.1'
4
+ VERSION = '2.2.1'
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,174 +13,296 @@ 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
+ })
131
109
 
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
110
+ # do stuff
111
+ purchase_logger.info({
112
+ message: 'customer bought a dog',
113
+ pet: {name: 'Barker', species: 'dog', breed: 'Bitsa'}
114
+ })
147
115
 
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
116
+ log = read_json @buffer
170
117
 
171
- it 'should log an error with backtrace' do
172
- begin
173
- 1 / 0
174
- rescue StandardError => e
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
124
+
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)
130
+
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","service":{"name":"petshop"},"log":{"level":"debug"},"message":"hi"}'\
140
+ "\n"\
141
+ '{"@timestamp":"2020-05-11T15:01:01.000Z","service":{"name":"petshop"},"log":{"level":"info"},"message":"there"}'\
142
+ "\n"\
143
+
144
+ assert_equal expected_output, @buffer.string
145
+ end
146
+
147
+ it 'should be able to convert dotted keys to nested objects' do
148
+ @logger.debug({
149
+ "trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
150
+ message: 'customer bought a dog',
151
+ "pet.name": 'Barker',
152
+ "pet.species": 'dog',
153
+ "pet.breed": 'Bitsa'
154
+ })
155
+ log = read_json(@buffer)
156
+
157
+ assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
158
+ assert_equal 'customer bought a dog', log[:message]
159
+ assert_equal 'Barker', log[:pet][:name]
160
+ assert_equal 'dog', log[:pet][:species]
161
+ assert_equal 'Bitsa', log[:pet][:breed]
162
+ end
163
+
164
+ it 'should be able to mix dotted keys and nested objects' do
165
+ @logger.debug({
166
+ "trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
167
+ message: 'customer bought a dog',
168
+ pet: {name: 'Barker', breed: 'Bitsa'},
169
+ "pet.species": 'dog'
170
+ })
171
+ log = read_json(@buffer)
172
+
173
+ assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
174
+ assert_equal 'customer bought a dog', log[:message]
175
+ assert_equal 'Barker', log[:pet][:name]
176
+ assert_equal 'dog', log[:pet][:species]
177
+ assert_equal 'Bitsa', log[:pet][:breed]
178
+ end
179
+
180
+ it 'should work with mixed string and symbol properties' do
181
+ log = {
182
+ "trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'
183
+ }
184
+ event = {}
185
+ log['event'] = event
186
+ log['message'] = 'customer bought a dog'
187
+ pet = {}
188
+ pet['name'] = 'Barker'
189
+ pet['breed'] = 'Bitsa'
190
+ pet[:species] = 'dog'
191
+ log[:pet] = pet
192
+
193
+ @logger.debug(log)
194
+ actual_log = read_json(@buffer)
195
+
196
+ assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', actual_log[:trace][:id]
197
+ assert_equal 'customer bought a dog', actual_log[:message]
198
+ assert_equal 'Barker', actual_log[:pet][:name]
199
+ assert_equal 'dog', actual_log[:pet][:species]
200
+ assert_equal 'Bitsa', actual_log[:pet][:breed]
201
+ end
202
+
203
+ it 'should log an error with backtrace' do
204
+ begin
205
+ 1 / 0
206
+ rescue StandardError => e
207
+ @logger.error({message: 'Artificially raised exception'}, e)
208
+ end
209
+
210
+ actual_log = read_json(@buffer)
211
+
212
+ assert_equal 'Artificially raised exception', actual_log[:message]
213
+ assert_equal 'divided by 0', actual_log[:error][:message]
214
+ assert_match 'logger_test.rb', actual_log[:error][:stack_trace].lines.first
215
+ end
216
+
217
+ it 'should log an error without backtrace' do
218
+ e = StandardError.new('Connection timed-out')
175
219
  @logger.error({message: 'Artificially raised exception'}, e)
220
+
221
+ actual_log = read_json(@buffer)
222
+
223
+ assert_equal 'Artificially raised exception', actual_log[:message]
224
+ assert_equal 'Connection timed-out', actual_log[:error][:message]
225
+ refute actual_log[:error].key?(:stack_trace)
226
+ end
227
+
228
+ LEVELS.each do |attrs|
229
+ it "should correctly log level when calling #{attrs[:method]}" do
230
+ @logger.public_send(attrs[:method], {message: 'a log message'})
231
+ actual_log = read_json(@buffer)
232
+
233
+ assert_equal attrs[:level], actual_log[:log][:level]
234
+ assert_equal 'a log message', actual_log[:message]
235
+ end
236
+ end
237
+ end
238
+
239
+ describe 'text logging' do
240
+ it 'should throw an error with an empty message' do
241
+ assert_raises RuntimeError do
242
+ @logger.info('')
243
+ end
176
244
  end
177
245
 
178
- actual_log = read_json(@buffer)
246
+ it 'should log mandatory attributes' do
247
+ @logger.error('Out of pets exception')
248
+ actual_log = read_json(@buffer)
179
249
 
180
- assert_equal 'Artificially raised exception', actual_log[:message]
181
- assert_equal 'divided by 0', actual_log[:error_name]
182
- assert_match 'logger_test.rb', actual_log[:backtrace].first
250
+ expected_log = {
251
+ message: 'Out of pets exception',
252
+ "@timestamp": '2020-05-11T15:01:01.000Z',
253
+ service: {
254
+ name: 'petshop'
255
+ },
256
+ log: {
257
+ level: 'error'
258
+ }
259
+ }
260
+
261
+ assert_equal expected_log, actual_log
262
+ end
263
+
264
+ it 'should log the provided message' do
265
+ @logger.error('Emergency! Emergency!')
266
+ log = read_json(@buffer)
267
+
268
+ assert_equal 'Emergency! Emergency!', log[:message]
269
+ end
270
+
271
+ LEVELS.each do |attrs|
272
+ it "should correctly log level when calling #{attrs[:method]}" do
273
+ @logger.public_send(attrs[:method], 'a log message')
274
+ actual_log = read_json(@buffer)
275
+
276
+ assert_equal attrs[:level], actual_log[:log][:level]
277
+ assert_equal 'a log message', actual_log[:message]
278
+ end
279
+ end
280
+ end
281
+
282
+ describe 'logging with a block' do
283
+ LEVELS.each do |attrs|
284
+ it "should correctly log the block when calling #{attrs[:method]}" do
285
+ block = proc { 'a block log message' }
286
+ @logger.public_send(attrs[:method], &block)
287
+ actual_log = read_json(@buffer)
288
+
289
+ assert_equal attrs[:level], actual_log[:log][:level]
290
+ assert_equal 'a block log message', actual_log[:message]
291
+ end
292
+ end
293
+ end
294
+
295
+ describe 'logger level' do
296
+ [
297
+ { expression: :info, level: 1 },
298
+ { expression: 'warn', level: 2 },
299
+ { expression: Logger::DEBUG, level: 0 }
300
+ ].each do |args|
301
+ it "sets the severity threshold to level #{args[:level]}" do
302
+ @logger.level = args[:expression]
303
+ assert_equal args[:level], @logger.level
304
+ end
305
+ end
183
306
  end
184
307
 
185
308
  private
@@ -189,3 +312,4 @@ describe Twiglet::Logger do
189
312
  JSON.parse(buffer.read, symbolize_names: true)
190
313
  end
191
314
  end
315
+ # 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.pre.alpha.1
4
+ version: 2.2.1
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-03 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:
@@ -31,8 +31,10 @@ files:
31
31
  - Rakefile
32
32
  - example_app.rb
33
33
  - lib/hash_extensions.rb
34
+ - lib/twiglet/formatter.rb
34
35
  - lib/twiglet/logger.rb
35
36
  - lib/twiglet/version.rb
37
+ - test/formatter_test.rb
36
38
  - test/hash_extensions_test.rb
37
39
  - test/logger_test.rb
38
40
  - twiglet.gemspec
@@ -51,9 +53,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
51
53
  version: '2.6'
52
54
  required_rubygems_version: !ruby/object:Gem::Requirement
53
55
  requirements:
54
- - - ">"
56
+ - - ">="
55
57
  - !ruby/object:Gem::Version
56
- version: 1.3.1
58
+ version: '0'
57
59
  requirements: []
58
60
  rubygems_version: 3.1.2
59
61
  signing_key: