twiglet 2.3.5 → 2.3.10
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/ruby.yml +14 -0
- data/.github/workflows/version-forget-me-not.yml +16 -0
- data/.gitignore +1 -0
- data/Dockerfile +7 -0
- data/Gemfile +1 -0
- data/Makefile +9 -0
- data/README.md +18 -6
- data/Rakefile +1 -1
- data/examples/rack/example_rack_app.rb +17 -0
- data/examples/rack/request_logger.rb +49 -0
- data/examples/rack/request_logger_test.rb +66 -0
- data/lib/twiglet/formatter.rb +3 -0
- data/lib/twiglet/logger.rb +2 -2
- data/lib/twiglet/version.rb +1 -1
- data/test/formatter_test.rb +3 -0
- data/test/logger_test.rb +18 -2
- data/test/test_coverage.rb +13 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 113edbe5053c76bc7490f90c4e3d85e68077547d26d9dc4e6e98ea474cc5beea
|
4
|
+
data.tar.gz: 94b8925876c5cc095db0f5a8380701ebd9e85d22dbca3bf2aa91cd46012920db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38f61f947ee52bfd29a0990d9dd2624ae68f628e66ecb2e67569499e3d7a81459f66d0316c9edd4ec45b2d514707474a91c0d6bd2c7661646d21ad0322a40f33
|
7
|
+
data.tar.gz: 20afdfefcd5b37363eca9797472bf1fb9f0947af68b53e994786671a69c2d26acce667351794c2e5540de786115c95cec34b8d8a52d16a206128a88382d71ff2
|
data/.github/workflows/ruby.yml
CHANGED
@@ -4,6 +4,9 @@ on:
|
|
4
4
|
push:
|
5
5
|
branches:
|
6
6
|
|
7
|
+
env:
|
8
|
+
CI: true
|
9
|
+
|
7
10
|
jobs:
|
8
11
|
build:
|
9
12
|
runs-on: ubuntu-latest
|
@@ -23,11 +26,22 @@ jobs:
|
|
23
26
|
ruby-version: ${{ matrix.ruby-version }}
|
24
27
|
- name: Install dependencies
|
25
28
|
run: bundle install
|
29
|
+
- name: install cc-test-reporter
|
30
|
+
env:
|
31
|
+
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
|
32
|
+
run: |
|
33
|
+
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-0.6.3-linux-amd64 > ./cc-test-reporter
|
34
|
+
chmod +x ./cc-test-reporter
|
35
|
+
./cc-test-reporter before-build
|
26
36
|
- name: Rubocop Check
|
27
37
|
run: bundle exec rubocop
|
28
38
|
- name: Run all tests
|
29
39
|
run: bundle exec rake test
|
30
40
|
shell: bash
|
41
|
+
- name: upload test coverage to CodeClimate
|
42
|
+
env:
|
43
|
+
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
|
44
|
+
run: ./cc-test-reporter after-build
|
31
45
|
- name: Run example_app
|
32
46
|
run: bundle exec ruby example_app.rb
|
33
47
|
shell: bash
|
@@ -0,0 +1,16 @@
|
|
1
|
+
name: Check version
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
types: [opened, synchronize]
|
8
|
+
jobs:
|
9
|
+
build:
|
10
|
+
runs-on: ubuntu-18.04
|
11
|
+
|
12
|
+
steps:
|
13
|
+
- uses: simplybusiness/version-forget-me-not@v1
|
14
|
+
env:
|
15
|
+
ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
16
|
+
VERSION_FILE_PATH: "lib/twiglet/version.rb"
|
data/.gitignore
CHANGED
data/Dockerfile
ADDED
data/Gemfile
CHANGED
data/Makefile
ADDED
data/README.md
CHANGED
@@ -42,9 +42,10 @@ This will write to STDOUT a JSON string:
|
|
42
42
|
|
43
43
|
Obviously the timestamp will be different.
|
44
44
|
|
45
|
-
Alternatively, if you just want to log some error
|
45
|
+
Alternatively, if you just want to log some error string:
|
46
|
+
|
46
47
|
```ruby
|
47
|
-
logger.error(
|
48
|
+
logger.error("Emergency! There's an Emergency going on")
|
48
49
|
```
|
49
50
|
|
50
51
|
This will write to STDOUT a JSON string:
|
@@ -53,11 +54,22 @@ This will write to STDOUT a JSON string:
|
|
53
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"}
|
54
55
|
```
|
55
56
|
|
56
|
-
|
57
|
+
A message is always required unless a block is provided. The message can be an object or a string.
|
58
|
+
|
59
|
+
An optional error can also be provided, in which case the error message and backtrace will be logged in the relevant ECS compliant fields:
|
57
60
|
|
58
61
|
```ruby
|
59
62
|
db_err = StandardError.new('Connection timed-out')
|
60
63
|
logger.error({ message: 'DB connection failed.' }, db_err)
|
64
|
+
|
65
|
+
# this is also valid
|
66
|
+
logger.error('DB connection failed.', db_err)
|
67
|
+
```
|
68
|
+
|
69
|
+
These will both result in the same JSON string written to STDOUT:
|
70
|
+
|
71
|
+
```json
|
72
|
+
{"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"}}
|
61
73
|
```
|
62
74
|
|
63
75
|
Add log event specific information simply as attributes in a hash:
|
@@ -81,18 +93,18 @@ This writes:
|
|
81
93
|
{"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"}}
|
82
94
|
```
|
83
95
|
|
84
|
-
Similar to error you can use
|
96
|
+
Similar to error you can use string logging here as:
|
85
97
|
|
86
98
|
```
|
87
99
|
logger.info('GET /pets success')
|
88
100
|
```
|
101
|
+
|
89
102
|
This writes:
|
90
103
|
|
91
104
|
```json
|
92
105
|
{"service":{"name":"service name"},"@timestamp":"2020-05-14T10:56:49.527+01:00","log":{"level":"info"}}
|
93
106
|
```
|
94
107
|
|
95
|
-
|
96
108
|
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:
|
97
109
|
|
98
110
|
```ruby
|
@@ -117,7 +129,7 @@ which will print:
|
|
117
129
|
{"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}}}
|
118
130
|
```
|
119
131
|
|
120
|
-
## Use of dotted keys
|
132
|
+
## Use of dotted keys (DEPRECATED)
|
121
133
|
|
122
134
|
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:
|
123
135
|
|
data/Rakefile
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'twiglet/logger'
|
2
|
+
require 'request_logger'
|
3
|
+
|
4
|
+
# basic rack application
|
5
|
+
class Application
|
6
|
+
def call(_env)
|
7
|
+
status = 200
|
8
|
+
headers = { "Content-Type" => "text/json" }
|
9
|
+
body = ["Example rack app"]
|
10
|
+
|
11
|
+
[status, headers, body]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
use RequestLogger, Twiglet::Logger.new('example_app')
|
16
|
+
|
17
|
+
run Application.new
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Middleware for logging request logs
|
2
|
+
class RequestLogger
|
3
|
+
def initialize(app, logger)
|
4
|
+
@app = app
|
5
|
+
@logger = logger
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
status, headers, body = @app.call(env)
|
10
|
+
log(env, status)
|
11
|
+
[status, headers, body]
|
12
|
+
rescue StandardError => e
|
13
|
+
log_error(env, 500, e)
|
14
|
+
[500, {}, body]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def log(env, status)
|
20
|
+
fields = get_fields(env, status)
|
21
|
+
@logger.info(fields)
|
22
|
+
end
|
23
|
+
|
24
|
+
def log_error(env, status, error)
|
25
|
+
fields = get_fields(env, status)
|
26
|
+
@logger.error(fields, error)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_fields(env, status)
|
30
|
+
message = "#{env['REQUEST_METHOD']}: #{env['PATH_INFO']}"
|
31
|
+
|
32
|
+
{
|
33
|
+
http: {
|
34
|
+
request: {
|
35
|
+
method: env['REQUEST_METHOD'],
|
36
|
+
server: env['SERVER_NAME'],
|
37
|
+
https_enabled: env['HTTPS'],
|
38
|
+
path: env['PATH_INFO'],
|
39
|
+
query: env['QUERY_STRING'] # Don't log PII query params
|
40
|
+
},
|
41
|
+
response: {
|
42
|
+
status: status,
|
43
|
+
body: { bytes: env['CONTENT_LENGTH'] }
|
44
|
+
}
|
45
|
+
},
|
46
|
+
message: message
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require_relative './request_logger'
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
describe RequestLogger do
|
6
|
+
let(:output) { StringIO.new }
|
7
|
+
|
8
|
+
before { output.rewind }
|
9
|
+
|
10
|
+
it 'log should not be empty' do
|
11
|
+
request.get("/some/path")
|
12
|
+
log = output.string
|
13
|
+
refute_empty log
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'logs the request data' do
|
17
|
+
request.get("/some/path?some_var=1")
|
18
|
+
log = JSON.parse(output.string)
|
19
|
+
http_body = {
|
20
|
+
"request" => {
|
21
|
+
"https_enabled" => "off",
|
22
|
+
"method" => "GET",
|
23
|
+
"path" => "/some/path",
|
24
|
+
"query" => "some_var=1",
|
25
|
+
"server" => "example.org"
|
26
|
+
},
|
27
|
+
"response" => {
|
28
|
+
"status" => 200,
|
29
|
+
"body" => { "bytes" => "0" }
|
30
|
+
}
|
31
|
+
}
|
32
|
+
assert_equal http_body, log["http"]
|
33
|
+
assert_equal "GET: /some/path", log["message"]
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'does not log PII' do
|
37
|
+
request.post("/user/info", input_data: {credit_card_no: '1234'})
|
38
|
+
log = output.string
|
39
|
+
assert_includes log, "POST: /user/info"
|
40
|
+
refute_includes log, 'credit_card_no'
|
41
|
+
refute_includes log, '1234'
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'logs an error message when a request is bad' do
|
45
|
+
bad_request.get("/some/path")
|
46
|
+
log = JSON.parse(output.string)
|
47
|
+
assert_equal 'error', log['log']['level']
|
48
|
+
assert_equal 'some exception', log['error']['message']
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def request
|
53
|
+
app = ->(env) { [200, env, "app"] }
|
54
|
+
base_request(app)
|
55
|
+
end
|
56
|
+
|
57
|
+
def bad_request
|
58
|
+
app = Rack::Lint.new ->(_env) { raise StandardError, 'some exception' }
|
59
|
+
base_request(app)
|
60
|
+
end
|
61
|
+
|
62
|
+
def base_request(app)
|
63
|
+
logger = Twiglet::Logger.new('example', output: output)
|
64
|
+
req_logger = RequestLogger.new(app, logger)
|
65
|
+
Rack::MockRequest.new(req_logger)
|
66
|
+
end
|
data/lib/twiglet/formatter.rb
CHANGED
data/lib/twiglet/logger.rb
CHANGED
@@ -29,7 +29,7 @@ module Twiglet
|
|
29
29
|
super(output, formatter: formatter, level: level)
|
30
30
|
end
|
31
31
|
|
32
|
-
def error(message =
|
32
|
+
def error(message = nil, error = nil, &block)
|
33
33
|
if error
|
34
34
|
error_fields = {
|
35
35
|
'error': {
|
@@ -37,7 +37,7 @@ module Twiglet
|
|
37
37
|
}
|
38
38
|
}
|
39
39
|
add_stack_trace(error_fields, error)
|
40
|
-
message.is_a?(Hash) ? message.merge
|
40
|
+
message = message.is_a?(Hash) ? message.merge(error_fields) : error_fields.merge(message: message)
|
41
41
|
end
|
42
42
|
|
43
43
|
super(message, &block)
|
data/lib/twiglet/version.rb
CHANGED
data/test/formatter_test.rb
CHANGED
@@ -17,6 +17,9 @@ describe Twiglet::Formatter do
|
|
17
17
|
it 'returns a formatted log from a string message' do
|
18
18
|
msg = @formatter.call('warn', nil, nil, 'shop is running low on dog food')
|
19
19
|
expected_log = {
|
20
|
+
"ecs" => {
|
21
|
+
"version" => '1.5.0'
|
22
|
+
},
|
20
23
|
"@timestamp" => '2020-05-11T15:01:01.000Z',
|
21
24
|
"service" => {
|
22
25
|
"name" => 'petshop'
|
data/test/logger_test.rb
CHANGED
@@ -49,6 +49,9 @@ describe Twiglet::Logger do
|
|
49
49
|
|
50
50
|
expected_log = {
|
51
51
|
message: 'Out of pets exception',
|
52
|
+
ecs: {
|
53
|
+
version: '1.5.0'
|
54
|
+
},
|
52
55
|
"@timestamp": '2020-05-11T15:01:01.000Z',
|
53
56
|
service: {
|
54
57
|
name: 'petshop'
|
@@ -136,10 +139,10 @@ describe Twiglet::Logger do
|
|
136
139
|
@logger.info({message: 'there'})
|
137
140
|
|
138
141
|
expected_output =
|
139
|
-
'{"@timestamp":"2020-05-11T15:01:01.000Z",'\
|
142
|
+
'{"ecs":{"version":"1.5.0"},"@timestamp":"2020-05-11T15:01:01.000Z",'\
|
140
143
|
'"service":{"name":"petshop"},"log":{"level":"debug"},"message":"hi"}'\
|
141
144
|
"\n"\
|
142
|
-
'{"@timestamp":"2020-05-11T15:01:01.000Z",'\
|
145
|
+
'{"ecs":{"version":"1.5.0"},"@timestamp":"2020-05-11T15:01:01.000Z",'\
|
143
146
|
'"service":{"name":"petshop"},"log":{"level":"info"},"message":"there"}'\
|
144
147
|
"\n"\
|
145
148
|
|
@@ -227,6 +230,16 @@ describe Twiglet::Logger do
|
|
227
230
|
refute actual_log[:error].key?(:stack_trace)
|
228
231
|
end
|
229
232
|
|
233
|
+
it 'should log an error with string message' do
|
234
|
+
e = StandardError.new('Unknown error')
|
235
|
+
@logger.error('Artificially raised exception with string message', e)
|
236
|
+
|
237
|
+
actual_log = read_json(@buffer)
|
238
|
+
|
239
|
+
assert_equal 'Artificially raised exception with string message', actual_log[:message]
|
240
|
+
assert_equal 'Unknown error', actual_log[:error][:message]
|
241
|
+
end
|
242
|
+
|
230
243
|
LEVELS.each do |attrs|
|
231
244
|
it "should correctly log level when calling #{attrs[:method]}" do
|
232
245
|
@logger.public_send(attrs[:method], {message: 'a log message'})
|
@@ -251,6 +264,9 @@ describe Twiglet::Logger do
|
|
251
264
|
|
252
265
|
expected_log = {
|
253
266
|
message: 'Out of pets exception',
|
267
|
+
ecs: {
|
268
|
+
version: '1.5.0'
|
269
|
+
},
|
254
270
|
"@timestamp": '2020-05-11T15:01:01.000Z',
|
255
271
|
service: {
|
256
272
|
name: 'petshop'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
|
3
|
+
SimpleCov.start do
|
4
|
+
add_filter "/test/"
|
5
|
+
add_filter "examples/rack/request_logger_test.rb"
|
6
|
+
|
7
|
+
if ENV['CI']
|
8
|
+
formatter SimpleCov::Formatter::SimpleFormatter
|
9
|
+
else
|
10
|
+
formatter SimpleCov::Formatter::MultiFormatter.new([SimpleCov::Formatter::SimpleFormatter,
|
11
|
+
SimpleCov::Formatter::HTMLFormatter])
|
12
|
+
end
|
13
|
+
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: 2.3.
|
4
|
+
version: 2.3.10
|
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-
|
11
|
+
date: 2020-08-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Like a log, only smaller.
|
14
14
|
email:
|
@@ -20,16 +20,22 @@ files:
|
|
20
20
|
- ".github/CODEOWNERS"
|
21
21
|
- ".github/workflows/gem-publish.yml"
|
22
22
|
- ".github/workflows/ruby.yml"
|
23
|
+
- ".github/workflows/version-forget-me-not.yml"
|
23
24
|
- ".gitignore"
|
24
25
|
- ".rubocop.yml"
|
25
26
|
- ".ruby-version"
|
26
27
|
- CODE_OF_CONDUCT.md
|
28
|
+
- Dockerfile
|
27
29
|
- Gemfile
|
28
30
|
- LICENSE
|
31
|
+
- Makefile
|
29
32
|
- RATIONALE.md
|
30
33
|
- README.md
|
31
34
|
- Rakefile
|
32
35
|
- example_app.rb
|
36
|
+
- examples/rack/example_rack_app.rb
|
37
|
+
- examples/rack/request_logger.rb
|
38
|
+
- examples/rack/request_logger_test.rb
|
33
39
|
- lib/hash_extensions.rb
|
34
40
|
- lib/twiglet/formatter.rb
|
35
41
|
- lib/twiglet/logger.rb
|
@@ -37,6 +43,7 @@ files:
|
|
37
43
|
- test/formatter_test.rb
|
38
44
|
- test/hash_extensions_test.rb
|
39
45
|
- test/logger_test.rb
|
46
|
+
- test/test_coverage.rb
|
40
47
|
- twiglet.gemspec
|
41
48
|
homepage: https://github.com/simplybusiness/twiglet-ruby
|
42
49
|
licenses:
|