twiglet 1.1.0 → 2.0.0.pre.alpha.1

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: 8780baf991dc54df9731f5cb2f02cbf1472c4f026d4b5a0604fe332b96f84384
4
- data.tar.gz: f059d2eafbb0ac5b5ba74c0f430508ca3966326ec832fb4b56df3efc3b436faf
3
+ metadata.gz: f2bb718963cc1602d548f4f80bbf03a932feac4ceaa5b11bc3b6d19a1ee58e10
4
+ data.tar.gz: 6ee69671edd4a487a905c45dec2833141fc6a4a7b8702bdffdc2dc0b81e6e9e4
5
5
  SHA512:
6
- metadata.gz: 1d133f5b631a3a192f1dd7d7f54fecb90c481d593a747ee1ce2b9772eb91c6209af78efb0ec1ebfebe06dabbc76946029edc6a70e6c6493f2351be933ffae7e4
7
- data.tar.gz: 343cb5284055f7c5172f7451bbdeb73d1fd9f6c9f53c7341a44d997a63ffbc2f90aecece369e63e27b5955d45aa570f787d83e0c5d83cc52267e7329f9073c02
6
+ metadata.gz: 3ccecc55b8320608406a0dfec87f617e12501dbdebbe62de7b74881afde44439ba789c59b80c1530c9e4325dc424a202c1ea71958adf32ce1bda020103b38a48
7
+ data.tar.gz: 2762f0a75ac22ae69ef30de4afa022dc91e1504e578ce0fcf3198291e0f4963adbda2ab57e7e5b8a852ff2bfe6f058f3478d23c622a09a0eef98cc2821881c4b
@@ -28,5 +28,5 @@ jobs:
28
28
  - name: Rubocop Check
29
29
  run: bundle exec rubocop
30
30
  - name: Run all tests
