loga 1.4.0 → 2.0.0

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.
@@ -1,68 +1,183 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Loga::Configuration do
4
- subject do
5
- described_class.new.tap { |config| config.device = STDOUT }
4
+ let(:options) do
5
+ { service_name: 'hello_world_app' }
6
6
  end
7
7
 
8
+ subject { described_class.new(options) }
9
+
8
10
  describe 'initialize' do
9
- subject { described_class.new }
11
+ let(:framework_exceptions) do
12
+ %w(
13
+ ActionController::RoutingError
14
+ ActiveRecord::RecordNotFound
15
+ Sinatra::NotFound
16
+ )
17
+ end
18
+
19
+ before do
20
+ allow(Loga::ServiceVersionStrategies).to receive(:call).and_return('unknown.sha')
21
+ end
22
+
10
23
  context 'defaults' do
24
+ specify { expect(subject.device).to eq(STDOUT) }
25
+ specify { expect(subject.filter_exceptions).to eq(framework_exceptions) }
26
+ specify { expect(subject.filter_parameters).to eq([]) }
27
+ specify { expect(subject.format).to eq(:simple) }
11
28
  specify { expect(subject.host).to eq(hostname_anchor) }
12
29
  specify { expect(subject.level).to eq(:info) }
13
- specify { expect(subject.device).to eq(nil) }
30
+ specify { expect(subject.service_name).to eq('hello_world_app') }
31
+ specify { expect(subject.service_version).to eq('unknown.sha') }
14
32
  specify { expect(subject.sync).to eq(true) }
15
- specify { expect(subject.filter_parameters).to eq([]) }
16
- specify { expect(subject.service_name).to eq(nil) }
17
- specify { expect(subject.service_version).to eq(:git) }
33
+ specify { expect(subject.tags).to eq([]) }
18
34
  end
19
35
 
20
- context 'when hostname cannot be resolved' do
21
- before do
22
- allow(Socket).to receive(:gethostname).and_raise(Exception)
36
+ describe 'device' do
37
+ context 'when initialized with nil' do
38
+ let(:options) { super().merge(device: nil) }
39
+
40
+ it 'raises an error' do
41
+ expect { described_class.new(options) }
42
+ .to raise_error(Loga::ConfigurationError, 'Device cannot be blank')
43
+ end
23
44
  end
45
+ end
24
46
 
25
- it 'uses a default hostname' do
26
- expect(subject.host).to eq('unknown.host')
47
+ describe 'hostname' do
48
+ context 'when hostname cannot be resolved' do
49
+ before do
50
+ allow(Socket).to receive(:gethostname).and_raise(SystemCallError, 'Something')
51
+ end
52
+
53
+ it 'uses a default hostname' do
54
+ expect(subject.host).to eq('unknown.host')
55
+ end
27
56
  end
28
57
  end
29
- end
30
58
 
31
- describe '#initialize!' do
32
- before do
33
- subject.tap do |config|
34
- config.service_name = ' hello_world_app '
35
- config.service_version = " 1.0\n"
59
+ describe 'service_name' do
60
+ context 'when service name is missing' do
61
+ let(:options) do
62
+ { service_: 'hello_world_app' }
63
+ end
64
+
65
+ it 'raises an error' do
66
+ expect { subject }.to raise_error(Loga::ConfigurationError,
67
+ 'Service name cannot be blank')
68
+ end
36
69
  end
37
70
  end
38
71
 
39
- it 'initializes the formatter with stiped service name and version' do
40
- expect(Loga::Formatter).to receive(:new)
41
- .with(service_name: 'hello_world_app',
42
- service_version: '1.0',
43
- host: hostname_anchor)
44
- subject.initialize!
72
+ describe 'service_version' do
73
+ context 'when service version is missing' do
74
+ it 'uses a service version strategy' do
75
+ expect(subject.service_version).to eq('unknown.sha')
76
+ end
77
+ end
78
+ context 'when initialized via user options' do
79
+ let(:options) { super().merge(service_version: 'v3.0.1') }
80
+
81
+ it 'sets the service version' do
82
+ expect(subject.service_version).to eq('v3.0.1')
83
+ end
84
+ end
45
85
  end
46
86
 
