twiglet 3.0.5 → 3.1.2
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/.github/workflows/dobby-actions.yml +16 -0
- data/Gemfile +3 -0
- data/README.md +5 -1
- data/example_app.rb +30 -24
- data/examples/rack/request_logger_test.rb +7 -5
- data/lib/twiglet/formatter.rb +6 -2
- data/lib/twiglet/logger.rb +23 -10
- data/lib/twiglet/message.rb +3 -12
- data/lib/twiglet/validation_schema.json +10 -0
- data/lib/twiglet/validator.rb +25 -0
- data/lib/twiglet/version.rb +1 -1
- data/test/formatter_test.rb +2 -1
- data/test/logger_test.rb +93 -47
- data/test/message_test.rb +0 -18
- data/test/test_coverage.rb +6 -2
- data/test/validator_test.rb +38 -0
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5aa4993d40d0b1f658c6b5f8c79f364e4bc1fef111d094b48446bde0fe7a0701
|
|
4
|
+
data.tar.gz: 8c6dee85c7622b274d570eb400bb8ca065c70e311a11699a990bc5548a2479d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 863b7bb7f3848a2345ffe5c8639add2f77fa7849d4f29d873bb25eb97c3510a2edec4c78cc6a95b343c1ee305571ca43534f88b494d983e0f00b23e7dd5ae5d5
|
|
7
|
+
data.tar.gz: 5827e6d6eee5fb90ca06822f7d5df2ac757a00f5d281453f74c3f3f3efcfee603639003eb711ccb026738ff9cd35e54405fc2ee125eb44b13f413f69f365f4c7
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: "dobby action"
|
|
2
|
+
on:
|
|
3
|
+
issue_comment:
|
|
4
|
+
types: [created]
|
|
5
|
+
jobs:
|
|
6
|
+
pr_commented:
|
|
7
|
+
runs-on: ubuntu-20.04
|
|
8
|
+
if: startsWith(github.event.comment.body, '/dobby')
|
|
9
|
+
|
|
10
|
+
steps:
|
|
11
|
+
- name: bump version
|
|
12
|
+
uses: simplybusiness/dobby@v2.0.0
|
|
13
|
+
env:
|
|
14
|
+
DOBBY_APP_ID: ${{ secrets.DOBBY_APP_ID }}
|
|
15
|
+
DOBBY_PRIVATE_KEY: ${{ secrets.DOBBY_PRIVATE_KEY }}
|
|
16
|
+
VERSION_FILE_PATH: lib/twiglet/version.rb
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -110,7 +110,7 @@ This writes:
|
|
|
110
110
|
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:
|
|
111
111
|
|
|
112
112
|
```ruby
|
|
113
|
-
|
|
113
|
+
request_logger = logger.with({ event: { action: 'HTTP request'}, trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' }})
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
This can be used like any other Logger instance:
|
|
@@ -140,6 +140,10 @@ To access the formatter:
|
|
|
140
140
|
logger.formatter
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
+
### HTTP Request Logging
|
|
144
|
+
Take a look at this sample [Rack application](examples/rack/example_rack_app.rb#L15) with an ECS compliant
|
|
145
|
+
[request logger](/examples/rack/request_logger.rb) as a template when configuring your own request logging middleware with Twiglet.
|
|
146
|
+
|
|
143
147
|
## Use of dotted keys (DEPRECATED)
|
|
144
148
|
|
|
145
149
|
Writing nested json objects could be confusing. This library has a built-in feature to convert dotted keys into nested objects, so if you log like this:
|
data/example_app.rb
CHANGED
|
@@ -7,28 +7,32 @@ PORT = 8080
|
|
|
7
7
|
logger = Twiglet::Logger.new('petshop')
|
|
8
8
|
|
|
9
9
|
# Start our petshop
|
|
10
|
-
logger.info(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
logger.info(
|
|
11
|
+
{
|
|
12
|
+
event: {
|
|
13
|
+
action: 'startup'
|
|
14
|
+
},
|
|
15
|
+
message: "Ready to go, listening on port #{PORT}",
|
|
16
|
+
server: {
|
|
17
|
+
port: PORT
|
|
18
|
+
}
|
|
17
19
|
}
|
|
18
|
-
|
|
20
|
+
)
|
|
19
21
|
|
|
20
22
|
# Use text logging
|
|
21
23
|
logger.info("Ready to go, listening on port #{PORT}")
|
|
22
24
|
#
|
|
23
25
|
# We get a request
|
|
24
|
-
request_logger = logger.with(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
request_logger = logger.with(
|
|
27
|
+
{
|
|
28
|
+
event: {
|
|
29
|
+
action: 'HTTP request'
|
|
30
|
+
},
|
|
31
|
+
trace: {
|
|
32
|
+
id: '126bb6fa-28a2-470f-b013-eefbf9182b2d'
|
|
33
|
+
}
|
|
30
34
|
}
|
|
31
|
-
|
|
35
|
+
)
|
|
32
36
|
|
|
33
37
|
# Oh noes!
|
|
34
38
|
db_err = StandardError.new('Connection timed-out')
|
|
@@ -36,17 +40,19 @@ db_err = StandardError.new('Connection timed-out')
|
|
|
36
40
|
request_logger.error({ message: 'DB connection failed.' }, db_err) if db_err
|
|
37
41
|
|
|
38
42
|
# We return an error to the requester
|
|
39
|
-
request_logger.info(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
request_logger.info(
|
|
44
|
+
{
|
|
45
|
+
message: 'Internal Server Error',
|
|
46
|
+
http: {
|
|
47
|
+
request: {
|
|
48
|
+
method: 'get'
|
|
49
|
+
},
|
|
50
|
+
response: {
|
|
51
|
+
status_code: 500
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
54
|
}
|
|
49
|
-
|
|
55
|
+
)
|
|
50
56
|
|
|
51
57
|
# Logging with an empty message is an anti-pattern and is therefore forbidden
|
|
52
58
|
# Both of the following lines would throw an error
|
|
@@ -15,10 +15,12 @@ describe RequestLogger do
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
it 'logs the request data' do
|
|
18
|
-
request.get(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
request.get(
|
|
19
|
+
"/some/path?some_var=1", 'HTTP_ACCEPT' => 'application/json',
|
|
20
|
+
'REMOTE_ADDR' => '0.0.0.0',
|
|
21
|
+
'HTTP_VERSION' => 'HTTP/1.1',
|
|
22
|
+
'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh)'
|
|
23
|
+
)
|
|
22
24
|
log = JSON.parse(output.string)
|
|
23
25
|
|
|
24
26
|
expected_log = {
|
|
@@ -55,7 +57,7 @@ describe RequestLogger do
|
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
it 'does not log PII' do
|
|
58
|
-
request.post("/user/info", input_data: {credit_card_no: '1234'})
|
|
60
|
+
request.post("/user/info", input_data: { credit_card_no: '1234' })
|
|
59
61
|
log = output.string
|
|
60
62
|
assert_includes log, "POST: /user/info"
|
|
61
63
|
refute_includes log, 'credit_card_no'
|
data/lib/twiglet/formatter.rb
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
require 'logger'
|
|
2
2
|
require_relative '../hash_extensions'
|
|
3
3
|
require_relative 'message'
|
|
4
|
+
require_relative 'validator'
|
|
4
5
|
|
|
5
6
|
module Twiglet
|
|
6
7
|
class Formatter < ::Logger::Formatter
|
|
7
8
|
Hash.include HashExtensions
|
|
8
9
|
|
|
9
10
|
def initialize(service_name,
|
|
10
|
-
default_properties: {},
|
|
11
|
+
validator:, default_properties: {},
|
|
11
12
|
now: -> { Time.now.utc })
|
|
12
13
|
@service_name = service_name
|
|
13
14
|
@now = now
|
|
14
15
|
@default_properties = default_properties
|
|
16
|
+
@validator = validator
|
|
15
17
|
|
|
16
18
|
super()
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def call(severity, _time, _progname, msg)
|
|
20
22
|
level = severity.downcase
|
|
21
|
-
|
|
23
|
+
message = Message.new(msg)
|
|
24
|
+
@validator.validate(message)
|
|
25
|
+
log(level: level, message: message)
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
private
|
data/lib/twiglet/logger.rb
CHANGED
|
@@ -26,16 +26,27 @@ module Twiglet
|
|
|
26
26
|
raise 'Service name is mandatory' \
|
|
27
27
|
unless service_name.is_a?(String) && !service_name.strip.empty?
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
@validator = Validator.from_file("#{__dir__}/validation_schema.json")
|
|
30
|
+
|
|
31
|
+
@formatter = Twiglet::Formatter.new(
|
|
32
|
+
service_name,
|
|
33
|
+
default_properties: default_properties,
|
|
34
|
+
now: now,
|
|
35
|
+
validator: @validator
|
|
36
|
+
)
|
|
37
|
+
super(output, formatter: @formatter, level: level)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def configure_validation_error_response(&block)
|
|
41
|
+
@validator.custom_error_handler = block
|
|
31
42
|
end
|
|
32
43
|
|
|
33
44
|
def error(message = nil, error = nil, &block)
|
|
34
45
|
if error
|
|
35
46
|
error_fields = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
error: {
|
|
48
|
+
type: error.class,
|
|
49
|
+
message: error.message
|
|
39
50
|
}
|
|
40
51
|
}
|
|
41
52
|
add_stack_trace(error_fields, error)
|
|
@@ -46,11 +57,13 @@ module Twiglet
|
|
|
46
57
|
end
|
|
47
58
|
|
|
48
59
|
def with(default_properties)
|
|
49
|
-
Logger.new(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
Logger.new(
|
|
61
|
+
@service_name,
|
|
62
|
+
default_properties: default_properties,
|
|
63
|
+
now: @now,
|
|
64
|
+
output: @output,
|
|
65
|
+
level: @level
|
|
66
|
+
)
|
|
54
67
|
end
|
|
55
68
|
|
|
56
69
|
alias_method :warning, :warn
|
data/lib/twiglet/message.rb
CHANGED
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
module Twiglet
|
|
2
2
|
class Message < Hash
|
|
3
3
|
def initialize(msg)
|
|
4
|
+
super
|
|
4
5
|
case msg
|
|
5
6
|
when String
|
|
6
7
|
self[:message] = msg
|
|
7
8
|
when Hash
|
|
8
9
|
replace(msg.transform_keys!(&:to_sym))
|
|
10
|
+
else
|
|
11
|
+
super(msg)
|
|
9
12
|
end
|
|
10
|
-
|
|
11
|
-
validate!
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
private
|
|
15
|
-
|
|
16
|
-
def validate!
|
|
17
|
-
raise 'Message must be initialized with a String or a non-empty Hash' if empty?
|
|
18
|
-
|
|
19
|
-
raise 'Log object must have a \'message\' property' unless self[:message]
|
|
20
|
-
|
|
21
|
-
raise 'The \'message\' property of the log object must not be empty' if self[:message].strip.empty?
|
|
22
13
|
end
|
|
23
14
|
end
|
|
24
15
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json-schema'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module Twiglet
|
|
7
|
+
class Validator
|
|
8
|
+
attr_accessor :custom_error_handler
|
|
9
|
+
|
|
10
|
+
def initialize(schema)
|
|
11
|
+
@schema = schema
|
|
12
|
+
@custom_error_handler = ->(e) { raise e }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.from_file(file_path)
|
|
16
|
+
new(JSON.parse(File.read(file_path)))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def validate(message)
|
|
20
|
+
JSON::Validator.validate!(@schema, message)
|
|
21
|
+
rescue JSON::Schema::ValidationError => e
|
|
22
|
+
custom_error_handler.call(e)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/twiglet/version.rb
CHANGED
data/test/formatter_test.rb
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
require 'minitest/autorun'
|
|
4
4
|
require 'json'
|
|
5
5
|
require_relative '../lib/twiglet/formatter'
|
|
6
|
+
require_relative '../lib/twiglet/validator'
|
|
6
7
|
|
|
7
8
|
describe Twiglet::Formatter do
|
|
8
9
|
before do
|
|
9
10
|
@now = -> { Time.utc(2020, 5, 11, 15, 1, 1) }
|
|
10
|
-
@formatter = Twiglet::Formatter.new('petshop', now: @now)
|
|
11
|
+
@formatter = Twiglet::Formatter.new('petshop', now: @now, validator: Twiglet::Validator.new({}))
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
it 'initializes an instance of a Ruby Logger Formatter' do
|
data/test/logger_test.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'minitest/autorun'
|
|
4
|
+
require 'minitest/mock'
|
|
4
5
|
require_relative '../lib/twiglet/logger'
|
|
5
6
|
|
|
6
7
|
LEVELS = [
|
|
@@ -18,9 +19,11 @@ describe Twiglet::Logger do
|
|
|
18
19
|
before do
|
|
19
20
|
@now = -> { Time.utc(2020, 5, 11, 15, 1, 1) }
|
|
20
21
|
@buffer = StringIO.new
|
|
21
|
-
@logger = Twiglet::Logger.new(
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
@logger = Twiglet::Logger.new(
|
|
23
|
+
'petshop',
|
|
24
|
+
now: @now,
|
|
25
|
+
output: @buffer
|
|
26
|
+
)
|
|
24
27
|
end
|
|
25
28
|
|
|
26
29
|
it 'should throw an error with an empty service name' do
|
|
@@ -30,21 +33,29 @@ describe Twiglet::Logger do
|
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
it 'conforms to the standard Ruby Logger API' do
|
|
33
|
-
[
|
|
34
|
-
|
|
36
|
+
[
|
|
37
|
+
:debug, :debug?, :info, :info?, :warn, :warn?, :fatal, :fatal?, :error, :error?,
|
|
38
|
+
:level, :level=, :sev_threshold=
|
|
39
|
+
].each do |call|
|
|
35
40
|
assert @logger.respond_to?(call), "Logger does not respond to #{call}"
|
|
36
41
|
end
|
|
37
42
|
end
|
|
38
43
|
|
|
39
44
|
describe 'JSON logging' do
|
|
40
45
|
it 'should throw an error with an empty message' do
|
|
41
|
-
assert_raises
|
|
42
|
-
@logger.info({message: ''})
|
|
46
|
+
assert_raises JSON::Schema::ValidationError, "The property '#/message' was not of a minimum string length of 1" do
|
|
47
|
+
@logger.info({ message: '' })
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'should throw an error if message is missing' do
|
|
52
|
+
assert_raises JSON::Schema::ValidationError, "The property '#/message' was not of a minimum string length of 1" do
|
|
53
|
+
@logger.info({ foo: 'bar' })
|
|
43
54
|
end
|
|
44
55
|
end
|
|
45
56
|
|
|
46
57
|
it 'should log mandatory attributes' do
|
|
47
|
-
@logger.error({message: 'Out of pets exception'})
|
|
58
|
+
@logger.error({ message: 'Out of pets exception' })
|
|
48
59
|
actual_log = read_json(@buffer)
|
|
49
60
|
|
|
50
61
|
expected_log = {
|
|
@@ -65,9 +76,13 @@ describe Twiglet::Logger do
|
|
|
65
76
|
end
|
|
66
77
|
|
|
67
78
|
it 'should log the provided message' do
|
|
68
|
-
@logger.error(
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
@logger.error(
|
|
80
|
+
{
|
|
81
|
+
event:
|
|
82
|
+
{ action: 'exception' },
|
|
83
|
+
message: 'Emergency! Emergency!'
|
|
84
|
+
}
|
|
85
|
+
)
|
|
71
86
|
log = read_json(@buffer)
|
|
72
87
|
|
|
73
88
|
assert_equal 'exception', log[:event][:action]
|
|
@@ -82,17 +97,19 @@ describe Twiglet::Logger do
|
|
|
82
97
|
service: {
|
|
83
98
|
type: 'shop'
|
|
84
99
|
},
|
|
85
|
-
request: {method: 'get'},
|
|
86
|
-
response: {status_code: 200}
|
|
100
|
+
request: { method: 'get' },
|
|
101
|
+
response: { status_code: 200 }
|
|
87
102
|
}
|
|
88
103
|
|
|
89
104
|
output = StringIO.new
|
|
90
|
-
logger = Twiglet::Logger.new(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
105
|
+
logger = Twiglet::Logger.new(
|
|
106
|
+
'petshop',
|
|
107
|
+
now: @now,
|
|
108
|
+
output: output,
|
|
109
|
+
default_properties: extra_properties
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
logger.error({ message: 'GET /cats' })
|
|
96
113
|
log = read_json output
|
|
97
114
|
|
|
98
115
|
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
|
@@ -104,17 +121,21 @@ describe Twiglet::Logger do
|
|
|
104
121
|
|
|
105
122
|
it "should be able to add properties with '.with'" do
|
|
106
123
|
# Let's add some context to this customer journey
|
|
107
|
-
purchase_logger = @logger.with(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
124
|
+
purchase_logger = @logger.with(
|
|
125
|
+
{
|
|
126
|
+
trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' },
|
|
127
|
+
customer: { full_name: 'Freda Bloggs' },
|
|
128
|
+
event: { action: 'pet purchase' }
|
|
129
|
+
}
|
|
130
|
+
)
|
|
112
131
|
|
|
113
132
|
# do stuff
|
|
114
|
-
purchase_logger.info(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
133
|
+
purchase_logger.info(
|
|
134
|
+
{
|
|
135
|
+
message: 'customer bought a dog',
|
|
136
|
+
pet: { name: 'Barker', species: 'dog', breed: 'Bitsa' }
|
|
137
|
+
}
|
|
138
|
+
)
|
|
118
139
|
|
|
119
140
|
log = read_json @buffer
|
|
120
141
|
|
|
@@ -135,8 +156,8 @@ describe Twiglet::Logger do
|
|
|
135
156
|
end
|
|
136
157
|
|
|
137
158
|
it "should log multiple messages properly" do
|
|
138
|
-
@logger.debug({message: 'hi'})
|
|
139
|
-
@logger.info({message: 'there'})
|
|
159
|
+
@logger.debug({ message: 'hi' })
|
|
160
|
+
@logger.info({ message: 'there' })
|
|
140
161
|
|
|
141
162
|
expected_output =
|
|
142
163
|
'{"ecs":{"version":"1.5.0"},"@timestamp":"2020-05-11T15:01:01.000Z",'\
|
|
@@ -174,7 +195,7 @@ describe Twiglet::Logger do
|
|
|
174
195
|
|
|
175
196
|
LEVELS.each do |attrs|
|
|
176
197
|
it "should correctly log level when calling #{attrs[:method]}" do
|
|
177
|
-
@logger.public_send(attrs[:method], {message: 'a log message'})
|
|
198
|
+
@logger.public_send(attrs[:method], { message: 'a log message' })
|
|
178
199
|
actual_log = read_json(@buffer)
|
|
179
200
|
|
|
180
201
|
assert_equal attrs[:level], actual_log[:log][:level]
|
|
@@ -188,7 +209,7 @@ describe Twiglet::Logger do
|
|
|
188
209
|
begin
|
|
189
210
|
1 / 0
|
|
190
211
|
rescue StandardError => e
|
|
191
|
-
@logger.error({message: 'Artificially raised exception'}, e)
|
|
212
|
+
@logger.error({ message: 'Artificially raised exception' }, e)
|
|
192
213
|
end
|
|
193
214
|
|
|
194
215
|
actual_log = read_json(@buffer)
|
|
@@ -201,7 +222,7 @@ describe Twiglet::Logger do
|
|
|
201
222
|
|
|
202
223
|
it 'should log an error without backtrace' do
|
|
203
224
|
e = StandardError.new('Connection timed-out')
|
|
204
|
-
@logger.error({message: 'Artificially raised exception'}, e)
|
|
225
|
+
@logger.error({ message: 'Artificially raised exception' }, e)
|
|
205
226
|
|
|
206
227
|
actual_log = read_json(@buffer)
|
|
207
228
|
|
|
@@ -225,7 +246,7 @@ describe Twiglet::Logger do
|
|
|
225
246
|
|
|
226
247
|
describe 'text logging' do
|
|
227
248
|
it 'should throw an error with an empty message' do
|
|
228
|
-
assert_raises
|
|
249
|
+
assert_raises JSON::Schema::ValidationError, "The property '#/message' was not of a minimum string length of 1" do
|
|
229
250
|
@logger.info('')
|
|
230
251
|
end
|
|
231
252
|
end
|
|
@@ -284,13 +305,15 @@ describe Twiglet::Logger do
|
|
|
284
305
|
|
|
285
306
|
describe 'dotted keys' do
|
|
286
307
|
it 'should be able to convert dotted keys to nested objects' do
|
|
287
|
-
@logger.debug(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
308
|
+
@logger.debug(
|
|
309
|
+
{
|
|
310
|
+
"trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
|
|
311
|
+
message: 'customer bought a dog',
|
|
312
|
+
"pet.name": 'Barker',
|
|
313
|
+
"pet.species": 'dog',
|
|
314
|
+
"pet.breed": 'Bitsa'
|
|
315
|
+
}
|
|
316
|
+
)
|
|
294
317
|
log = read_json(@buffer)
|
|
295
318
|
|
|
296
319
|
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
|
@@ -301,12 +324,14 @@ describe Twiglet::Logger do
|
|
|
301
324
|
end
|
|
302
325
|
|
|
303
326
|
it 'should be able to mix dotted keys and nested objects' do
|
|
304
|
-
@logger.debug(
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
327
|
+
@logger.debug(
|
|
328
|
+
{
|
|
329
|
+
"trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
|
|
330
|
+
message: 'customer bought a dog',
|
|
331
|
+
pet: { name: 'Barker', breed: 'Bitsa' },
|
|
332
|
+
"pet.species": 'dog'
|
|
333
|
+
}
|
|
334
|
+
)
|
|
310
335
|
log = read_json(@buffer)
|
|
311
336
|
|
|
312
337
|
assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
|
|
@@ -334,6 +359,27 @@ describe Twiglet::Logger do
|
|
|
334
359
|
end
|
|
335
360
|
end
|
|
336
361
|
|
|
362
|
+
describe 'configuring error response' do
|
|
363
|
+
it 'blows up by default' do
|
|
364
|
+
assert_raises JSON::Schema::ValidationError,
|
|
365
|
+
"The property '#/message' of type boolean did not match the following type: string" do
|
|
366
|
+
@logger.debug(message: true)
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
it 'silently swallows errors when configured to do so' do
|
|
371
|
+
mock = Minitest::Mock.new
|
|
372
|
+
|
|
373
|
+
@logger.configure_validation_error_response do |_e|
|
|
374
|
+
mock.notify_error("Logging schema validation error")
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
mock.expect(:notify_error, nil, ["Logging schema validation error"])
|
|
378
|
+
nonconformant_log = { message: true }
|
|
379
|
+
@logger.debug(nonconformant_log)
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
337
383
|
private
|
|
338
384
|
|
|
339
385
|
def read_json(buffer)
|
data/test/message_test.rb
CHANGED
|
@@ -2,24 +2,6 @@ require 'minitest/autorun'
|
|
|
2
2
|
require_relative '../lib/twiglet/message'
|
|
3
3
|
|
|
4
4
|
describe Twiglet::Message do
|
|
5
|
-
it 'raises if message is empty' do
|
|
6
|
-
assert_raises RuntimeError do
|
|
7
|
-
Twiglet::Message.new(' ')
|
|
8
|
-
end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
it 'raises if message is not provided' do
|
|
12
|
-
assert_raises RuntimeError do
|
|
13
|
-
Twiglet::Message.new(foo: 'bar')
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
it 'raises on unrecognized inputs' do
|
|
18
|
-
assert_raises RuntimeError do
|
|
19
|
-
Twiglet::Message.new(OpenStruct.new(message: 'hello'))
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
5
|
it 'returns a message hash from a string' do
|
|
24
6
|
assert_equal Twiglet::Message.new('hello, world'), { message: 'hello, world' }
|
|
25
7
|
end
|
data/test/test_coverage.rb
CHANGED
|
@@ -7,7 +7,11 @@ SimpleCov.start do
|
|
|
7
7
|
if ENV['CI']
|
|
8
8
|
formatter SimpleCov::Formatter::SimpleFormatter
|
|
9
9
|
else
|
|
10
|
-
formatter SimpleCov::Formatter::MultiFormatter.new(
|
|
11
|
-
|
|
10
|
+
formatter SimpleCov::Formatter::MultiFormatter.new(
|
|
11
|
+
[
|
|
12
|
+
SimpleCov::Formatter::SimpleFormatter,
|
|
13
|
+
SimpleCov::Formatter::HTMLFormatter
|
|
14
|
+
]
|
|
15
|
+
)
|
|
12
16
|
end
|
|
13
17
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'minitest/autorun'
|
|
4
|
+
require_relative '../lib/twiglet/validator'
|
|
5
|
+
|
|
6
|
+
describe Twiglet::Validator do
|
|
7
|
+
let(:valid)
|
|
8
|
+
|
|
9
|
+
before do
|
|
10
|
+
schema = {
|
|
11
|
+
"type" => "object",
|
|
12
|
+
"required" => ["message"],
|
|
13
|
+
"properties" => {
|
|
14
|
+
"message" => {
|
|
15
|
+
"type" => "string"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@validator = Twiglet::Validator.new(schema)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'does not raise when validation passes' do
|
|
24
|
+
assert_equal(@validator.validate({ message: 'this is my message', foo: 'bar' }), true)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'raises when validation fails' do
|
|
28
|
+
assert_raises JSON::Schema::ValidationError do
|
|
29
|
+
@validator.validate({ message: true })
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'is a no-op when validator is configured to swallow errors' do
|
|
34
|
+
@validator.custom_error_handler = ->(e) { puts e }
|
|
35
|
+
|
|
36
|
+
assert_nil(@validator.validate({ message: true }))
|
|
37
|
+
end
|
|
38
|
+
end
|
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: 3.
|
|
4
|
+
version: 3.1.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: 2021-
|
|
11
|
+
date: 2021-02-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Like a log, only smaller.
|
|
14
14
|
email:
|
|
@@ -19,6 +19,7 @@ extra_rdoc_files: []
|
|
|
19
19
|
files:
|
|
20
20
|
- ".github/CODEOWNERS"
|
|
21
21
|
- ".github/dependabot.yml"
|
|
22
|
+
- ".github/workflows/dobby-actions.yml"
|
|
22
23
|
- ".github/workflows/gem-publish.yml"
|
|
23
24
|
- ".github/workflows/ruby.yml"
|
|
24
25
|
- ".github/workflows/version-forget-me-not.yml"
|
|
@@ -41,12 +42,15 @@ files:
|
|
|
41
42
|
- lib/twiglet/formatter.rb
|
|
42
43
|
- lib/twiglet/logger.rb
|
|
43
44
|
- lib/twiglet/message.rb
|
|
45
|
+
- lib/twiglet/validation_schema.json
|
|
46
|
+
- lib/twiglet/validator.rb
|
|
44
47
|
- lib/twiglet/version.rb
|
|
45
48
|
- test/formatter_test.rb
|
|
46
49
|
- test/hash_extensions_test.rb
|
|
47
50
|
- test/logger_test.rb
|
|
48
51
|
- test/message_test.rb
|
|
49
52
|
- test/test_coverage.rb
|
|
53
|
+
- test/validator_test.rb
|
|
50
54
|
- twiglet.gemspec
|
|
51
55
|
homepage: https://github.com/simplybusiness/twiglet-ruby
|
|
52
56
|
licenses:
|
|
@@ -67,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
67
71
|
- !ruby/object:Gem::Version
|
|
68
72
|
version: '0'
|
|
69
73
|
requirements: []
|
|
70
|
-
rubygems_version: 3.
|
|
74
|
+
rubygems_version: 3.2.3
|
|
71
75
|
signing_key:
|
|
72
76
|
specification_version: 4
|
|
73
77
|
summary: Twiglet
|