31
- run: bundle exec ruby test/*
31
+ run: bundle exec rake test
32
32
  shell: bash
@@ -0,0 +1 @@
1
+ 2.6.5
data/Gemfile CHANGED
@@ -4,4 +4,5 @@ source 'https://rubygems.org'
4
4
  gem 'simplycop', git: 'https://github.com/simplybusiness/simplycop.git'
5
5
  group :development, :test do
6
6
  gem 'minitest'
7
+ gem 'rake'
7
8
  end
@@ -27,6 +27,7 @@ GEM
27
27
  ast (~> 2.4.0)
28
28
  rack (2.2.2)
29
29
  rainbow (3.0.0)
30
+ rake (13.0.1)
30
31
  rexml (3.2.4)
31
32
  rubocop (0.80.1)
32
33
  jaro_winkler (~> 1.5.1)
@@ -54,6 +55,7 @@ PLATFORMS
54
55
 
55
56
  DEPENDENCIES
56
57
  minitest
58
+ rake
57
59
  simplycop!
58
60
 
59
61
  BUNDLED WITH
@@ -0,0 +1,90 @@
1
+ # Twiglet
2
+
3
+ Why Twiglet? Because it's a log, only micro-sized.
4
+
5
+ This is a machine-readable first, human-readable second, JSON-based logging micro-library suitable for a wide variety of microservice uses.
6
+
7
+ This logging library is available in a cornucopia of languages:
8
+ * Ruby - here
9
+ * Python - TBC
10
+ * Node.js - [twiglet-node](https://github.com/simplybusiness/twiglet-node/)
11
+
12
+ ## Design considerations
13
+
14
+ The design goals of this library are:
15
+
16
+ - Write logs as JSON
17
+ - One line per log entry
18
+ - One JSON object per log entry
19
+ - Each log entry contains a severity[1]
20
+ - Each log entry contains an ISO8601 UTC timestamp
21
+ - Each log entry contains the name of the service that wrote it
22
+ - Each log entry pertains to an 'event'[2]
23
+ - Each log entry either a) propagates an existing trace.id or b) creates a new trace.id as a correlation ID if one does not already exist[3]
24
+ - Stack traces are written inside a single JSON log object rather than sprawling over several lines (TODO:)
25
+ - Personally Identifiable Information. Don't log it.
26
+
27
+ [1] It turns out that there isn't a single authoritative standard for severity levels, we chose to go with DEBUG, INFO, WARNING, ERROR and CRITICAL as our choices.
28
+ [2] The ‘event’ here merely refers to the action that the customer (or employee, service, or other actor) is currently attempting. It does not refer specifically to Kafka, or CQRS, though that might be the case.
29
+ [3] A correlation ID is a UUIDv4 string
30
+
31
+ ## Elastic Common Schema (ECS)
32
+ https://www.elastic.co/blog/introducing-the-elastic-common-schema
33
+ We have decided to standardise on the Elastic Common Schema for log attribute names. Whilst some attributes are expected for all logs, service owners should feel free to add relevant entries from the ECS schema if they are needed.
34
+ All application specific information is embedded in the `message` attribute JSON payload.
35
+
36
+ | Attribute name (mandatory) | Description |
37
+ | -------------------------- | ------------------------------- |
38
+ | log.level | text, one of DEBUG, INFO, WARNING, ERROR or CRITICAL. |
39
+ | service.name | text, the name of the service |
40
+ | @timestamp | text, ISO8601 UTC |
41
+ | message | text, human-readable summary |
42
+
43
+ | Attribute name (optional) | Description |
44
+ | -------------------------- | ------------------------------- |
45
+ | error.stack_trace | Stack trace, as JSON[4] |
46
+ | tags | Array, e.g. ["production"] |
47
+ | trace.id | text, UUIDv4 - a correlation ID |
48
+ | (other examples) | ... |
49
+
50
+ [4] Helper method to be provided to allow stack trace objects to be represented cleanly as JSON.
51
+
52
+ Errors should provide appropriate data using the fields from https://www.elastic.co/guide/en/ecs/current/ecs-error.html
53
+ If any other fields are provided in a log then these should be from the ECS schema rather than in a custom format, if at all possible.
54
+
55
+ ## Example log output (prettified)
56
+ ```json
57
+ {
58
+ "log": {
59
+ "level": "INFO"
60
+ },
61
+ "service": {
62
+ "name": "payments"
63
+ },
64
+ "@timestamp": "2020-05-07T11:51:33.976Z",
65
+ "event": {
66
+ "action": "customer-payment-accepted"
67
+ },
68
+ "trace": {
69
+ "id": "bf6f5ea3-614b-42f5-8e73-43deea2d1838"
70
+ },
71
+ "tags": ["staging"],
72
+ "message": "Pet cat Spot purchased",
73
+ "user": {
74
+ "email": "sleepyfox@gmail.com"
75
+ },
76
+ "pet": {
77
+ "type": "cat",
78
+ "name": "Spot",
79
+ "colour": "Ginger Tabby"
80
+ }
81
+ }
82
+ ```
83
+
84
+ # Code of conduct
85
+
86
+ Please see the [code of conduct](CODE_OF_CONDUCT.md) for further info.
87
+
88
+ # License
89
+
90
+ This work is licensed under the MIT license - see the [LICENSE](LICENSE) file for further details.
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Twiglet: Ruby version
2
2
  Like a log, only smaller.
3
3
 
4
- This library provides a minimal JSON logging interface suitable for use in (micro)services. See the [README](../README.md) for design rationale and an explantion of the Elastic Common Schema that we are using for log attribute naming.
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
6
  ## Installation
7
7
 
@@ -15,10 +15,16 @@ Create a new logger like so:
15
15
 
16
16
  ```ruby
17
17
  require 'twiglet/logger'
18
- logger = Twiglet::Logger.new(conf: { service: 'petshop' })
18
+ logger = Twiglet::Logger.new('service name')
19
19
  ```
20
20
 
21
- The logger may be passed in the configuration object an optional `output` attribute which should be an object with a `puts` method - like `$stdout`. The configuration object may also have an optional `now` attribute, which should be a function returning a `Time` object. The defaults should serve for most uses, though you may want to override them for testing as we have done [here](test/logger_test.rb).
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
+ Lastly, you can provide another optional keyword argument called `now`, which should be a function returning a `Time` string in ISO8601 format.
26
+
27
+ 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).
22
28
 
23
29
  To use, simply invoke like most other loggers:
24
30
 
@@ -29,7 +35,7 @@ logger.error({ event: { action: 'startup' }, message: "Emergency! There's an Eme
29
35
  This will write to STDOUT a JSON string:
30
36
 
31
37
  ```json
32
- {"service":{"name":"petshop"},"@timestamp":"2020-05-14T10:54:59.164+01:00","log":{"level":"error"},"event":{"action":"startup"},"message":"Emergency! There's an Emergency going on"}
38
+ {"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"}
33
39
  ```
34
40
 
35
41
  Obviously the timestamp will be different.
@@ -52,7 +58,7 @@ logger.info({
52
58
  This writes:
53
59
 
54
60
  ```json
55
- {"service":{"name":"petshop"},"@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"}}
61
+ {"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"}}
56
62
  ```
57
63
 
58
64
  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:
@@ -76,7 +82,7 @@ request_logger.error({
76
82
  which will print:
77
83
 
78
84
  ```json
79
- {"service":{"name":"petshop"},"@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}}}
85
+ {"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}}}
80
86
  ```
81
87
 
82
88
  ## Use of dotted keys
@@ -110,7 +116,7 @@ logger.info({
110
116
  Both cases would print out exact the same log item:
111
117
 
112
118
  ```json
113
- {"service":{"name":"petshop"},"@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"}}
119
+ {"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"}}
114
120
  ```
115
121
 
116
122
  ## How to contribute
@@ -0,0 +1,7 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << "test"
5
+ t.test_files = FileList['test/*_test.rb']
6
+ t.verbose = true
7
+ end
@@ -4,7 +4,7 @@ require_relative 'lib/twiglet/logger'
4
4
 
5
5
  PORT = 8080
6
6
 
7
- logger = Logger.new(conf: { service: 'petshop' })
7
+ logger = Twiglet::Logger.new('petshop')
8
8
 
9
9
  # Start our petshop
10
10
  logger.info({
@@ -45,7 +45,7 @@ request_logger.info({
45
45
  }
46
46
  })
47
47
 
48
- # Logging with a non-empty message is an anti-pattern and is therefore forbidden
48
+ # Logging with an empty message is an anti-pattern and is therefore forbidden
49
49
  # Both of the following lines would throw an error
50
50
  # request_logger.error({ message: "" })
51
51
  # logger.debug({ message: " " })
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HashExtensions
4
+ def to_nested
5
+ self unless contains_dotted_key?
6
+
7
+ keys.reduce({}) do |nested, key|
8
+ nested.deep_merge(build_nested_object(key, self[key]))
9
+ end
10
+ end
11
+
12
+ def deep_merge(hash_to_merge)
13
+ merger = proc { |_, val1, val2| val1.is_a?(Hash) && val2.is_a?(Hash) ? val1.merge(val2, &merger) : val2 }
14
+ merge(hash_to_merge, &merger)
15
+ end
16
+
17
+ private
18
+
19
+ def contains_dotted_key?
20
+ keys.any? { |x| x.to_s.include?('.') }
21
+ end
22
+
23
+ def build_nested_object(key, val)
24
+ key.to_s
25
+ .split('.')
26
+ .reverse
27
+ .reduce(val) { |nested, key_part| Hash[key_part.to_sym, nested] }
28
+ end
29
+ end
@@ -2,21 +2,26 @@
2
2
 
3
3
  require 'time'
4
4
  require 'json'
5
- require_relative '../elastic_common_schema'
5
+ require_relative '../hash_extensions'
6
6
 
7
7
  module Twiglet
8
8
  class Logger
9
- include ElasticCommonSchema
10
-
11
- def initialize(conf:, scoped_properties: {})
12
- @service = conf[:service]
13
- @now = conf[:now] || -> { Time.now.utc }
14
- @output = conf[:output] || $stdout
15
-
16
- raise 'configuration must have a service name' \
17
- unless @service.is_a?(String) && !@service.strip.empty?
18
-
19
- @scoped_properties = scoped_properties
9
+ Hash.include HashExtensions
10
+
11
+ def initialize(
12
+ service_name,
13
+ default_properties: {},
14
+ now: -> { Time.now.utc },
15
+ output: $stdout
16
+ )
17
+ @service_name = service_name
18
+ @now = now
19
+ @output = output
20
+
21
+ raise 'Service name is mandatory' \
22
+ unless @service_name.is_a?(String) && !@service_name.strip.empty?
23
+
24
+ @default_properties = default_properties
20
25
  end
21
26
 
22
27
  def debug(message)
@@ -46,11 +51,11 @@ module Twiglet
46
51
  log(level: 'critical', message: message)
47
52
  end
48
53
 
49
- def with(scoped_properties)
50
- Logger.new(conf: { service: @service,
51
- now: @now,
52
- output: @output },
53
- scoped_properties: scoped_properties)
54
+ def with(default_properties)
55
+ Logger.new(@service_name,
56
+ default_properties: default_properties,
57
+ now: @now,
58
+ output: @output)
54
59
  end
55
60
 
56
61
  private
@@ -63,20 +68,20 @@ module Twiglet
63
68
 
64
69
  message[:message].strip.empty? && raise('The \'message\' property of log object must not be empty')
65
70
 
66
- total_message = {
71
+ base_message = {
67
72
  service: {
68
- name: @service
73
+ name: @service_name
69
74
  },
70
75
  "@timestamp": @now.call.iso8601(3),
71
76
  log: {
72
77
  level: level
73
78
  }
74
79
  }
75
- total_message = total_message.merge(@scoped_properties)
76
- .merge!(message)
77
- .then { |log_entry| to_nested(log_entry) }
78
80
 
79
- @output.puts total_message.to_json
81
+ @output.puts base_message
82
+ .deep_merge(@default_properties.to_nested)
83
+ .deep_merge(message.to_nested)
84
+ .to_json
80
85
  end
81
86
  end
82
87
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Twiglet
4
- VERSION = '1.1.0'
4
+ VERSION = '2.0.0-alpha.1'
5
5
  end
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'minitest/autorun'
4
- require_relative '../lib/elastic_common_schema'
4
+ require_relative '../lib/hash_extensions'
5
5
 
6
- describe ElasticCommonSchema do
6
+ describe HashExtensions do
7
7
  before do
8
- @ecs = Object.new
9
- @ecs.extend(ElasticCommonSchema)
8
+ Hash.include HashExtensions
10
9
  end
11
10
 
12
11
  it 'should retain an object without . in any keys' do
@@ -21,7 +20,7 @@ describe ElasticCommonSchema do
21
20
  "@timestamp": '2020-05-09T15:13:20.736Z'
22
21
  }
23
22
 
24
- expected = @ecs.to_nested(actual)
23
+ expected = actual.to_nested
25
24
  assert_equal actual, expected
26
25
  end
27
26
 
@@ -31,7 +30,7 @@ describe ElasticCommonSchema do
31
30
  "log.level": 'error'
32
31
  }
33
32
 
34
- nested = @ecs.to_nested(actual)
33
+ nested = actual.to_nested
35
34
 
36
35
  assert_equal 'petshop', nested[:service][:name]
37
36
  assert_equal 'error', nested[:log][:level]
@@ -45,7 +44,7 @@ describe ElasticCommonSchema do
45
44
  "log.level": 'error'
46
45
  }
47
46
 
48
- nested = @ecs.to_nested(actual)
47
+ nested = actual.to_nested
49
48
 
50
49
  assert_equal 'petshop', nested[:service][:name]
51
50
  assert_equal 'ps001', nested[:service][:id]
@@ -61,7 +60,7 @@ describe ElasticCommonSchema do
61
60
  "http.response.status_code": 200
62
61
  }
63
62
 
64
- nested = @ecs.to_nested(actual)
63
+ nested = actual.to_nested
65
64
 
66
65
  assert_equal 'get', nested[:http][:request][:method]
67
66
  assert_equal 112, nested[:http][:request][:body][:bytes]
@@ -73,7 +72,7 @@ describe ElasticCommonSchema do
73
72
  first = { id: 1, name: 'petshop' }
74
73
  second = { level: 'debug', code: 5 }
75
74
 
76
- actual = @ecs.deep_merge(first, second)
75
+ actual = first.deep_merge(second)
77
76
 
78
77
  assert_equal 1, actual[:id]
79
78
  assert_equal 'petshop', actual[:name]
@@ -85,7 +84,7 @@ describe ElasticCommonSchema do
85
84
  first = { id: 1, name: 'petshop', level: 'debug' }
86
85
  second = { name: 'petstore', level: 'error', code: 5 }
87
86
 
88
- actual = @ecs.deep_merge(first, second)
87
+ actual = first.deep_merge(second)
89
88
 
90
89
  assert_equal 1, actual[:id]
91
90
  assert_equal 'petstore', actual[:name]
@@ -97,7 +96,7 @@ describe ElasticCommonSchema do
97
96
  first = { service: { name: 'petshop' } }
98
97
  second = { service: { id: 'ps001' } }
99
98
 
100
- actual = @ecs.deep_merge(first, second)
99
+ actual = first.deep_merge(second)
101
100
  assert_equal 'petshop', actual[:service][:name]
102
101
  assert_equal 'ps001', actual[:service][:id]
103
102
  end
@@ -106,7 +105,7 @@ describe ElasticCommonSchema do
106
105
  first = { http: { request: { method: 'get', bytes: 124 } } }
107
106
  second = { http: { response: { status_code: 200, bytes: 5001 } } }
108
107
 
109
- actual = @ecs.deep_merge(first, second)
108
+ actual = first.deep_merge(second)
110
109
 
111
110
  assert_equal 'get', actual[:http][:request][:method]
112
111
  assert_equal 124, actual[:http][:request][:bytes]
@@ -118,7 +117,7 @@ describe ElasticCommonSchema do
118
117
  first = {}
119
118
  second = { id: 1 }
120
119
 
121
- actual = @ecs.deep_merge(first, second)
120
+ actual = first.deep_merge(second)
122
121
 
123
122
  assert_equal 1, actual[:id]
124
123
  end
@@ -127,7 +126,7 @@ describe ElasticCommonSchema do
127
126
  first = { id: 1 }
128
127
  second = {}
129
128
 
130
- actual = @ecs.deep_merge(first, second)
129
+ actual = first.deep_merge(second)
131
130
 
132
131
  assert_equal 1, actual[:id]
133
132
  end
@@ -7,16 +7,14 @@ describe Twiglet::Logger do
7
7
  before do
8
8
  @now = -> { Time.utc(2020, 5, 11, 15, 1, 1) }
9
9
  @buffer = StringIO.new
10
- @logger = Twiglet::Logger.new(conf: {
11
- service: 'petshop',
12
- now: @now,
13
- output: @buffer
14
- })
10
+ @logger = Twiglet::Logger.new('petshop',
11
+ now: @now,
12
+ output: @buffer)
15
13
  end
16
14
 
17
15
  it 'should throw an error with an empty service name' do
18
16
  assert_raises RuntimeError do
19
- Twiglet::Logger.new(conf: { service: ' ' })
17
+ Twiglet::Logger.new(' ')
20
18
  end
21
19
  end
22
20
 
@@ -27,7 +25,7 @@ describe Twiglet::Logger do
27
25
  end
28
26
 
29
27
  it 'should log mandatory attributes' do
30
- @logger.error({ message: 'Out of pets exception' })
28
+ @logger.error({message: 'Out of pets exception'})
31
29
  actual_log = read_json(@buffer)
32
30
 
33
31
  expected_log = {
@@ -45,9 +43,9 @@ describe Twiglet::Logger do
45
43
  end
46
44
 
47
45
  it 'should log the provided message' do
48
- @logger.error({ event:
49
- { action: 'exception' },
50
- message: 'Emergency! Emergency!' })
46
+ @logger.error({event:
47
+ {action: 'exception'},
48
+ message: 'Emergency! Emergency!'})
51
49
  log = read_json(@buffer)
52
50
 
53
51
  assert_equal 'exception', log[:event][:action]
@@ -59,22 +57,25 @@ describe Twiglet::Logger do
59
57
  trace: {
60
58
  id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'
61
59
  },
62
- request: { method: 'get' },
63
- response: { status_code: 200 }
60
+ service: {
61
+ type: 'shop'
62
+ },
63
+ request: {method: 'get'},
64
+ response: {status_code: 200}
64
65
  }
65
66
 
66
67
  output = StringIO.new
67
- logger = Twiglet::Logger.new(conf: {
68
- service: 'petshop',
69
- now: @now,
70
- output: output
71
- },
72
- scoped_properties: extra_properties)
73
-
74
- logger.error({ message: 'GET /cats' })
68
+ logger = Twiglet::Logger.new('petshop',
69
+ now: @now,
70
+ output: output,
71
+ default_properties: extra_properties)
72
+
73
+ logger.error({message: 'GET /cats'})
75
74
  log = read_json output
76
75
 
77
76
  assert_equal '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb', log[:trace][:id]
77
+ assert_equal 'petshop', log[:service][:name]
78
+ assert_equal 'shop', log[:service][:type]
78
79
  assert_equal 'get', log[:request][:method]
79
80
  assert_equal 200, log[:response][:status_code]
80
81
  end
@@ -82,15 +83,15 @@ describe Twiglet::Logger do
82
83
  it "should be able to add properties with '.with'" do
83
84
  # Let's add some context to this customer journey
84
85
  purchase_logger = @logger.with({
85
- trace: { id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb' },
86
- customer: { full_name: 'Freda Bloggs' },
87
- event: { action: 'pet purchase' }
86
+ trace: {id: '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb'},
87
+ customer: {full_name: 'Freda Bloggs'},
88
+ event: {action: 'pet purchase'}
88
89
  })
89
90
 
90
91
  # do stuff
91
92
  purchase_logger.info({
92
93
  message: 'customer bought a dog',
93
- pet: { name: 'Barker', species: 'dog', breed: 'Bitsa' }
94
+ pet: {name: 'Barker', species: 'dog', breed: 'Bitsa'}
94
95
  })
95
96
 
96
97
  log = read_json @buffer
@@ -132,7 +133,7 @@ describe Twiglet::Logger do
132
133
  @logger.debug({
133
134
  "trace.id": '1c8a5fb2-fecd-44d8-92a4-449eb2ce4dcb',
134
135
  message: 'customer bought a dog',
135
- pet: { name: 'Barker', breed: 'Bitsa' },
136
+ pet: {name: 'Barker', breed: 'Bitsa'},
136
137
  "pet.species": 'dog'
137
138
  })
138
139
  log = read_json(@buffer)
@@ -171,7 +172,7 @@ describe Twiglet::Logger do
171
172
  begin
172
173
  1 / 0
173
174
  rescue StandardError => e
174
- @logger.error({ message: 'Artificially raised exception' }, e)
175
+ @logger.error({message: 'Artificially raised exception'}, e)
175
176
  end
176
177
 
177
178
  actual_log = read_json(@buffer)
@@ -9,7 +9,7 @@ Gem::Specification.new do |gem|
9
9
  gem.version = Twiglet::VERSION
10
10
  gem.authors = ['Simply Business']
11
11
  gem.email = ['tech@simplybusiness.co.uk']
12
- gem.homepage = 'https://github.com/simplybusiness/twiglet'
12
+ gem.homepage = 'https://github.com/simplybusiness/twiglet-ruby'
13
13
 
14
14
  gem.summary = 'Twiglet'
15
15
  gem.description = 'Like a log, only smaller.'
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: 1.1.0
4
+ version: 2.0.0.pre.alpha.1
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-06-01 00:00:00.000000000 Z
11
+ date: 2020-06-03 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Like a log, only smaller.
14
14
  email:
@@ -21,19 +21,22 @@ files:
21
21
  - ".github/workflows/ruby.yml"
22
22
  - ".gitignore"
23
23
  - ".rubocop.yml"
24
+ - ".ruby-version"
24
25
  - CODE_OF_CONDUCT.md
25
26
  - Gemfile
26
27
  - Gemfile.lock
27
28
  - LICENSE
29
+ - RATIONALE.md
28
30
  - README.md
31
+ - Rakefile
29
32
  - example_app.rb
30
- - lib/elastic_common_schema.rb
33
+ - lib/hash_extensions.rb
31
34
  - lib/twiglet/logger.rb
32
35
  - lib/twiglet/version.rb
33
- - test/elastic_common_schema_test.rb
36
+ - test/hash_extensions_test.rb
34
37
  - test/logger_test.rb
35
38
  - twiglet.gemspec
36
- homepage: https://github.com/simplybusiness/twiglet
39
+ homepage: https://github.com/simplybusiness/twiglet-ruby
37
40
  licenses:
38
41
  - Copyright SimplyBusiness
39
42
  metadata: {}
@@ -48,11 +51,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
48
51
  version: '2.6'
49
52
  required_rubygems_version: !ruby/object:Gem::Requirement
50
53
  requirements:
51
- - - ">="
54
+ - - ">"
52
55
  - !ruby/object:Gem::Version
53
- version: '0'
56
+ version: 1.3.1
54
57
  requirements: []
55
- rubygems_version: 3.0.3
58
+ rubygems_version: 3.1.2
56
59
  signing_key:
57
60
  specification_version: 4
58
61
  summary: Twiglet
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ElasticCommonSchema
4
- def to_nested(log)
5
- log unless contains_dotted_key?(log)
6
-
7
- log.keys.reduce({}) do |nested, key|
8
- deep_merge(nested, build_nested_object(key, log[key]))
9
- end
10
- end
11
-
12
- def deep_merge(hash1, hash2)
13
- merger = proc { |_, val1, val2| val1.is_a?(Hash) && val2.is_a?(Hash) ? val1.merge(val2, &merger) : val2 }
14
- hash1.merge(hash2, &merger)
15
- end
16
-
17
- private
18
-
19
- def contains_dotted_key?(log)
20
- log.keys.any? { |x| x.to_s.include?('.') }
21
- end
22
-
23
- def build_nested_object(key, val)
24
- key.to_s
25
- .split('.')
26
- .reverse
27
- .reduce(val) { |nested, key_part| Hash[key_part.to_sym, nested] }
28
- end
29
- end