47
- describe 'logger' do
48
- let(:logdev) { subject.logger.instance_variable_get(:@logdev) }
87
+ describe 'format' do
88
+ context 'when initialized via user options' do
89
+ let(:options) { super().merge(format: :gelf) }
49
90
 
50
- context 'when device is nil' do
91
+ it 'sets the format' do
92
+ expect(subject.format).to eq(:gelf)
93
+ end
94
+ end
95
+
96
+ context 'when initialized via ENV' do
51
97
  before do
52
- subject.device = nil
53
- allow(STDERR).to receive(:write)
98
+ allow(ENV).to receive(:[]).with('LOGA_FORMAT').and_return('gelf')
54
99
  end
55
- let(:error_message) { /Loga could not be initialized/ }
56
- it 'uses STDERR' do
57
- subject.initialize!
58
- expect(logdev.dev).to eq(STDERR)
100
+
101
+ it 'sets the format' do
102
+ expect(subject.format).to eq(:gelf)
59
103
  end
60
- it 'logs an error to STDERR' do
61
- expect(STDERR).to receive(:write).with(error_message)
62
- subject.initialize!
104
+ end
105
+
106
+ context 'when initialized via framework options' do
107
+ subject { described_class.new(options, framework_options) }
108
+ let(:framework_options) { { format: :gelf } }
109
+
110
+ it 'sets the format' do
111
+ expect(subject.format).to eq(:gelf)
63
112
  end
64
113
  end
65
114
 
115
+ context 'when initialized with user options and ENV' do
116
+ let(:options) { super().merge(format: :gelf) }
117
+
118
+ before do
119
+ allow(ENV).to receive(:[]).with('LOGA_FORMAT').and_return('simple')
120
+ end
121
+
122
+ it 'prefers user option' do
123
+ expect(subject.format).to eq(:gelf)
124
+ end
125
+ end
126
+
127
+ context 'when initialized with ENV and framework options' do
128
+ subject { described_class.new(options, framework_options) }
129
+ let(:framework_options) { { format: :gelf } }
130
+
131
+ before do
132
+ allow(ENV).to receive(:[]).with('LOGA_FORMAT').and_return('simple')
133
+ end
134
+
135
+ it 'prefers env' do
136
+ expect(subject.format).to eq(:simple)
137
+ end
138
+ end
139
+ end
140
+
141
+ describe 'formatter' do
142
+ context 'when format is :gelf' do
143
+ let(:options) do
144
+ super().merge(
145
+ format: :gelf,
146
+ service_name: ' hello_world_app ',
147
+ service_version_strategies: ['1.0'],
148
+ )
149
+ end
150
+ let(:formatter) { subject.logger.formatter }
151
+
152
+ it 'uses the GELF formatter' do
153
+ expect(subject.logger.formatter).to be_a(Loga::Formatter)
154
+ end
155
+
156
+ it 'strips the service name' do
157
+ expect(formatter.instance_variable_get(:@service_name)).to eq('hello_world_app')
158
+ end
159
+ end
160
+
161
+ context 'when format is :simple' do
162
+ let(:options) { super().merge(format: :simple) }
163
+
164
+ it 'uses the SimpleFormatter' do
165
+ expect(subject.logger.formatter).to be_a(ActiveSupport::Logger::SimpleFormatter)
166
+ end
167
+ end
168
+
169
+ context 'when the ActiveSupport::VERSION is unsupported' do
170
+ it 'raises an error' do
171
+ stub_const('ActiveSupport::VERSION::MAJOR', 1)
172
+ expect { described_class.new(options) }
173
+ .to raise_error(Loga::ConfigurationError, 'ActiveSupport 1 is unsupported')
174
+ end
175
+ end
176
+ end
177
+
178
+ describe 'logger' do
179
+ let(:logdev) { subject.logger.instance_variable_get(:@logdev) }
180
+
66
181
  {
67
182
  debug: 0,
68
183
  info: 1,
@@ -72,44 +187,35 @@ describe Loga::Configuration do
72
187
  unknown: 5,
73
188
  }.each do |sym, level|
74
189
  context "when log level is #{sym}" do
75
- before { subject.level = sym }
190
+ let(:options) { super().merge(level: sym) }
191
+
76
192
  it "uses log level #{sym}" do
