alephant-logger-json 0.4.0 → 0.5.0

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
  SHA1:
3
- metadata.gz: 312008a258504317188c1b14bd2671bbe8c1cb57
4
- data.tar.gz: 28bc444b2bb9b8001a56416c0c80860ceec0525c
3
+ metadata.gz: 6716c8a5d3446d582752238b022fe71cb1af5574
4
+ data.tar.gz: eddc2df5fa9deb630a92fecc546d575bbf62ea64
5
5
  SHA512:
6
- metadata.gz: 224caa6384c612f490e1a64a4c697224ae65f8b3faf8309c976172d22873d25be6f76c3362d19cb637cbb0817e5549c09fe8d232513f9e6631ab7366dbcf98b9
7
- data.tar.gz: 7fc9d04d683e3e6dde99604e1b25eb559240b36503f59f3220323fc6940bfaf38edcbe28c23a14298f426cdc31e8c5309510d2733efed50f38509be9f5a17393
6
+ metadata.gz: 36f9a992d64486a1152cc6ef974e4e634d78dee69e5918b7eee6382fcd052280ec86e2db677926bb2851369f2a950a0895c8847c530d16fe685030321eed9bf9
7
+ data.tar.gz: 573d6232f79aa04207fb8eb7c1525cc5004ec7728a9c8aa2638798addc1596b6af58c5a703e430571ddc5459c0fd803c7956f7a2a033853b07cc65a4f2cdcbca
data/README.md CHANGED
@@ -51,7 +51,7 @@ There are two methods available to help you:
51
51
 
52
52
  When using tracing, you'll need to provide a binding context as the first argument to your log level method calls.
53
53
 
54
- This is to resolve issues with lambda's scope availability. See `Kernal#binding` for more details.
54
+ This is to resolve issues with lambda's scope availability. See `Kernal#binding` for more details.
55
55
 
56
56
  Example usage:
57
57
 
@@ -63,6 +63,37 @@ If no `binding` is provided then tracing is ignored and the logger falls back to
63
63
 
64
64
  > Note: you can hide the binding necessity behind an abstraction layer if you prefer
65
65
 
66
+ ### Logging Levels
67
+
68
+ The logger includes an option to define a desired logging level. Only log levels that are equal to or higher than the desired level will be logged.
69
+ The logger defaults to the _lowest_ level `0` i.e. `:debug` when a desired level is undefined.
70
+
71
+ Example
72
+
73
+ ```ruby
74
+ # Hierarchical Log levels
75
+ # 0 => debug
76
+ # 1 => info
77
+ # 2 => warn
78
+ # 3 => error
79
+
80
+ # When Default level :debug
81
+ json_logger = Alephant::Logger::JSON.new("path/to/logfile.log")
82
+
83
+ # Log all levels >= 0
84
+ json_logger.info "This will log"
85
+
86
+ # When log level is defined
87
+ json_logger = Alephant::Logger::JSON.new("path/to/logfile.log", level: :info)
88
+
89
+ # log all levels >= 1
90
+
91
+ json_logger.debug "This will NOT log"
92
+ json_logger.info "This will log"
93
+ ```
94
+
95
+ > Note: The logger expects the desired level to be defined as a Symbol, String or Integer type.
96
+
66
97
  ## Contributing
67
98
 
