twiglet 2.3.6 → 2.3.11

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: 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: