alephant-logger-json 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +3 -3
- data/alephant-logger-json.gemspec +15 -15
- data/lib/alephant/logger/dynamic_binding.rb +2 -2
- data/lib/alephant/logger/json/version.rb +1 -1
- data/lib/alephant/logger/json.rb +25 -18
- data/lib/alephant/logger/json_to_io.rb +22 -0
- data/spec/alephant/logger/json_spec.rb +44 -46
- data/spec/alephant/logger/json_to_io_spec.rb +132 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 312008a258504317188c1b14bd2671bbe8c1cb57
|
4
|
+
data.tar.gz: 28bc444b2bb9b8001a56416c0c80860ceec0525c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 224caa6384c612f490e1a64a4c697224ae65f8b3faf8309c976172d22873d25be6f76c3362d19cb637cbb0817e5549c09fe8d232513f9e6631ab7366dbcf98b9
|
7
|
+
data.tar.gz: 7fc9d04d683e3e6dde99604e1b25eb559240b36503f59f3220323fc6940bfaf38edcbe28c23a14298f426cdc31e8c5309510d2733efed50f38509be9f5a17393
|
data/Rakefile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/rspec'
|
3
3
|
|
4
|
-
task :
|
4
|
+
task default: :spec
|
@@ -1,27 +1,27 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
4
|
+
require 'alephant/logger/json/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'alephant-logger-json'
|
8
8
|
spec.version = Alephant::Logger::JSON::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
9
|
+
spec.authors = ['Dan Arnould']
|
10
|
+
spec.email = ['dan@arnould.co.uk']
|
11
|
+
spec.summary = 'alephant-logger driver enabling structured logging in JSON'
|
12
|
+
spec.description = 'alephant-logger driver enabling structured logging in JSON'
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
19
|
+
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_development_dependency
|
22
|
-
spec.add_development_dependency
|
23
|
-
spec.add_development_dependency
|
24
|
-
spec.add_development_dependency
|
25
|
-
spec.add_development_dependency
|
26
|
-
spec.add_development_dependency
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
22
|
+
spec.add_development_dependency 'rake'
|
23
|
+
spec.add_development_dependency 'rspec'
|
24
|
+
spec.add_development_dependency 'rspec-nc'
|
25
|
+
spec.add_development_dependency 'rake-rspec'
|
26
|
+
spec.add_development_dependency 'pry'
|
27
27
|
end
|
@@ -7,7 +7,7 @@ module DynamicBinding
|
|
7
7
|
def method_missing(m, *args)
|
8
8
|
@bindings.reverse_each do |bind|
|
9
9
|
begin
|
10
|
-
method = eval(
|
10
|
+
method = eval('method(%s)' % m.inspect, bind)
|
11
11
|
rescue NameError
|
12
12
|
else
|
13
13
|
return method.call(*args)
|
@@ -18,7 +18,7 @@ module DynamicBinding
|
|
18
18
|
rescue NameError
|
19
19
|
end
|
20
20
|
end
|
21
|
-
raise NoMethodError,
|
21
|
+
raise NoMethodError, 'No such variable or method: %s' % m
|
22
22
|
end
|
23
23
|
|
24
24
|
def run_proc(p, *args)
|
data/lib/alephant/logger/json.rb
CHANGED
@@ -1,39 +1,46 @@
|
|
1
|
-
require_relative
|
2
|
-
require
|
1
|
+
require_relative './dynamic_binding.rb'
|
2
|
+
require 'json'
|
3
3
|
|
4
4
|
module Alephant
|
5
5
|
module Logger
|
6
6
|
class JSON
|
7
7
|
def initialize(log_path, options = {})
|
8
|
-
@log_file
|
9
|
-
@log_file.sync
|
10
|
-
@nesting
|
8
|
+
@log_file = File.open(log_path, 'a+')
|
9
|
+
@log_file.sync = true
|
10
|
+
@nesting = options.fetch(:nesting, false)
|
11
|
+
self.class.session = -> { 'n/a' } unless self.class.session?
|
11
12
|
end
|
12
13
|
|
13
14
|
[:debug, :info, :warn, :error].each do |level|
|
14
|
-
define_method(level) do |b=nil, hash|
|
15
|
+
define_method(level) do |b = nil, hash|
|
15
16
|
return if hash.is_a? String
|
16
|
-
|
17
|
+
|
17
18
|
h = {
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:
|
21
|
-
}.merge
|
22
|
-
|
23
|
-
|
19
|
+
timestamp: Time.now.to_s,
|
20
|
+
uuid: b.nil? ? 'n/a' : self.class.session.call_with_binding(b),
|
21
|
+
level: level.to_s
|
22
|
+
}.merge(hash)
|
23
|
+
|
24
|
+
hash = flatten_values_to_s(h) unless @nesting
|
25
|
+
|
26
|
+
write(hash)
|
24
27
|
end
|
25
28
|
end
|
26
29
|
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
+
class << self
|
31
|
+
attr_accessor :session
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
+
def session?
|
34
|
+
defined?(@session)
|
35
|
+
end
|
33
36
|
end
|
34
37
|
|
35
38
|
private
|
36
39
|
|
40
|
+
def write(hash)
|
41
|
+
@log_file.write(::JSON.generate(hash) + "\n")
|
42
|
+
end
|
43
|
+
|
37
44
|
def flatten_values_to_s(hash)
|
38
45
|
Hash[hash.map { |k, v| [k, v.to_s] }]
|
39
46
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'json'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Alephant
|
5
|
+
module Logger
|
6
|
+
class JSONtoIO < Alephant::Logger::JSON
|
7
|
+
attr_reader :output
|
8
|
+
|
9
|
+
def initialize(output, options = {})
|
10
|
+
@output = output
|
11
|
+
@nesting = options.fetch(:nesting, false)
|
12
|
+
self.class.session = -> { 'n/a' } unless self.class.session?
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def write(hash)
|
18
|
+
output.puts(::JSON.generate(hash))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,14 +1,12 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require 'date'
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'alephant/logger/json'
|
4
4
|
|
5
5
|
describe Alephant::Logger::JSON do
|
6
|
-
subject
|
7
|
-
described_class.new log_path
|
8
|
-
end
|
6
|
+
subject { described_class.new(log_path) }
|
9
7
|
|
10
|
-
let(:fn) { -> {
|
11
|
-
let(:log_path) {
|
8
|
+
let(:fn) { -> { 'foo' } }
|
9
|
+
let(:log_path) { '/log/path.log' }
|
12
10
|
let(:log_file) { instance_double File }
|
13
11
|
|
14
12
|
before do
|
@@ -16,32 +14,32 @@ describe Alephant::Logger::JSON do
|
|
16
14
|
allow(log_file).to receive :sync=
|
17
15
|
end
|
18
16
|
|
19
|
-
shared_examples
|
17
|
+
shared_examples 'JSON logging' do
|
20
18
|
let(:log_hash) do
|
21
|
-
{
|
19
|
+
{ 'foo' => 'bar', 'baz' => 'quux' }
|
22
20
|
end
|
23
21
|
|
24
|
-
it
|
25
|
-
allow(Time).to receive(:now).and_return(
|
22
|
+
it 'writes JSON dump of hash to log with corresponding level key' do
|
23
|
+
allow(Time).to receive(:now).and_return('foobar')
|
26
24
|
|
27
25
|
expect(log_file).to receive(:write) do |json_dump|
|
28
|
-
h = {
|
29
|
-
expect(JSON.parse
|
26
|
+
h = { 'timestamp' => 'foobar', 'uuid' => 'n/a', 'level' => level }
|
27
|
+
expect(JSON.parse(json_dump)).to eq h.merge log_hash
|
30
28
|
end
|
31
29
|
|
32
30
|
subject.send(level, log_hash)
|
33
31
|
end
|
34
32
|
|
35
|
-
it
|
33
|
+
it 'automatically includes a timestamp' do
|
36
34
|
expect(log_file).to receive(:write) do |json_dump|
|
37
|
-
t = JSON.parse(json_dump)[
|
38
|
-
expect{DateTime.parse(t)}.to_not raise_error
|
35
|
+
t = JSON.parse(json_dump)['timestamp']
|
36
|
+
expect { DateTime.parse(t) }.to_not raise_error
|
39
37
|
end
|
40
38
|
|
41
39
|
subject.send(level, log_hash)
|
42
40
|
end
|
43
41
|
|
44
|
-
it
|
42
|
+
it 'outputs the timestamp first' do
|
45
43
|
expect(log_file).to receive(:write) do |json_dump|
|
46
44
|
h = JSON.parse(json_dump)
|
47
45
|
expect(h.first[0].to_sym).to be :timestamp
|
@@ -50,69 +48,69 @@ describe Alephant::Logger::JSON do
|
|
50
48
|
subject.send(level, log_hash)
|
51
49
|
end
|
52
50
|
|
53
|
-
it
|
51
|
+
it 'displays a default session value if a custom function is not provided' do
|
54
52
|
expect(log_file).to receive(:write) do |json_dump|
|
55
53
|
h = JSON.parse(json_dump)
|
56
|
-
expect(h[
|
54
|
+
expect(h['uuid']).to eq 'n/a'
|
57
55
|
end
|
58
56
|
|
59
57
|
subject.send(level, log_hash)
|
60
58
|
end
|
61
59
|
|
62
|
-
it
|
60
|
+
it 'displays a custom session value when provided a user defined function' do
|
63
61
|
expect(log_file).to receive(:write) do |json_dump|
|
64
62
|
h = JSON.parse(json_dump)
|
65
|
-
expect(h[
|
63
|
+
expect(h['uuid']).to eq 'foo'
|
66
64
|
end
|
67
65
|
|
68
|
-
|
66
|
+
described_class.session = fn
|
69
67
|
subject.send(level, binding, log_hash)
|
70
|
-
|
68
|
+
described_class.session = -> { 'n/a' }
|
71
69
|
end
|
72
70
|
|
73
|
-
it
|
74
|
-
|
75
|
-
expect(
|
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'
|
76
74
|
|
77
|
-
|
78
|
-
expect(
|
75
|
+
described_class.send(:remove_instance_variable, :@session)
|
76
|
+
expect(described_class.session?).to eq nil
|
79
77
|
end
|
80
78
|
end
|
81
79
|
|
82
|
-
shared_context
|
80
|
+
shared_context 'nested log hash' do
|
83
81
|
let(:log_hash) do
|
84
|
-
{
|
82
|
+
{ 'nest' => nest }
|
85
83
|
end
|
86
84
|
|
87
|
-
let(:nest) { {
|
85
|
+
let(:nest) { { 'bird' => 'eggs' } }
|
88
86
|
end
|
89
87
|
|
90
|
-
shared_examples
|
91
|
-
include_context
|
88
|
+
shared_examples 'nests flattened to strings' do
|
89
|
+
include_context 'nested log hash'
|
92
90
|
|
93
91
|
specify do
|
94
92
|
expect(log_file).to receive(:write) do |json_dump|
|
95
|
-
expect(JSON.parse(json_dump)[
|
93
|
+
expect(JSON.parse(json_dump)['nest']).to eq nest.to_s
|
96
94
|
end
|
97
95
|
|
98
96
|
subject.send(level, log_hash)
|
99
97
|
end
|
100
98
|
end
|
101
99
|
|
102
|
-
shared_examples
|
103
|
-
include_context
|
100
|
+
shared_examples 'nesting allowed' do
|
101
|
+
include_context 'nested log hash'
|
104
102
|
|
105
103
|
specify do
|
106
104
|
expect(log_file).to receive(:write) do |json_dump|
|
107
|
-
expect(JSON.parse
|
105
|
+
expect(JSON.parse(json_dump)).to eq log_hash
|
108
106
|
end
|
109
107
|
|
110
108
|
subject.send(level, log_hash)
|
111
109
|
end
|
112
110
|
end
|
113
111
|
|
114
|
-
shared_examples
|
115
|
-
let(:log_message) {
|
112
|
+
shared_examples 'gracefully fail with string arg' do
|
113
|
+
let(:log_message) { 'Unable to connect to server' }
|
116
114
|
|
117
115
|
specify { expect(log_file).not_to receive(:write) }
|
118
116
|
specify do
|
@@ -124,18 +122,18 @@ describe Alephant::Logger::JSON do
|
|
124
122
|
describe "##{level}" do
|
125
123
|
let(:level) { level }
|
126
124
|
|
127
|
-
it_behaves_like
|
125
|
+
it_behaves_like 'JSON logging'
|
128
126
|
|
129
|
-
it_behaves_like
|
127
|
+
it_behaves_like 'nests flattened to strings'
|
130
128
|
|
131
|
-
it_behaves_like
|
129
|
+
it_behaves_like 'gracefully fail with string arg'
|
132
130
|
|
133
|
-
context
|
131
|
+
context 'with nesting allowed' do
|
134
132
|
subject do
|
135
|
-
described_class.new(log_path, :
|
133
|
+
described_class.new(log_path, nesting: true)
|
136
134
|
end
|
137
135
|
|
138
|
-
it_behaves_like
|
136
|
+
it_behaves_like 'nesting allowed'
|
139
137
|
end
|
140
138
|
end
|
141
139
|
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'alephant/logger/json_to_io'
|
4
|
+
|
5
|
+
describe Alephant::Logger::JSONtoIO do
|
6
|
+
subject { described_class.new(logger_io) }
|
7
|
+
|
8
|
+
let(:fn) { -> { 'foo' } }
|
9
|
+
let(:logger_io) { spy }
|
10
|
+
|
11
|
+
shared_examples 'JSON logging' do
|
12
|
+
let(:log_hash) do
|
13
|
+
{ 'foo' => 'bar', 'baz' => 'quux' }
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'writes JSON dump of hash to log with corresponding level key' do
|
17
|
+
allow(Time).to receive(:now).and_return('foobar')
|
18
|
+
|
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
|
+
|
24
|
+
subject.public_send(level, log_hash)
|
25
|
+
end
|
26
|
+
|
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
|
32
|
+
|
33
|
+
subject.public_send(level, log_hash)
|
34
|
+
end
|
35
|
+
|
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
|
41
|
+
|
42
|
+
subject.public_send(level, log_hash)
|
43
|
+
end
|
44
|
+
|
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
|
50
|
+
|
51
|
+
subject.public_send(level, log_hash)
|
52
|
+
end
|
53
|
+
|
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
|
59
|
+
|
60
|
+
described_class.session = fn
|
61
|
+
subject.send(level, binding, log_hash)
|
62
|
+
described_class.session = -> { 'n/a' }
|
63
|
+
end
|
64
|
+
|
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'
|
68
|
+
|
69
|
+
described_class.send(:remove_instance_variable, :@session)
|
70
|
+
expect(described_class.session?).to eq nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
shared_context 'nested log hash' do
|
75
|
+
let(:log_hash) do
|
76
|
+
{ 'nest' => nest }
|
77
|
+
end
|
78
|
+
|
79
|
+
let(:nest) { { 'bird' => 'eggs' } }
|
80
|
+
end
|
81
|
+
|
82
|
+
shared_examples 'nests flattened to strings' do
|
83
|
+
include_context 'nested log hash'
|
84
|
+
|
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
|
89
|
+
|
90
|
+
subject.public_send(level, log_hash)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
shared_examples 'nesting allowed' do
|
95
|
+
include_context 'nested log hash'
|
96
|
+
|
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
|
101
|
+
|
102
|
+
subject.public_send(level, log_hash)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
shared_examples 'gracefully fail with string arg' do
|
107
|
+
let(:log_message) { 'Unable to connect to server' }
|
108
|
+
|
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
|
114
|
+
|
115
|
+
%w(debug info warn error).each do |level|
|
116
|
+
describe "##{level}" do
|
117
|
+
let(:level) { level }
|
118
|
+
|
119
|
+
it_behaves_like 'JSON logging'
|
120
|
+
|
121
|
+
it_behaves_like 'nests flattened to strings'
|
122
|
+
|
123
|
+
it_behaves_like 'gracefully fail with string arg'
|
124
|
+
|
125
|
+
context 'with nesting allowed' do
|
126
|
+
subject { described_class.new(logger_io, nesting: true) }
|
127
|
+
|
128
|
+
it_behaves_like 'nesting allowed'
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
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
|
+
version: 0.4.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-
|
11
|
+
date: 2016-09-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -111,7 +111,9 @@ files:
|
|
111
111
|
- lib/alephant/logger/dynamic_binding.rb
|
112
112
|
- lib/alephant/logger/json.rb
|
113
113
|
- lib/alephant/logger/json/version.rb
|
114
|
+
- lib/alephant/logger/json_to_io.rb
|
114
115
|
- spec/alephant/logger/json_spec.rb
|
116
|
+
- spec/alephant/logger/json_to_io_spec.rb
|
115
117
|
- spec/spec_helper.rb
|
116
118
|
homepage: ''
|
117
119
|
licenses:
|
@@ -133,10 +135,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
133
135
|
version: '0'
|
134
136
|
requirements: []
|
135
137
|
rubyforge_project:
|
136
|
-
rubygems_version: 2.
|
138
|
+
rubygems_version: 2.5.1
|
137
139
|
signing_key:
|
138
140
|
specification_version: 4
|
139
141
|
summary: alephant-logger driver enabling structured logging in JSON
|
140
142
|
test_files:
|
141
143
|
- spec/alephant/logger/json_spec.rb
|
144
|
+
- spec/alephant/logger/json_to_io_spec.rb
|
142
145
|
- spec/spec_helper.rb
|