68
99
  1. Fork it ( https://github.com/BBC-News/alephant-logger-json/fork )
@@ -1,7 +1,7 @@
1
1
  module Alephant
2
2
  module Logger
3
3
  class JSON
4
- VERSION = '0.4.0'.freeze
4
+ VERSION = '0.5.0'.freeze
5
5
  end
6
6
  end
7
7
  end
@@ -1,5 +1,6 @@
1
- require_relative './dynamic_binding.rb'
2
1
  require 'json'
2
+ require_relative 'dynamic_binding'
3
+ require_relative 'levels_controller'
3
4
 
4
5
  module Alephant
5
6
  module Logger
@@ -8,10 +9,11 @@ module Alephant
8
9
  @log_file = File.open(log_path, 'a+')
9
10
  @log_file.sync = true
10
11
  @nesting = options.fetch(:nesting, false)
12
+ @write_level = options.fetch(:level, :debug)
11
13
  self.class.session = -> { 'n/a' } unless self.class.session?
12
14
  end
13
15
 
14
- [:debug, :info, :warn, :error].each do |level|
16
+ LevelsController::LEVELS.each do |level|
15
17
  define_method(level) do |b = nil, hash|
16
18
  return if hash.is_a? String
17
19
 
@@ -23,7 +25,7 @@ module Alephant
23
25
 
24
26
  hash = flatten_values_to_s(h) unless @nesting
25
27
 
26
- write(hash)
28
+ write(hash) if writeable?(level)
27
29
  end
28
30
  end
29
31
 
@@ -41,6 +43,13 @@ module Alephant
41
43
  @log_file.write(::JSON.generate(hash) + "\n")
42
44
  end
43
45
 
46
+ def writeable?(message_level)
47
+ LevelsController.should_log?(
48
+ message_level: message_level,
49
+ desired_level: @write_level
50
+ )
51
+ end
52
+
44
53
  def flatten_values_to_s(hash)
45
54
  Hash[hash.map { |k, v| [k, v.to_s] }]
46
55
  end
@@ -9,6 +9,7 @@ module Alephant
9
9
  def initialize(output, options = {})
10
10
  @output = output
11
11
  @nesting = options.fetch(:nesting, false)
12
+ @write_level = options.fetch(:level, :debug)
12
13
  self.class.session = -> { 'n/a' } unless self.class.session?
13
14
  end
14
15
 
@@ -0,0 +1,41 @@
1
+ module Alephant
2
+ module Logger
3
+ class LevelsController
4
+ LEVELS = %i(debug info warn error).freeze
5
+
6
+ class << self
7
+ def should_log?(message_level:, desired_level:)
8
+ message_level_index = level_index(message_level)
9
+
10
+ return false unless message_level_index
11
+
12
+ message_level_index >= desired_level_index(desired_level)
13
+ end
14
+
15
+ private
16
+
17
+ def desired_level_index(desired_level)
18
+ case desired_level
19
+ when Symbol then level_index_with_default(desired_level)
20
+ when String then level_index_with_default(desired_level.to_sym)
21
+ when Integer then desired_level
22
+ else
23
+ raise(
24
+ ArgumentError,
25
+ 'wrong type of argument: expected Integer, Symbol or String. '\
26
+ "got #{desired_level.class}"
27
+ )
28
+ end
29
+ end
30
+
31
+ def level_index_with_default(desired_level)
32
+ level_index(desired_level) || 0
33
+ end
34
+
35
+ def level_index(level)
36
+ LEVELS.index(level)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,137 +1,123 @@
1
1
  require 'date'
2
2
  require 'spec_helper'
3
3
  require 'alephant/logger/json'
4
+ require 'alephant/logger/levels_controller'
5
+ require_relative 'support/json_shared_examples'
4
6
 
5
7
  describe Alephant::Logger::JSON do
6
- subject { described_class.new(log_path) }
7
-
8
8
  let(:fn) { -> { 'foo' } }
9
9
  let(:log_path) { '/log/path.log' }
10
- let(:log_file) { instance_double File }
10
+ let(:log_output_obj) { instance_double(File) }
11
+ let(:msg) { :write }
11
12
 
12
13
  before do
13
- allow(File).to receive(:open) { log_file }
14
- allow(log_file).to receive :sync=
14
+ allow(File).to receive(:open) { log_output_obj }
15
+ allow(log_output_obj).to receive :sync=
15
16
  end
16
17
 
17
- shared_examples 'JSON logging' do
18
- let(:log_hash) do
19
- { 'foo' => 'bar', 'baz' => 'quux' }
20
- end
18
+ logging_levels = Alephant::Logger::LevelsController::LEVELS
21
19
 
22
- it 'writes JSON dump of hash to log with corresponding level key' do
23
- allow(Time).to receive(:now).and_return('foobar')
20
+ logging_levels.each_with_index do |level, i|
21
+ describe "##{level}" do
22
+ let(:level) { level }
23
+ let(:log_hash) { { 'foo' => 'bar', 'baz' => 'quux' } }
24
24
 
25
- expect(log_file).to receive(:write) do |json_dump|
26
- h = { 'timestamp' => 'foobar', 'uuid' => 'n/a', 'level' => level }
27
- expect(JSON.parse(json_dump)).to eq h.merge log_hash
28
- end
25
+ context 'when write level is specified' do
26
+ subject(:logger) do
27
+ described_class.new(log_path, level: write_level)
28
+ end
29
29
 
30
- subject.send(level, log_hash)
31
- end
30
+ context 'when message level is same as write level' do
31
+ context 'Symbol' do
32
+ let(:write_level) { level }
32
33
 
33
- it 'automatically includes a timestamp' do
34
- expect(log_file).to receive(:write) do |json_dump|
35
- t = JSON.parse(json_dump)['timestamp']
36
- expect { DateTime.parse(t) }.to_not raise_error
37
- end
34
+ it_behaves_like 'a JSON log writer'
38
35
 
39
- subject.send(level, log_hash)
40
- end
36
+ it_behaves_like 'nested JSON message flattened to strings'
41
37
 
42
- it 'outputs the timestamp first' do
43
- expect(log_file).to receive(:write) do |json_dump|
44
- h = JSON.parse(json_dump)
45
- expect(h.first[0].to_sym).to be :timestamp
46
- end
38
+ it_behaves_like 'gracefully fails with string message'
39
+ end
47
40
 
48
- subject.send(level, log_hash)
49
- end
41
+ context 'String' do
42
+ let(:write_level) { level.to_s }
50
43
 
51
- it 'displays a default session value if a custom function is not provided' do
52
- expect(log_file).to receive(:write) do |json_dump|
53
- h = JSON.parse(json_dump)
54
- expect(h['uuid']).to eq 'n/a'
55
- end
44
+ it_behaves_like 'a JSON log writer'
56
45
 
57
- subject.send(level, log_hash)
58
- end
46
+ it_behaves_like 'nested JSON message flattened to strings'
59
47
 
60
- it 'displays a custom session value when provided a user defined function' do
61
- expect(log_file).to receive(:write) do |json_dump|
62
- h = JSON.parse(json_dump)
63
- expect(h['uuid']).to eq 'foo'
64
- end
48
+ it_behaves_like 'gracefully fails with string message'
49
+ end
65
50
 
66
- described_class.session = fn
67
- subject.send(level, binding, log_hash)
68
- described_class.session = -> { 'n/a' }
69
- end
51
+ context 'Integer' do
52
+ let(:write_level) { logging_levels.index(level) }
70
53
 
71
- it 'provides a static method for checking if a session has been set' do
72
- described_class.session = fn
73
- expect(described_class.session?).to eq 'instance-variable'
54
+ it_behaves_like 'a JSON log writer'
74
55
 
75
- described_class.send(:remove_instance_variable, :@session)
76
- expect(described_class.session?).to eq nil
77
- end
78
- end
56
+ it_behaves_like 'nested JSON message flattened to strings'
79
57
 
80
- shared_context 'nested log hash' do
81
- let(:log_hash) do
82
- { 'nest' => nest }
83
- end
58
+ it_behaves_like 'gracefully fails with string message'
59
+ end
60
+ end
84
61
 
85
- let(:nest) { { 'bird' => 'eggs' } }
86
- end
62
+ context 'when message level is lower than write level' do
63
+ context 'Integer' do
64
+ let(:write_level) { logging_levels.index(level) + 1 }
87
65
 
88
- shared_examples 'nests flattened to strings' do
89
- include_context 'nested log hash'
66
+ it_behaves_like 'a JSON log non writer'
90
67
 
91
- specify do
92
- expect(log_file).to receive(:write) do |json_dump|
93
- expect(JSON.parse(json_dump)['nest']).to eq nest.to_s
94
- end
68
+ it_behaves_like 'gracefully fails with string message'
69
+ end
70
+ end
95
71
 
96
- subject.send(level, log_hash)
97
- end
98
- end
72
+ context 'when message level is higher than write level' do
73
+ let(:write_level_index) do
74
+ i > 0 ? logging_levels.index(level) - 1 : 0
75
+ end
99
76
 
100
- shared_examples 'nesting allowed' do
101
- include_context 'nested log hash'
77
+ context 'Symbol' do
78
+ let(:write_level) { logging_levels[write_level_index] }
102
79
 
103
- specify do
104
- expect(log_file).to receive(:write) do |json_dump|
105
- expect(JSON.parse(json_dump)).to eq log_hash
106
- end
80
+ it_behaves_like 'a JSON log writer'
107
81
 
108
- subject.send(level, log_hash)
109
- end
110
- end
82
+ it_behaves_like 'nested JSON message flattened to strings'
111
83
 
112
- shared_examples 'gracefully fail with string arg' do
113
- let(:log_message) { 'Unable to connect to server' }
84
+ it_behaves_like 'gracefully fails with string message'
85
+ end
114
86
 
115
- specify { expect(log_file).not_to receive(:write) }
116
- specify do
117
- expect { subject.debug log_message }.not_to raise_error
118
- end
119
- end
87
+ context 'String' do
88
+ let(:write_level) { logging_levels[write_level_index].to_s }
120
89
 
121
- %w(debug info warn error).each do |level|
122
- describe "##{level}" do
123
- let(:level) { level }
90
+ it_behaves_like 'a JSON log writer'
124
91
 
125
- it_behaves_like 'JSON logging'
92
+ it_behaves_like 'nested JSON message flattened to strings'
126
93
 
127
- it_behaves_like 'nests flattened to strings'
94
+ it_behaves_like 'gracefully fails with string message'
95
+ end
128
96
 
129
- it_behaves_like 'gracefully fail with string arg'
97
+ context 'Integer' do
98
+ let(:write_level) { write_level_index }
130
99
 
131
- context 'with nesting allowed' do
132
- subject do
133
- described_class.new(log_path, nesting: true)
100
+ it_behaves_like 'a JSON log writer'
101
+
102
+ it_behaves_like 'nested JSON message flattened to strings'
103
+
104
+ it_behaves_like 'gracefully fails with string message'
105
+ end
134
106
  end
107
+ end
108
+
109
+ context 'when write level is not specified' do
110
+ subject(:logger) { described_class.new(log_path) }
111
+
112
+ it_behaves_like 'a JSON log writer'
113
+
114
+ it_behaves_like 'nested JSON message flattened to strings'
115
+
116
+ it_behaves_like 'gracefully fails with string message'
117
+ end
118
+
119
+ context 'with nesting allowed' do
120
+ subject(:logger) { described_class.new(log_path, nesting: true) }
135
121
 
136
122
  it_behaves_like 'nesting allowed'
137
123
  end
@@ -1,129 +1,116 @@
1
1
  require 'date'
2
2
  require 'spec_helper'
3
3
  require 'alephant/logger/json_to_io'
4
+ require_relative 'support/json_shared_examples'
4
5
 
5
6
  describe Alephant::Logger::JSONtoIO do
6
- subject { described_class.new(logger_io) }
7
+ let(:fn) { -> { 'foo' } }
8
+ let(:log_output_obj) { spy }
9
+ let(:msg) { :puts }
7
10
 
8
- let(:fn) { -> { 'foo' } }
9
- let(:logger_io) { spy }
11
+ logging_levels = Alephant::Logger::LevelsController::LEVELS
10
12
 
11
- shared_examples 'JSON logging' do
12
- let(:log_hash) do
13
- { 'foo' => 'bar', 'baz' => 'quux' }
14
- end
13
+ logging_levels.each_with_index do |level, i|
14
+ describe "##{level}" do
15
+ let(:level) { level }
16
+ let(:log_hash) { { 'foo' => 'bar', 'baz' => 'quux' } }
15
17
 
16
- it 'writes JSON dump of hash to log with corresponding level key' do
17
- allow(Time).to receive(:now).and_return('foobar')
18
+ context 'when write level is specified' do
19
+ subject(:logger) do
20
+ described_class.new(log_output_obj, level: write_level)
21
+ end
18
22
 
19
- expect(logger_io).to receive(:puts) do |json_dump|
20
- h = { 'timestamp' => 'foobar', 'uuid' => 'n/a', 'level' => level }
21
- expect(JSON.parse(json_dump)).to eq h.merge log_hash
22
- end
23
+ context 'when message level is same as write level' do
24
+ context 'Symbol' do
25
+ let(:write_level) { level }
23
26
 
24
- subject.public_send(level, log_hash)
25
- end
27
+ it_behaves_like 'a JSON log writer'
26
28
 
27
- it 'automatically includes a timestamp' do
28
- expect(logger_io).to receive(:puts) do |json_dump|
29
- t = JSON.parse(json_dump)['timestamp']
30
- expect { DateTime.parse(t) }.to_not raise_error
31
- end
29
+ it_behaves_like 'nested JSON message flattened to strings'
32
30
 
33
- subject.public_send(level, log_hash)
34
- end
31
+ it_behaves_like 'gracefully fails with string message'
32
+ end
35
33
 
36
- it 'outputs the timestamp first' do
37
- expect(logger_io).to receive(:puts) do |json_dump|
38
- h = JSON.parse(json_dump)
39
- expect(h.first[0].to_sym).to be :timestamp
40
- end
34
+ context 'String' do
35
+ let(:write_level) { level.to_s }
41
36
 
42
- subject.public_send(level, log_hash)
43
- end
37
+ it_behaves_like 'a JSON log writer'
44
38
 
45
- it 'displays a default session value if a custom function is not provided' do
46
- expect(logger_io).to receive(:puts) do |json_dump|
47
- h = JSON.parse(json_dump)
48
- expect(h['uuid']).to eq 'n/a'
49
- end
39
+ it_behaves_like 'nested JSON message flattened to strings'
50
40
 
51
- subject.public_send(level, log_hash)
52
- end
41
+ it_behaves_like 'gracefully fails with string message'
42
+ end
53
43
 
54
- it 'displays a custom session value when provided a user defined function' do
55
- expect(logger_io).to receive(:puts) do |json_dump|
56
- h = JSON.parse(json_dump)
57
- expect(h['uuid']).to eq 'foo'
58
- end
44
+ context 'Integer' do
45
+ let(:write_level) { logging_levels.index(level) }
59
46
 
60
- described_class.session = fn
61
- subject.send(level, binding, log_hash)
62
- described_class.session = -> { 'n/a' }
63
- end
47
+ it_behaves_like 'a JSON log writer'
64
48
 
65
- it 'provides a static method for checking if a session has been set' do
66
- described_class.session = fn
67
- expect(described_class.session?).to eq 'instance-variable'
49
+ it_behaves_like 'nested JSON message flattened to strings'
68
50
 
69
- described_class.send(:remove_instance_variable, :@session)
70
- expect(described_class.session?).to eq nil
71
- end
72
- end
51
+ it_behaves_like 'gracefully fails with string message'
52
+ end
53
+ end
73
54
 
74
- shared_context 'nested log hash' do
75
- let(:log_hash) do
76
- { 'nest' => nest }
77
- end
55
+ context 'when message level is lower than write level' do
56
+ context 'Integer' do
57
+ let(:write_level) { logging_levels.index(level) + 1 }
78
58
 
79
- let(:nest) { { 'bird' => 'eggs' } }
80
- end
59
+ it_behaves_like 'a JSON log non writer'
81
60
 
82
- shared_examples 'nests flattened to strings' do
83
- include_context 'nested log hash'
61
+ it_behaves_like 'gracefully fails with string message'
62
+ end
63
+ end
84
64
 
85
- specify do
86
- expect(logger_io).to receive(:puts) do |json_dump|
87
- expect(JSON.parse(json_dump)['nest']).to eq nest.to_s
88
- end
65
+ context 'when message level is higher than write level' do
66
+ let(:write_level_index) do
67
+ i > 0 ? logging_levels.index(level) - 1 : 0
68
+ end
89
69
 
90
- subject.public_send(level, log_hash)
91
- end
92
- end
70
+ context 'Symbol' do
71
+ let(:write_level) { logging_levels[write_level_index] }
93
72
 
94
- shared_examples 'nesting allowed' do
95
- include_context 'nested log hash'
73
+ it_behaves_like 'a JSON log writer'
96
74
 
97
- specify do
98
- expect(logger_io).to receive(:puts) do |json_dump|
99
- expect(JSON.parse(json_dump)).to eq log_hash
100
- end
75
+ it_behaves_like 'nested JSON message flattened to strings'
101
76
 
102
- subject.public_send(level, log_hash)
103
- end
104
- end
77
+ it_behaves_like 'gracefully fails with string message'
78
+ end
105
79
 
106
- shared_examples 'gracefully fail with string arg' do
107
- let(:log_message) { 'Unable to connect to server' }
80
+ context 'String' do
81
+ let(:write_level) { logging_levels[write_level_index].to_s }
108
82
 
109
- specify { expect(logger_io).not_to receive(:puts) }
110
- specify do
111
- expect { subject.debug log_message }.not_to raise_error
112
- end
113
- end
83
+ it_behaves_like 'a JSON log writer'
114
84
 
115
- %w(debug info warn error).each do |level|
116
- describe "##{level}" do
117
- let(:level) { level }
85
+ it_behaves_like 'nested JSON message flattened to strings'
118
86
 
119
- it_behaves_like 'JSON logging'
87
+ it_behaves_like 'gracefully fails with string message'
88
+ end
120
89
 
121
- it_behaves_like 'nests flattened to strings'
90
+ context 'Symbol' do
91
+ let(:write_level) { write_level_index }
122
92
 
123
- it_behaves_like 'gracefully fail with string arg'
93
+ it_behaves_like 'a JSON log writer'
94
+
95
+ it_behaves_like 'nested JSON message flattened to strings'
96
+
97
+ it_behaves_like 'gracefully fails with string message'
98
+ end
99
+ end
100
+ end
101
+
102
+ context 'when write level is not specified' do
103
+ subject(:logger) { described_class.new(log_output_obj) }
104
+
105
+ it_behaves_like 'a JSON log writer'
106
+
107
+ it_behaves_like 'nested JSON message flattened to strings'
108
+
109
+ it_behaves_like 'gracefully fails with string message'
110
+ end
124
111
 
125
112
  context 'with nesting allowed' do
126
- subject { described_class.new(logger_io, nesting: true) }
113
+ subject(:logger) { described_class.new(log_output_obj, nesting: true) }
127
114
 
128
115
  it_behaves_like 'nesting allowed'
129
116
  end
@@ -0,0 +1,145 @@
1
+ require 'spec_helper'
2
+ require 'alephant/logger/levels_controller'
3
+ require_relative 'support/levels_controller_shared_examples'
4
+
5
+ RSpec.describe Alephant::Logger::LevelsController do
6
+ describe '.should_log?' do
7
+ subject(:loggable?) do
8
+ described_class.should_log?(
9
+ message_level: message_level,
10
+ desired_level: desired_level
11
+ )
12
+ end
13
+
14
+ describe 'Message level' do
15
+ let(:message_level) { :info }
16
+
17
+ context 'when message level is higher than desired level' do
18
+ context 'Symbol' do
19
+ let(:desired_level) { :debug }
20
+
21
+ it_behaves_like 'a loggable level'
22
+ end
23
+
24
+ context 'String' do
25
+ let(:desired_level) { 'debug' }
26
+
27
+ it_behaves_like 'a loggable level'
28
+ end
29
+
30
+ context 'Integer' do
31
+ let(:desired_level) { 0 }
32
+
33
+ it_behaves_like 'a loggable level'
34
+
35
+ context 'when out of range' do
36
+ let(:desired_level) { -200 }
37
+
38
+ it_behaves_like 'a loggable level'
39
+ end
40
+ end
41
+ end
42
+
43
+ context 'when message level is lower than desired level' do
44
+ context 'Symbol' do
45
+ let(:desired_level) { :warn }
46
+
47
+ it_behaves_like 'a non loggable level'
48
+ end
49
+
50
+ context 'String' do
51
+ let(:desired_level) { 'warn' }
52
+
53
+ it_behaves_like 'a non loggable level'
54
+ end
55
+
56
+ context 'Integer' do
57
+ let(:desired_level) { 3 }
58
+
59
+ it_behaves_like 'a non loggable level'
60
+
61
+ context 'when out of range' do
62
+ let(:desired_level) { 100 }
63
+
64
+ it_behaves_like 'a non loggable level'
65
+ end
66
+ end
67
+ end
68
+
69
+ context 'when message level is equal to desired level' do
70
+ context 'Symbol' do
71
+ let(:desired_level) { message_level }
72
+
73
+ it_behaves_like 'a loggable level'
74
+ end
75
+
76
+ context 'String' do
77
+ let(:desired_level) { message_level.to_s }
78
+
79
+ it_behaves_like 'a loggable level'
80
+ end
81
+
82
+ context 'Integer' do
83
+ let(:desired_level) { 1 }
84
+
85
+ it_behaves_like 'a loggable level'
86
+ end
87
+ end
88
+
89
+ context 'when message level is invalid' do
90
+ let(:desired_level) { :debug }
91
+
92
+ context 'when message level is not in LEVELS' do
93
+ let(:message_level) { :foobar }
94
+
95
+ it_behaves_like 'a non loggable level'
96
+ end
97
+
98
+ context 'when message level is nil' do
99
+ let(:message_level) { nil }
100
+
101
+ it_behaves_like 'a non loggable level'
102
+ end
103
+ end
104
+ end
105
+
106
+ describe 'Desired level' do
107
+ context 'when desired level is not in LEVELS' do
108
+ let(:message_level) { :error }
109
+ let(:desired_level) { :foobar }
110
+
111
+ it 'defaults to debug' do
112
+ expect(loggable?).to be(true)
113
+ end
114
+ end
115
+
116
+ context 'when desired level is an unsupported type' do
117
+ context 'Hash' do
118
+ let(:message_level) { :error }
119
+ let(:desired_level) { {} }
120
+
121
+ it 'raises an argument error' do
122
+ expect { loggable? }.to raise_error(
123
+ ArgumentError,
124
+ 'wrong type of argument: expected Integer, '\
125
+ 'Symbol or String. got Hash'
126
+ )
127
+ end
128
+ end
129
+
130
+ context 'Nil' do
131
+ let(:message_level) { :error }
132
+ let(:desired_level) { nil }
133
+
134
+ it 'raises an argument error' do
135
+ expect { loggable? }.to raise_error(
136
+ ArgumentError,
137
+ 'wrong type of argument: expected Integer, '\
138
+ 'Symbol or String. got NilClass'
139
+ )
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,119 @@
1
+ shared_examples 'a JSON log writer' do
2
+ let(:log_hash) do
3
+ { 'foo' => 'bar', 'baz' => 'quux' }
4
+ end
5
+
6
+ it 'writes JSON dump of hash to log with corresponding level key' do
7
+ allow(Time).to receive(:now).and_return('foobar')
8
+
9
+ expect(log_output_obj).to receive(msg) do |json_dump|
10
+ h = { 'timestamp' => 'foobar', 'uuid' => 'n/a', 'level' => level.to_s }
11
+ expect(JSON.parse(json_dump)).to eq(h.merge(log_hash))
12
+ end
13
+
14
+ logger.send(level, log_hash)
15
+ end
16
+
17
+ it 'automatically includes a timestamp' do
18
+ expect(log_output_obj).to receive(msg) do |json_dump|
19
+ t = JSON.parse(json_dump)['timestamp']
20
+ expect { DateTime.parse(t) }.not_to raise_error
21
+ end
22
+
23
+ logger.send(level, log_hash)
24
+ end
25
+
26
+ it 'outputs the timestamp first' do
27
+ expect(log_output_obj).to receive(msg) do |json_dump|
28
+ h = JSON.parse(json_dump)
29
+ expect(h.first[0].to_sym).to be(:timestamp)
30
+ end
31
+
32
+ logger.send(level, log_hash)
33
+ end
34
+
35
+ context 'when a session is set' do
36
+ it 'provides a static reader method' do
37
+ described_class.session = fn
38
+ expect(described_class.session?).to eq('instance-variable')
39
+
40
+ described_class.send(:remove_instance_variable, :@session)
41
+ expect(described_class.session?).to be(nil)
42
+ end
43
+ end
44
+
45
+ context 'when a user defined function is provided' do
46
+ it 'displays a custom session value' do
47
+ expect(log_output_obj).to receive(msg) do |json_dump|
48
+ h = JSON.parse(json_dump)
49
+ expect(h['uuid']).to eq('foo')
50
+ end
51
+
52
+ described_class.session = fn
53
+ logger.send(level, binding, log_hash)
54
+ described_class.session = -> { 'n/a' }
55
+ end
56
+ end
57
+
58
+ context 'when a user defined function is not provided' do
59
+ it 'displays a default session value' do
60
+ expect(log_output_obj).to receive(msg) do |json_dump|
61
+ h = JSON.parse(json_dump)
62
+ expect(h['uuid']).to eq('n/a')
63
+ end
64
+
65
+ logger.send(level, log_hash)
66
+ end
67
+ end
68
+ end
69
+
70
+ shared_examples 'a JSON log non writer' do
71
+ before do
72
+ allow(Time).to receive(:now).and_return('foobar')
73
+ logger.send(level, log_hash)
74
+ end
75
+
76
+ it 'does not write' do
77
+ expect(log_output_obj).not_to receive(msg)
78
+ end
79
+ end
80
+
81
+ shared_context 'nested log hash' do
82
+ let(:log_hash) { { 'nest' => nest } }
83
+
84
+ let(:nest) { { 'bird' => 'eggs' } }
85
+ end
86
+
87
+ shared_examples 'nested JSON message flattened to strings' do
88
+ include_context 'nested log hash'
89
+
90
+ specify do
91
+ expect(log_output_obj).to receive(msg) do |json_dump|
92
+ expect(JSON.parse(json_dump)['nest']).to eq(nest.to_s)
93
+ end
94
+
95
+ logger.send(level, log_hash)
96
+ end
97
+ end
98
+
99
+ shared_examples 'nesting allowed' do
100
+ include_context 'nested log hash'
101
+
102
+ specify do
103
+ expect(log_output_obj).to receive(msg) do |json_dump|
104
+ expect(JSON.parse(json_dump)).to eq(log_hash)
105
+ end
106
+
107
+ logger.send(level, log_hash)
108
+ end
109
+ end
110
+
111
+ shared_examples 'gracefully fails with string message' do
112
+ let(:log_message) { 'Unable to connect to server' }
113
+
114
+ specify { expect(log_output_obj).not_to receive(msg) }
115
+
116
+ specify do
117
+ expect { logger.debug(log_message) }.not_to raise_error
118
+ end
119
+ end
@@ -0,0 +1,11 @@
1
+ shared_examples 'a loggable level' do
2
+ it 'returns true' do
3
+ expect(loggable?).to be(true)
4
+ end
5
+ end
6
+
7
+ shared_examples 'a non loggable level' do
8
+ it 'returns false' do
9
+ expect(loggable?).to be(false)
10
+ end
11
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alephant-logger-json
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Arnould
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-23 00:00:00.000000000 Z
11
+ date: 2017-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -112,8 +112,12 @@ files:
112
112
  - lib/alephant/logger/json.rb
113
113
  - lib/alephant/logger/json/version.rb
114
114
  - lib/alephant/logger/json_to_io.rb
115
+ - lib/alephant/logger/levels_controller.rb
115
116
  - spec/alephant/logger/json_spec.rb
116
117
  - spec/alephant/logger/json_to_io_spec.rb
118
+ - spec/alephant/logger/levels_controller_spec.rb
119
+ - spec/alephant/logger/support/json_shared_examples.rb
120
+ - spec/alephant/logger/support/levels_controller_shared_examples.rb
117
121
  - spec/spec_helper.rb
118
122
  homepage: ''
119
123
  licenses:
@@ -135,11 +139,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
139
  version: '0'
136
140
  requirements: []
137
141
  rubyforge_project:
138
- rubygems_version: 2.5.1
142
+ rubygems_version: 2.6.8
139
143
  signing_key:
140
144
  specification_version: 4
141
145
  summary: alephant-logger driver enabling structured logging in JSON
142
146
  test_files:
143
147
  - spec/alephant/logger/json_spec.rb
144
148
  - spec/alephant/logger/json_to_io_spec.rb
149
+ - spec/alephant/logger/levels_controller_spec.rb
150
+ - spec/alephant/logger/support/json_shared_examples.rb
151
+ - spec/alephant/logger/support/levels_controller_shared_examples.rb
145
152
  - spec/spec_helper.rb