twiglet 3.2.1 → 3.3.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 +1 -1
- data/.github/workflows/version-forget-me-not.yml +1 -1
- data/README.md +1 -251
- data/{CODE_OF_CONDUCT.md → docs/CODE_OF_CONDUCT.md} +0 -0
- data/{RATIONALE.md → docs/RATIONALE.md} +1 -1
- data/docs/index.md +256 -0
- data/examples/rack/request_logger_test.rb +2 -2
- data/lib/hash_extensions.rb +1 -1
- data/lib/twiglet/logger.rb +2 -2
- data/lib/twiglet/version.rb +1 -1
- data/mkdocs.yml +8 -0
- data/test/logger_test.rb +12 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b8119a5caed97ef5f329bfba3ebf1bd30a1d7f33db1b3e6908007f09facd2a0
|
4
|
+
data.tar.gz: c90de0a6b8cbc5e111f699cc9c0b1d9b0123d1e6745a7f3f93d0a3b8f52321a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94042f642ab02be62ec592691084df2445ea91c7ebccad768a270ef5b0c55e5d3152a935b8f21f1addd58e76f5b57192d864db2808262fd387969e2cfafc7d02
|
7
|
+
data.tar.gz: b050780d6193b3d69b2f5372bc2cf6870dd0ff139f42db12c2c7602f7ec5ecd5f810c71c8f77b7bdc0facfe3a5922d6de1a4733f64b8db1fe6a15a31e7327212
|
data/README.md
CHANGED
@@ -3,254 +3,4 @@ Like a log, only smaller.
|
|
3
3
|
|
4
4
|
This library provides a minimal JSON logging interface suitable for use in (micro)services. See the [RATIONALE](RATIONALE.md) for design rationale and an explantion of the Elastic Common Schema that we are using for log attribute naming.
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
```bash
|
9
|
-
gem install twiglet
|
10
|
-
```
|
11
|
-
|
12
|
-
## How to use
|
13
|
-
|
14
|
-
### Instantiate the logger
|
15
|
-
|
16
|
-
```ruby
|
17
|
-
require 'twiglet/logger'
|
18
|
-
logger = Twiglet::Logger.new('service name')
|
19
|
-
```
|
20
|
-
#### Optional initialization parameters
|
21
|
-
A hash can optionally be passed in as a keyword argument for `default_properties`. This hash must be in the Elastic Common Schema format and will be present in every log message created by this Twiglet logger object.
|
22
|
-
|
23
|
-
You may also provide an optional `output` keyword argument which should be an object with a `puts` method - like `$stdout`.
|
24
|
-
|
25
|
-
In addition, you can provide another optional keyword argument called `now`, which should be a function returning a `Time` string in ISO8601 format.
|
26
|
-
|
27
|
-
Lastly, you may provide the optional keyword argument `level` to initialize the logger with a severity threshold. Alternatively, the threshold can be updated at runtime by calling the `level` instance method.
|
28
|
-
|
29
|
-
The defaults for both `output` and `now` should serve for most uses, though you may want to override them for testing as we have done [here](test/logger_test.rb).
|
30
|
-
|
31
|
-
### Invoke the Logger
|
32
|
-
|
33
|
-
```ruby
|
34
|
-
logger.error({ event: { action: 'startup' }, message: "Emergency! There's an Emergency going on" })
|
35
|
-
```
|
36
|
-
|
37
|
-
This will write to STDOUT a JSON string:
|
38
|
-
|
39
|
-
```json
|
40
|
-
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:54:59.164+01:00","log":{"level":"error"},"event":{"action":"startup"},"message":"Emergency! There's an Emergency going on"}
|
41
|
-
```
|
42
|
-
|
43
|
-
Obviously the timestamp will be different.
|
44
|
-
|
45
|
-
Alternatively, if you just want to log some error string:
|
46
|
-
|
47
|
-
```ruby
|
48
|
-
logger.error("Emergency! There's an Emergency going on")
|
49
|
-
```
|
50
|
-
|
51
|
-
This will write to STDOUT a JSON string:
|
52
|
-
|
53
|
-
```json
|
54
|
-
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:54:59.164+01:00","log":{"level":"error"}, "message":"Emergency! There's an Emergency going on"}
|
55
|
-
```
|
56
|
-
|
57
|
-
A message is always required unless a block is provided. The message can be an object or a string.
|
58
|
-
|
59
|
-
#### Error logging
|
60
|
-
An optional error can also be provided, in which case the error message and backtrace will be logged in the relevant ECS compliant fields:
|
61
|
-
|
62
|
-
```ruby
|
63
|
-
db_err = StandardError.new('Connection timed-out')
|
64
|
-
logger.error({ message: 'DB connection failed.' }, db_err)
|
65
|
-
|
66
|
-
# this is also valid
|
67
|
-
logger.error('DB connection failed.', db_err)
|
68
|
-
```
|
69
|
-
|
70
|
-
These will both result in the same JSON string written to STDOUT:
|
71
|
-
|
72
|
-
```json
|
73
|
-
{"ecs":{"version":"1.5.0"},"@timestamp":"2020-08-21T15:44:37.890Z","service":{"name":"service name"},"log":{"level":"error"},"message":"DB connection failed.","error":{"message":"Connection timed-out"}}
|
74
|
-
```
|
75
|
-
|
76
|
-
#### Custom fields
|
77
|
-
Log custom event-specific information simply as attributes in a hash:
|
78
|
-
|
79
|
-
```ruby
|
80
|
-
logger.info({
|
81
|
-
event: { action: 'HTTP request' },
|
82
|
-
message: 'GET /pets success',
|
83
|
-
trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' },
|
84
|
-
http: {
|
85
|
-
request: { method: 'get' },
|
86
|
-
response: { status_code: 200 }
|
87
|
-
},
|
88
|
-
url: { path: '/pets' }
|
89
|
-
})
|
90
|
-
```
|
91
|
-
|
92
|
-
This writes:
|
93
|
-
|
94
|
-
```json
|
95
|
-
{"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"}}
|
96
|
-
```
|
97
|
-
|
98
|
-
Similar to error you can use string logging here as:
|
99
|
-
|
100
|
-
```
|
101
|
-
logger.info('GET /pets success')
|
102
|
-
```
|
103
|
-
|
104
|
-
This writes:
|
105
|
-
|
106
|
-
```json
|
107
|
-
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:56:49.527+01:00","log":{"level":"info"}}
|
108
|
-
```
|
109
|
-
|
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
|
-
|
112
|
-
```ruby
|
113
|
-
request_logger = logger.with({ event: { action: 'HTTP request'}, trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' }})
|
114
|
-
```
|
115
|
-
|
116
|
-
This can be used like any other Logger instance:
|
117
|
-
|
118
|
-
```ruby
|
119
|
-
request_logger.error({
|
120
|
-
message: 'Error 500 in /pets/buy',
|
121
|
-
http: {
|
122
|
-
request: { method: 'post', 'url.path': '/pet/buy' },
|
123
|
-
response: { status_code: 500 }
|
124
|
-
}
|
125
|
-
})
|
126
|
-
```
|
127
|
-
|
128
|
-
which will print:
|
129
|
-
|
130
|
-
```json
|
131
|
-
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:58:30.780+01:00","log":{"level":"error"},"event":{"action":"HTTP request"},"trace":{"id":"126bb6fa-28a2-470f-b013-eefbf9182b2d"},"message":"Error 500 in /pets/buy","http":{"request":{"method":"post","url.path":"/pet/buy"},"response":{"status_code":500}}}
|
132
|
-
```
|
133
|
-
|
134
|
-
### Log formatting
|
135
|
-
Some third party applications will allow you to optionally specify a [log formatter](https://ruby-doc.org/stdlib-2.4.0/libdoc/logger/rdoc/Logger/Formatter.html).
|
136
|
-
Supplying a Twiglet log formatter will format those third party logs so that they are ECS compliant and have the same default parameters as your application's internal logs.
|
137
|
-
|
138
|
-
To access the formatter:
|
139
|
-
```ruby
|
140
|
-
logger.formatter
|
141
|
-
```
|
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
|
-
|
147
|
-
### Log format validation
|
148
|
-
Twiglet allows for the configuration of a custom validation schema. The validation schema must be [JSON Schema](https://json-schema.org/) compliant. Any fields not explicitly included in the provided schema are permitted by default.
|
149
|
-
|
150
|
-
For example, given the following JSON Schema:
|
151
|
-
```ruby
|
152
|
-
validation_schema = <<-JSON
|
153
|
-
{
|
154
|
-
"type": "object",
|
155
|
-
"required": ["pet"],
|
156
|
-
"properties": {
|
157
|
-
"pet": {
|
158
|
-
"type": "object",
|
159
|
-
"required": ["name", "best_boy_or_girl?"],
|
160
|
-
"properties": {
|
161
|
-
"name": {
|
162
|
-
"type": "string",
|
163
|
-
"minLength": 1
|
164
|
-
},
|
165
|
-
"good_boy?": {
|
166
|
-
"type": "boolean"
|
167
|
-
}
|
168
|
-
}
|
169
|
-
}
|
170
|
-
}
|
171
|
-
}
|
172
|
-
JSON
|
173
|
-
```
|
174
|
-
|
175
|
-
The logger can be instantiated with the custom schema
|
176
|
-
```ruby
|
177
|
-
custom_logger = Twiglet::Logger.new('service name', validation_schema: validation_schema)
|
178
|
-
```
|
179
|
-
|
180
|
-
Compliant log messages will log as normal.
|
181
|
-
```ruby
|
182
|
-
# this is compliant
|
183
|
-
custom_logger.debug(pet: { name: 'Davis', good_boy?: true })
|
184
|
-
|
185
|
-
# the result
|
186
|
-
{:ecs=>{:version=>"1.5.0"}, :@timestamp=>"2020-05-11T15:01:01.000Z", :service=>{:name=>"petshop"}, :log=>{:level=>"debug"}, :pet=>{:name=>"Davis", :good_boy?=>true}}
|
187
|
-
```
|
188
|
-
|
189
|
-
Non compliant messages will raise an error.
|
190
|
-
```ruby
|
191
|
-
begin
|
192
|
-
custom_logger.debug(pet: { name: 'Davis' })
|
193
|
-
rescue JSON::Schema::ValidationError
|
194
|
-
# we forgot to specify that he's a good boy!
|
195
|
-
puts 'uh-oh'
|
196
|
-
end
|
197
|
-
```
|
198
|
-
|
199
|
-
#### Customizing error responses
|
200
|
-
Depending on the application, it may not be desirable for the logger to raise Runtime errors. Twiglet allows you to configure a custom response for handling validation errors.
|
201
|
-
|
202
|
-
Configure error handling by writing a block
|
203
|
-
```ruby
|
204
|
-
logger.configure_validation_error_response do |error|
|
205
|
-
# validation error handling goes here
|
206
|
-
# for example:
|
207
|
-
{YOUR APPLICATION BUG TRACKING SERVICE}.notify_error(error)
|
208
|
-
end
|
209
|
-
|
210
|
-
```
|
211
|
-
|
212
|
-
### Use of dotted keys (DEPRECATED)
|
213
|
-
|
214
|
-
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:
|
215
|
-
|
216
|
-
```ruby
|
217
|
-
logger.info({
|
218
|
-
'event.action': 'HTTP request',
|
219
|
-
message: 'GET /pets success',
|
220
|
-
'trace.id': '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
|
221
|
-
'http.request.method': 'get',
|
222
|
-
'http.response.status_code': 200,
|
223
|
-
'url.path': '/pets'
|
224
|
-
})
|
225
|
-
```
|
226
|
-
|
227
|
-
or mix between dotted keys and nested objects:
|
228
|
-
|
229
|
-
```ruby
|
230
|
-
logger.info({
|
231
|
-
'event.action': 'HTTP request',
|
232
|
-
message: 'GET /pets success',
|
233
|
-
trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' },
|
234
|
-
'http.request.method': 'get',
|
235
|
-
'http.response.status_code': 200,
|
236
|
-
url: { path: '/pets' }
|
237
|
-
})
|
238
|
-
```
|
239
|
-
|
240
|
-
Both cases would print out exact the same log item:
|
241
|
-
|
242
|
-
```json
|
243
|
-
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:59:31.183+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"}}
|
244
|
-
```
|
245
|
-
|
246
|
-
## How to contribute
|
247
|
-
|
248
|
-
First: Please read our project [Code of Conduct](../CODE_OF_CONDUCT.md).
|
249
|
-
|
250
|
-
Second: run the tests and make sure your changes don't break anything:
|
251
|
-
|
252
|
-
```bash
|
253
|
-
bundle exec rake test
|
254
|
-
```
|
255
|
-
|
256
|
-
Then please feel free to submit a PR.
|
6
|
+
See [Full docs](docs/index.md)
|
File without changes
|
@@ -87,4 +87,4 @@ Please see the [code of conduct](CODE_OF_CONDUCT.md) for further info.
|
|
87
87
|
|
88
88
|
# License
|
89
89
|
|
90
|
-
This work is licensed under the MIT license - see the [LICENSE](LICENSE) file for further details.
|
90
|
+
This work is licensed under the MIT license - see the [LICENSE](../LICENSE) file for further details.
|
data/docs/index.md
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
# Twiglet: Ruby version
|
2
|
+
Like a log, only smaller.
|
3
|
+
|
4
|
+
This library provides a minimal JSON logging interface suitable for use in (micro)services. See the [RATIONALE](docs/RATIONALE.md) for design rationale and an explantion of the Elastic Common Schema that we are using for log attribute naming.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
```bash
|
9
|
+
gem install twiglet
|
10
|
+
```
|
11
|
+
|
12
|
+
## How to use
|
13
|
+
|
14
|
+
### Instantiate the logger
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
require 'twiglet/logger'
|
18
|
+
logger = Twiglet::Logger.new('service name')
|
19
|
+
```
|
20
|
+
#### Optional initialization parameters
|
21
|
+
A hash can optionally be passed in as a keyword argument for `default_properties`. This hash must be in the Elastic Common Schema format and will be present in every log message created by this Twiglet logger object.
|
22
|
+
|
23
|
+
You may also provide an optional `output` keyword argument which should be an object with a `puts` method - like `$stdout`.
|
24
|
+
|
25
|
+
In addition, you can provide another optional keyword argument called `now`, which should be a function returning a `Time` string in ISO8601 format.
|
26
|
+
|
27
|
+
Lastly, you may provide the optional keyword argument `level` to initialize the logger with a severity threshold. Alternatively, the threshold can be updated at runtime by calling the `level` instance method.
|
28
|
+
|
29
|
+
The defaults for both `output` and `now` should serve for most uses, though you may want to override them for testing as we have done [here](test/logger_test.rb).
|
30
|
+
|
31
|
+
### Invoke the Logger
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
logger.error({ event: { action: 'startup' }, message: "Emergency! There's an Emergency going on" })
|
35
|
+
```
|
36
|
+
|
37
|
+
This will write to STDOUT a JSON string:
|
38
|
+
|
39
|
+
```json
|
40
|
+
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:54:59.164+01:00","log":{"level":"error"},"event":{"action":"startup"},"message":"Emergency! There's an Emergency going on"}
|
41
|
+
```
|
42
|
+
|
43
|
+
Obviously the timestamp will be different.
|
44
|
+
|
45
|
+
Alternatively, if you just want to log some error string:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
logger.error("Emergency! There's an Emergency going on")
|
49
|
+
```
|
50
|
+
|
51
|
+
This will write to STDOUT a JSON string:
|
52
|
+
|
53
|
+
```json
|
54
|
+
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:54:59.164+01:00","log":{"level":"error"}, "message":"Emergency! There's an Emergency going on"}
|
55
|
+
```
|
56
|
+
|
57
|
+
A message is always required unless a block is provided. The message can be an object or a string.
|
58
|
+
|
59
|
+
#### Error logging
|
60
|
+
An optional error can also be provided, in which case the error message and backtrace will be logged in the relevant ECS compliant fields:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
db_err = StandardError.new('Connection timed-out')
|
64
|
+
logger.error({ message: 'DB connection failed.' }, db_err)
|
65
|
+
|
66
|
+
# this is also valid
|
67
|
+
logger.error('DB connection failed.', db_err)
|
68
|
+
```
|
69
|
+
|
70
|
+
These will both result in the same JSON string written to STDOUT:
|
71
|
+
|
72
|
+
```json
|
73
|
+
{"ecs":{"version":"1.5.0"},"@timestamp":"2020-08-21T15:44:37.890Z","service":{"name":"service name"},"log":{"level":"error"},"message":"DB connection failed.","error":{"message":"Connection timed-out"}}
|
74
|
+
```
|
75
|
+
|
76
|
+
#### Custom fields
|
77
|
+
Log custom event-specific information simply as attributes in a hash:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
logger.info({
|
81
|
+
event: { action: 'HTTP request' },
|
82
|
+
message: 'GET /pets success',
|
83
|
+
trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' },
|
84
|
+
http: {
|
85
|
+
request: { method: 'get' },
|
86
|
+
response: { status_code: 200 }
|
87
|
+
},
|
88
|
+
url: { path: '/pets' }
|
89
|
+
})
|
90
|
+
```
|
91
|
+
|
92
|
+
This writes:
|
93
|
+
|
94
|
+
```json
|
95
|
+
{"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"}}
|
96
|
+
```
|
97
|
+
|
98
|
+
Similar to error you can use string logging here as:
|
99
|
+
|
100
|
+
```
|
101
|
+
logger.info('GET /pets success')
|
102
|
+
```
|
103
|
+
|
104
|
+
This writes:
|
105
|
+
|
106
|
+
```json
|
107
|
+
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:56:49.527+01:00","log":{"level":"info"}}
|
108
|
+
```
|
109
|
+
|
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
|
+
|
112
|
+
```ruby
|
113
|
+
request_logger = logger.with({ event: { action: 'HTTP request'}, trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' }})
|
114
|
+
```
|
115
|
+
|
116
|
+
This can be used like any other Logger instance:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
request_logger.error({
|
120
|
+
message: 'Error 500 in /pets/buy',
|
121
|
+
http: {
|
122
|
+
request: { method: 'post', 'url.path': '/pet/buy' },
|
123
|
+
response: { status_code: 500 }
|
124
|
+
}
|
125
|
+
})
|
126
|
+
```
|
127
|
+
|
128
|
+
which will print:
|
129
|
+
|
130
|
+
```json
|
131
|
+
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:58:30.780+01:00","log":{"level":"error"},"event":{"action":"HTTP request"},"trace":{"id":"126bb6fa-28a2-470f-b013-eefbf9182b2d"},"message":"Error 500 in /pets/buy","http":{"request":{"method":"post","url.path":"/pet/buy"},"response":{"status_code":500}}}
|
132
|
+
```
|
133
|
+
|
134
|
+
### Log formatting
|
135
|
+
Some third party applications will allow you to optionally specify a [log formatter](https://ruby-doc.org/stdlib-2.4.0/libdoc/logger/rdoc/Logger/Formatter.html).
|
136
|
+
Supplying a Twiglet log formatter will format those third party logs so that they are ECS compliant and have the same default parameters as your application's internal logs.
|
137
|
+
|
138
|
+
To access the formatter:
|
139
|
+
```ruby
|
140
|
+
logger.formatter
|
141
|
+
```
|
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
|
+
|
147
|
+
### Log format validation
|
148
|
+
Twiglet allows for the configuration of a custom validation schema. The validation schema must be [JSON Schema](https://json-schema.org/) compliant. Any fields not explicitly included in the provided schema are permitted by default.
|
149
|
+
|
150
|
+
For example, given the following JSON Schema:
|
151
|
+
```ruby
|
152
|
+
validation_schema = <<-JSON
|
153
|
+
{
|
154
|
+
"type": "object",
|
155
|
+
"required": ["pet"],
|
156
|
+
"properties": {
|
157
|
+
"pet": {
|
158
|
+
"type": "object",
|
159
|
+
"required": ["name", "best_boy_or_girl?"],
|
160
|
+
"properties": {
|
161
|
+
"name": {
|
162
|
+
"type": "string",
|
163
|
+
"minLength": 1
|
164
|
+
},
|
165
|
+
"good_boy?": {
|
166
|
+
"type": "boolean"
|
167
|
+
}
|
168
|
+
}
|
169
|
+
}
|
170
|
+
}
|
171
|
+
}
|
172
|
+
JSON
|
173
|
+
```
|
174
|
+
|
175
|
+
The logger can be instantiated with the custom schema
|
176
|
+
```ruby
|
177
|
+
custom_logger = Twiglet::Logger.new('service name', validation_schema: validation_schema)
|
178
|
+
```
|
179
|
+
|
180
|
+
Compliant log messages will log as normal.
|
181
|
+
```ruby
|
182
|
+
# this is compliant
|
183
|
+
custom_logger.debug(pet: { name: 'Davis', good_boy?: true })
|
184
|
+
|
185
|
+
# the result
|
186
|
+
{:ecs=>{:version=>"1.5.0"}, :@timestamp=>"2020-05-11T15:01:01.000Z", :service=>{:name=>"petshop"}, :log=>{:level=>"debug"}, :pet=>{:name=>"Davis", :good_boy?=>true}}
|
187
|
+
```
|
188
|
+
|
189
|
+
Non compliant messages will raise an error.
|
190
|
+
```ruby
|
191
|
+
begin
|
192
|
+
custom_logger.debug(pet: { name: 'Davis' })
|
193
|
+
rescue JSON::Schema::ValidationError
|
194
|
+
# we forgot to specify that he's a good boy!
|
195
|
+
puts 'uh-oh'
|
196
|
+
end
|
197
|
+
```
|
198
|
+
|
199
|
+
#### Customizing error responses
|
200
|
+
Depending on the application, it may not be desirable for the logger to raise Runtime errors. Twiglet allows you to configure a custom response for handling validation errors.
|
201
|
+
|
202
|
+
Configure error handling by writing a block
|
203
|
+
```ruby
|
204
|
+
logger.configure_validation_error_response do |error|
|
205
|
+
# validation error handling goes here
|
206
|
+
# for example:
|
207
|
+
{YOUR APPLICATION BUG TRACKING SERVICE}.notify_error(error)
|
208
|
+
end
|
209
|
+
|
210
|
+
```
|
211
|
+
|
212
|
+
### Use of dotted keys (DEPRECATED)
|
213
|
+
|
214
|
+
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:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
logger.info({
|
218
|
+
'event.action': 'HTTP request',
|
219
|
+
message: 'GET /pets success',
|
220
|
+
'trace.id': '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
|
221
|
+
'http.request.method': 'get',
|
222
|
+
'http.response.status_code': 200,
|
223
|
+
'url.path': '/pets'
|
224
|
+
})
|
225
|
+
```
|
226
|
+
|
227
|
+
or mix between dotted keys and nested objects:
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
logger.info({
|
231
|
+
'event.action': 'HTTP request',
|
232
|
+
message: 'GET /pets success',
|
233
|
+
trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' },
|
234
|
+
'http.request.method': 'get',
|
235
|
+
'http.response.status_code': 200,
|
236
|
+
url: { path: '/pets' }
|
237
|
+
})
|
238
|
+
```
|
239
|
+
|
240
|
+
Both cases would print out exact the same log item:
|
241
|
+
|
242
|
+
```json
|
243
|
+
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:59:31.183+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"}}
|
244
|
+
```
|
245
|
+
|
246
|
+
## How to contribute
|
247
|
+
|
248
|
+
First: Please read our project [Code of Conduct](../CODE_OF_CONDUCT.md).
|
249
|
+
|
250
|
+
Second: run the tests and make sure your changes don't break anything:
|
251
|
+
|
252
|
+
```bash
|
253
|
+
bundle exec rake test
|
254
|
+
```
|
255
|
+
|
256
|
+
Then please feel free to submit a PR.
|
@@ -65,12 +65,12 @@ describe RequestLogger do
|
|
65
65
|
end
|
66
66
|
|
67
67
|
it 'logs an error message when a request is bad' do
|
68
|
-
|
68
|
+
expect { bad_request.get("/some/path") }.must_raise StandardError
|
69
69
|
log = JSON.parse(output.string)
|
70
70
|
assert_equal log['log']['level'], 'error'
|
71
71
|
assert_equal log['error']['message'], 'some exception'
|
72
72
|
assert_equal log['error']['type'], 'StandardError'
|
73
|
-
assert_includes log['error']['stack_trace'], 'request_logger_test.rb'
|
73
|
+
assert_includes log['error']['stack_trace'].first, 'examples/rack/request_logger_test.rb'
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
data/lib/hash_extensions.rb
CHANGED
data/lib/twiglet/logger.rb
CHANGED
@@ -21,7 +21,7 @@ module Twiglet
|
|
21
21
|
|
22
22
|
now = args.fetch(:now, -> { Time.now.utc })
|
23
23
|
output = args.fetch(:output, $stdout)
|
24
|
-
level = args.fetch(:level,
|
24
|
+
level = args.fetch(:level, DEBUG)
|
25
25
|
validation_schema = args.fetch(:validation_schema, File.read("#{__dir__}/validation_schema.json"))
|
26
26
|
|
27
27
|
raise 'Service name is mandatory' \
|
@@ -70,7 +70,7 @@ module Twiglet
|
|
70
70
|
private
|
71
71
|
|
72
72
|
def add_stack_trace(hash_to_add_to, error)
|
73
|
-
hash_to_add_to[:error][:stack_trace] = error.backtrace
|
73
|
+
hash_to_add_to[:error][:stack_trace] = error.backtrace if error.backtrace
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
data/lib/twiglet/version.rb
CHANGED
data/mkdocs.yml
ADDED
data/test/logger_test.rb
CHANGED
@@ -217,7 +217,7 @@ describe Twiglet::Logger do
|
|
217
217
|
assert_equal 'Artificially raised exception', actual_log[:message]
|
218
218
|
assert_equal 'divided by 0', actual_log[:error][:message]
|
219
219
|
assert_equal 'ZeroDivisionError', actual_log[:error][:type]
|
220
|
-
assert_match 'logger_test.rb', actual_log[:error][:stack_trace].
|
220
|
+
assert_match 'test/logger_test.rb', actual_log[:error][:stack_trace].first
|
221
221
|
end
|
222
222
|
|
223
223
|
it 'should log an error without backtrace' do
|
@@ -357,6 +357,17 @@ describe Twiglet::Logger do
|
|
357
357
|
it 'initializes the logger with the provided level' do
|
358
358
|
assert_equal Logger::WARN, Twiglet::Logger.new('petshop', level: :warn).level
|
359
359
|
end
|
360
|
+
|
361
|
+
it 'does not log lower level' do
|
362
|
+
logger = Twiglet::Logger.new(
|
363
|
+
'petshop',
|
364
|
+
now: @now,
|
365
|
+
output: @buffer,
|
366
|
+
level: Logger::INFO
|
367
|
+
)
|
368
|
+
logger.debug({ name: 'Davis', best_boy_or_girl?: true, species: 'dog' })
|
369
|
+
assert_empty @buffer.read
|
370
|
+
end
|
360
371
|
end
|
361
372
|
|
362
373
|
describe 'configuring error response' do
|
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.2
|
4
|
+
version: 3.3.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-06-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json-schema
|
@@ -96,14 +96,15 @@ files:
|
|
96
96
|
- ".gitignore"
|
97
97
|
- ".rubocop.yml"
|
98
98
|
- ".ruby-version"
|
99
|
-
- CODE_OF_CONDUCT.md
|
100
99
|
- Dockerfile
|
101
100
|
- Gemfile
|
102
101
|
- LICENSE
|
103
102
|
- Makefile
|
104
|
-
- RATIONALE.md
|
105
103
|
- README.md
|
106
104
|
- Rakefile
|
105
|
+
- docs/CODE_OF_CONDUCT.md
|
106
|
+
- docs/RATIONALE.md
|
107
|
+
- docs/index.md
|
107
108
|
- example_app.rb
|
108
109
|
- examples/rack/example_rack_app.rb
|
109
110
|
- examples/rack/request_logger.rb
|
@@ -115,6 +116,7 @@ files:
|
|
115
116
|
- lib/twiglet/validation_schema.json
|
116
117
|
- lib/twiglet/validator.rb
|
117
118
|
- lib/twiglet/version.rb
|
119
|
+
- mkdocs.yml
|
118
120
|
- test/formatter_test.rb
|
119
121
|
- test/hash_extensions_test.rb
|
120
122
|
- test/logger_test.rb
|
@@ -141,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
143
|
- !ruby/object:Gem::Version
|
142
144
|
version: '0'
|
143
145
|
requirements: []
|
144
|
-
rubygems_version: 3.2.
|
146
|
+
rubygems_version: 3.2.15
|
145
147
|
signing_key:
|
146
148
|
specification_version: 4
|
147
149
|
summary: Twiglet
|