77
- subject.initialize!
78
193
  expect(subject.logger.level).to eq(level)
79
194
  end
80
195
  end
81
196
  end
82
197
 
83
198
  context 'when sync is false' do
84
- before { subject.sync = false }
199
+ let(:options) { super().merge(sync: false) }
200
+
85
201
  it 'uses warn log level' do
86
- subject.initialize!
87
202
  expect(logdev.dev.sync).to eq(false)
88
203
  end
89
204
  end
90
205
  end
91
- end
92
206
 
93
- describe '#logger' do
94
- context 'when initialized' do
95
- before { subject.initialize! }
96
- it 'returns a logger' do
97
- expect(subject.logger).to be_a(Logger)
98
- end
207
+ describe '#structured?' do
208
+ context 'when format is :simple' do
209
+ let(:options) { super().merge(format: :simple) }
99
210
 
100
- it 'returns a tagged logger' do
101
- expect(subject.logger).to respond_to(:tagged)
211
+ specify { expect(subject.structured?).to eql(false) }
102
212
  end
103
- end
104
213
 
105
- context 'when not initialized' do
106
- specify { expect(subject.logger).to be_nil }
107
- end
108
- end
214
+ context 'when format is :gelf' do
215
+ let(:options) { super().merge(format: :gelf) }
109
216
 
110
- describe '#configure' do
111
- it 'yields self' do
112
- expect { |b| subject.configure(&b) }.to yield_with_args(subject)
217
+ specify { expect(subject.structured?).to eql(true) }
218
+ end
113
219
  end
114
220
  end
115
221
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe Loga::Event do
3
+ RSpec.describe Loga::Event, timecop: true do
4
4
  describe 'initialize' do
5
5
  context 'no message is passed' do
6
6
  it 'sets message to an empty string' do
@@ -17,4 +17,34 @@ RSpec.describe Loga::Event do
17
17
  end
18
18
  end
19
19
  end
20
+
21
+ describe '#to_s' do
22
+ let(:opts) { { message: 'Hello World', timestamp: Time.now } }
23
+ subject { described_class.new(opts) }
24
+
25
+ context 'when exception' do
26
+ let(:exception) do
27
+ instance_double(StandardError, to_s: 'Some Message', backtrace: ['file'])
28
+ end
29
+ let(:opts) { super().merge(exception: exception) }
30
+ it 'outputs the message with exception' do
31
+ expect(subject.to_s)
32
+ .to eql("#{time_anchor.iso8601(3)} Hello World\nSome Message\nfile")
33
+ end
34
+ end
35
+
36
+ context 'when no exception' do
37
+ it 'outputs the message' do
38
+ expect(subject.to_s).to eql("#{time_anchor.iso8601(3)} Hello World")
39
+ end
40
+ end
41
+ end
42
+
43
+ describe '#inspect' do
44
+ subject { described_class.new message: 'Hey Siri', timestamp: Time.now }
45
+
46
+ it 'aliases to to_s' do
47
+ expect(subject.to_s).to eql(subject.inspect)
48
+ end
49
+ end
20
50
  end
@@ -5,14 +5,22 @@ describe Loga::Rack::Logger do
5
5
  let(:env) { Rack::MockRequest.env_for('/about_us?limit=1', options) }
6
6
  let(:options) { {} }
7
7
  let(:app) { ->(_env) { [response_status, {}, ''] } }
8
- let(:logger) { double(:logger) }
8
+ let(:logger) { instance_double(Logger, info: nil, error: nil) }
9
+ let(:tags) { [] }
10
+
11
+ let(:configuration) do
12
+ instance_double(
13
+ Loga::Configuration,
14
+ filter_exceptions: %w(ActionController::RoutingError),
15
+ filter_parameters: [],
16
+ logger: logger,
17
+ tags: tags,
18
+ )
19
+ end
9
20
 
10
- subject { described_class.new(app, logger) }
21
+ subject { described_class.new(app) }
11
22
 
12
- before do
13
- allow(logger).to receive(:info)
14
- allow(logger).to receive(:error)
15
- end
23
+ before { Loga.instance_variable_set(:@configuration, configuration) }
16
24
 
17
25
  shared_examples 'logs the event' do |details|
18
26
  let(:level) { details[:level] }
@@ -103,11 +111,15 @@ describe Loga::Rack::Logger do
103
111
  end
104
112
  end
105
113
 
106
- it 'yields the app with tags' do
107
- expect(logger).to receive(:tagged).with(:tag) do |&block|
108
- expect(block.call).to eq(:response)
114
+ context 'when tags are present' do
115
+ let(:tags) { [:foo] }
116
+
117
+ it 'yields the app with tags' do
118
+ expect(logger).to receive(:tagged).with(:tag) do |&block|
119
+ expect(block.call).to eq(:response)
120
+ end
121
+ subject.call(env)
109
122
  end
110
- subject.call(env)
111
123
  end
112
124
  end
113
125
  end
@@ -6,13 +6,23 @@ describe Loga::Rack::Request do
6
6
  let(:env) { Rack::MockRequest.env_for(full_path, options) }
7
7
 
8
8
  let(:action_controller_class) do
9
- ApplicationController = Class.new do
9
+ Class.new do
10
+ def self.name
11
+ 'ApplicationController'
12
+ end
13
+
10
14
  def action_name
11
15
  'index'
12
16
  end
13
17
  end
14
18
  end
15
19
 
20
+ let(:config) { instance_double Loga::Configuration, filter_parameters: [:password] }
21
+
22
+ before do
23
+ allow(Loga).to receive(:configuration).and_return(config)
24
+ end
25
+
16
26
  subject { described_class.new(env) }
17
27
 
18
28
  describe '#uuid' do
@@ -34,35 +44,24 @@ describe Loga::Rack::Request do
34
44
  end
35
45
  end
36
46
 
37
- describe '#action_controller_instance' do
38
- let(:action_controller_instance) { action_controller_class.new }
39
-
40
- context 'when ACTION_CONTROLLER_INSTANCE is present' do
47
+ describe '#controller_action_name' do
48
+ context 'when available' do
41
49
  let(:options) do
42
- { 'action_controller.instance' => action_controller_instance }
50
+ { 'action_controller.instance' => action_controller_class.new }
43
51
  end
44
- it 'returns the instance' do
45
- expect(subject.action_controller_instance).to eq(action_controller_instance)
52
+
53
+ it 'returns the namespaced controller name with the action_name' do
54
+ expect(subject.controller_action_name).to eq('ApplicationController#index')
46
55
  end
47
56
  end
48
57
 
49
- context 'when ACTION_DISPATCH_REQUEST_ID blank' do
58
+ context 'when missing' do
50
59
  it 'returns nil' do
51
- expect(subject.action_controller_instance).to be_nil
60
+ expect(subject.controller_action_name).to be_nil
52
61
  end
53
62
  end
54
63
  end
55
64
 
56
- describe '#action_controller' do
57
- let(:options) do
58
- { 'action_controller.instance' => action_controller_class.new }
59
- end
60
-
61
- it 'returns the controller with the action_name' do
62
- expect(subject.action_controller).to eq('ApplicationController#index')
63
- end
64
- end
65
-
66
65
  describe '#request_id' do
67
66
  let(:action_dispatch_request_id) { 'ABCD' }
68
67
  let(:options) do
@@ -84,8 +83,6 @@ describe Loga::Rack::Request do
84
83
  end
85
84
 
86
85
  describe '#filtered_full_path' do
87
- let(:config) { double :config, filter_parameters: [:password] }
88
-
89
86
  let(:path) { '/hello' }
90
87
  let(:query) { { 'password' => 123, 'color' => 'red' } }
91
88
  let(:query_string) { Rack::Utils.build_query(query) }
@@ -93,10 +90,6 @@ describe Loga::Rack::Request do
93
90
 
94
91
  let(:options) { { 'loga.request.original_path' => path } }
95
92
 
96
- before do
97
- allow(Loga).to receive(:configuration).and_return(config)
98
- end
99
-
100
93
  context 'request with sensitive parameters' do
101
94
  it 'returns the path with sensitive parameters filtered' do
102
95
  expect(subject.filtered_full_path).to eq('/hello?password=[FILTERED]&color=red')