twiglet 2.3.6 → 2.3.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6b403959c8c42d8942d17af72a53419a68b8a90c0a805fbdec6703aa4e86d95
4
- data.tar.gz: 7c99b71016b8f41ab0d82cfedcb376dd5704c77e1bdb7a00a99f48a89d1b2666
3
+ metadata.gz: aa67e232d4c30f51fa72893c00e3c348f82e70b5cfe4b65c3ed695ef96a10bd9
4
+ data.tar.gz: d67da5f68a44f3d4f382f2be0d507325a1698081664b2977f44139faa117581b
5
5
  SHA512:
6
- metadata.gz: e22959d3b8796813b6f4b789ae00cf8629b83b14b31a4f525babc3718d7462c93e961391f3322c39a15bd26d08a38194cd1dd6d025d5c4871ada864be4e52c5b
7
- data.tar.gz: bcf49062251a7b68b20ef559c124e04928593fc5a004ad21cbfd3262a716f382bcc3b462ca338bfd628796e5c40d14bf4c543d430f4e9d7c542a6201a7639f2b
6
+ metadata.gz: 2100bc269dcd76fe9edefa2723f172e376ebb6340fb0f1b489cd8fc6b9629967385eb2bc081934ba42ac7d552fd63ce3232c58021df6fd74e2c331a0d4c04cbb
7
+ data.tar.gz: fc7f824c61cf1156551018a1adfe7ebae5313844725e837393d19082ff7d89487a54e8f5adcc8900c9bf3f67d398344b353b010d87c4c04f638d3be9bf7af07f
@@ -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
@@ -1,3 +1,4 @@
1
+ *~
1
2
  *.gem
2
3
  Gemfile.lock
3
4
  *.rbc
@@ -0,0 +1,7 @@
1
+ FROM ruby:2.6.5
2
+
3
+ WORKDIR /var/app
4
+ COPY Gemfile .
5
+ RUN bundle install
6
+
7
+ CMD ["bash"]
data/Gemfile CHANGED
@@ -5,4 +5,5 @@ gem 'simplycop', git: 'https://github.com/simplybusiness/simplycop.git'
5
5
  group :development, :test do
6
6
  gem 'minitest'
7
7
  gem 'rake'
8
+ gem 'simplecov', '0.17.1'
8
9
  end
@@ -0,0 +1,9 @@
1
+ .PHONY: all build test
2
+
3
+ all: build test
4
+
5
+ build:
6
+ docker build -t simplybusiness/ruby-dev:2.6.5 .
7
+
8
+ test:
9
+ docker run -it -v `pwd`:/var/app simplybusiness/ruby-dev:2.6.5 bundle exec rake test
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 message in text format
45
+ Alternatively, if you just want to log some error string:
46
+
46
47
  ```ruby
47
- logger.error( "Emergency! There's an Emergency going on")
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
- Errors can be logged as well, and this will log the error message and backtrace in the relevant ECS compliant fields:
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 text logging here as:
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
@@ -2,6 +2,6 @@ require 'rake/testtask'
2
2
 
3
3
  Rake::TestTask.new do |t|
4
4
  t.libs << "test"
5
- t.test_files = FileList['test/*_test.rb']
5
+ t.test_files = FileList['test/test_coverage.rb', 'test/*_test.rb', 'examples/rack/request_logger_test.rb']
6
6
  t.verbose = true
7
7
  end
@@ -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
@@ -1,5 +1,6 @@
1
1
  require 'logger'
2
2
  require_relative '../hash_extensions'
3
+ require_relative 'message'
3
4
 
4
5
  module Twiglet
5
6
  class Formatter < ::Logger::Formatter
@@ -17,38 +18,12 @@ module Twiglet
17
18
 
18
19
  def call(severity, _time, _progname, msg)
19
20
  level = severity.downcase
20
- log(level: level, message: msg)
21
+ log(level: level, message: Message.new(msg))
21
22
  end
22
23
 
23
24
  private
24
25
 
25
26
  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
27
  base_message = {
53
28
  ecs: {
54
29
  version: '1.5.0'
@@ -5,6 +5,7 @@ require 'time'
5
5
  require 'json'
6
6
  require_relative 'formatter'
7
7
  require_relative '../hash_extensions'
8
+ require_relative 'message'
8
9
 
9
10
  module Twiglet
10
11
  class Logger < ::Logger
@@ -29,7 +30,7 @@ module Twiglet
29
30
  super(output, formatter: formatter, level: level)
30
31
  end
31
32
 
32
- def error(message = {}, error = nil, &block)
33
+ def error(message = nil, error = nil, &block)
33
34
  if error
34
35
  error_fields = {
35
36
  'error': {
@@ -37,7 +38,7 @@ module Twiglet
37
38
  }
38
39
  }
39
40
  add_stack_trace(error_fields, error)
40
- message.is_a?(Hash) ? message.merge!(error_fields) : error_fields.merge!(message: message)
41
+ message = Message.new(message).merge(error_fields)
41
42
  end
42
43
 
43
44
  super(message, &block)
@@ -0,0 +1,24 @@
1
+ module Twiglet
2
+ class Message < Hash
3
+ def initialize(msg)
4
+ case msg
5
+ when String
6
+ self[:message] = msg
7
+ when Hash
8
+ replace(msg.transform_keys!(&:to_sym))
9
+ 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
+ end
23
+ end
24
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Twiglet
4
- VERSION = '2.3.6'
4
+ VERSION = '2.3.11'
5
5
  end
@@ -230,6 +230,16 @@ describe Twiglet::Logger do
230
230
  refute actual_log[:error].key?(:stack_trace)
231
231
  end
232
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
+
233
243
  LEVELS.each do |attrs|
234
244
  it "should correctly log level when calling #{attrs[:method]}" do
235
245
  @logger.public_send(attrs[:method], {message: 'a log message'})
@@ -0,0 +1,31 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../lib/twiglet/message'
3
+
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
+ it 'returns a message hash from a string' do
24
+ assert_equal Twiglet::Message.new('hello, world'), { message: 'hello, world' }
25
+ end
26
+
27
+ it 'returns a message hash with symbolized keys' do
28
+ input_message = { 'key' => 'value', 'message' => 'hello, world' }
29
+ assert_equal Twiglet::Message.new(input_message), { key: 'value', message: 'hello, world' }
30
+ end
31
+ end
@@ -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.6
4
+ version: 2.3.11
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-07-10 00:00:00.000000000 Z
11
+ date: 2020-08-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Like a log, only smaller.
14
14
  email:
@@ -20,23 +20,32 @@ 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
42
+ - lib/twiglet/message.rb
36
43
  - lib/twiglet/version.rb
37
44
  - test/formatter_test.rb
38
45
  - test/hash_extensions_test.rb
39
46
  - test/logger_test.rb
47
+ - test/message_test.rb
48
+ - test/test_coverage.rb
40
49
  - twiglet.gemspec
41
50
  homepage: https://github.com/simplybusiness/twiglet-ruby
42
51
  licenses: