loga 1.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.
- checksums.yaml +7 -0
- data/.gitignore +25 -0
- data/.rspec +2 -0
- data/.rubocop.yml +19 -0
- data/.rubocop_todo.yml +33 -0
- data/Appraisals +14 -0
- data/Gemfile +8 -0
- data/README.md +147 -0
- data/Rakefile +9 -0
- data/circle.yml +23 -0
- data/gemfiles/rails32.gemfile +11 -0
- data/gemfiles/rails40.gemfile +11 -0
- data/gemfiles/sinatra14.gemfile +11 -0
- data/gemfiles/unit.gemfile +9 -0
- data/lib/loga.rb +33 -0
- data/lib/loga/configuration.rb +96 -0
- data/lib/loga/event.rb +21 -0
- data/lib/loga/ext/rails/rack/logger3.rb +21 -0
- data/lib/loga/ext/rails/rack/logger4.rb +13 -0
- data/lib/loga/formatter.rb +104 -0
- data/lib/loga/parameter_filter.rb +65 -0
- data/lib/loga/rack/logger.rb +102 -0
- data/lib/loga/rack/request.rb +77 -0
- data/lib/loga/rack/request_id.rb +44 -0
- data/lib/loga/railtie.rb +139 -0
- data/lib/loga/tagged_logging.rb +76 -0
- data/lib/loga/utilities.rb +7 -0
- data/lib/loga/version.rb +3 -0
- data/loga.gemspec +31 -0
- data/spec/fixtures/README.md +8 -0
- data/spec/fixtures/rails32/Rakefile +7 -0
- data/spec/fixtures/rails32/app/controllers/application_controller.rb +28 -0
- data/spec/fixtures/rails32/app/helpers/application_helper.rb +2 -0
- data/spec/fixtures/rails32/app/views/layouts/application.html.erb +14 -0
- data/spec/fixtures/rails32/app/views/user.html.erb +1 -0
- data/spec/fixtures/rails32/config.ru +4 -0
- data/spec/fixtures/rails32/config/application.rb +71 -0
- data/spec/fixtures/rails32/config/boot.rb +6 -0
- data/spec/fixtures/rails32/config/environment.rb +5 -0
- data/spec/fixtures/rails32/config/environments/development.rb +26 -0
- data/spec/fixtures/rails32/config/environments/production.rb +50 -0
- data/spec/fixtures/rails32/config/environments/test.rb +35 -0
- data/spec/fixtures/rails32/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/fixtures/rails32/config/initializers/inflections.rb +15 -0
- data/spec/fixtures/rails32/config/initializers/mime_types.rb +5 -0
- data/spec/fixtures/rails32/config/initializers/secret_token.rb +7 -0
- data/spec/fixtures/rails32/config/initializers/session_store.rb +8 -0
- data/spec/fixtures/rails32/config/initializers/wrap_parameters.rb +10 -0
- data/spec/fixtures/rails32/config/locales/en.yml +5 -0
- data/spec/fixtures/rails32/config/routes.rb +64 -0
- data/spec/fixtures/rails32/public/404.html +26 -0
- data/spec/fixtures/rails32/public/422.html +26 -0
- data/spec/fixtures/rails32/public/500.html +25 -0
- data/spec/fixtures/rails32/public/favicon.ico +0 -0
- data/spec/fixtures/rails32/public/index.html +241 -0
- data/spec/fixtures/rails32/public/robots.txt +5 -0
- data/spec/fixtures/rails32/script/rails +6 -0
- data/spec/fixtures/rails40/Rakefile +6 -0
- data/spec/fixtures/rails40/app/controllers/application_controller.rb +30 -0
- data/spec/fixtures/rails40/app/helpers/application_helper.rb +2 -0
- data/spec/fixtures/rails40/app/views/layouts/application.html.erb +14 -0
- data/spec/fixtures/rails40/app/views/user.html.erb +1 -0
- data/spec/fixtures/rails40/bin/bundle +3 -0
- data/spec/fixtures/rails40/bin/rails +4 -0
- data/spec/fixtures/rails40/bin/rake +4 -0
- data/spec/fixtures/rails40/config.ru +4 -0
- data/spec/fixtures/rails40/config/application.rb +37 -0
- data/spec/fixtures/rails40/config/boot.rb +4 -0
- data/spec/fixtures/rails40/config/environment.rb +5 -0
- data/spec/fixtures/rails40/config/environments/development.rb +24 -0
- data/spec/fixtures/rails40/config/environments/production.rb +65 -0
- data/spec/fixtures/rails40/config/environments/test.rb +39 -0
- data/spec/fixtures/rails40/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/fixtures/rails40/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/fixtures/rails40/config/initializers/inflections.rb +16 -0
- data/spec/fixtures/rails40/config/initializers/mime_types.rb +5 -0
- data/spec/fixtures/rails40/config/initializers/secret_token.rb +12 -0
- data/spec/fixtures/rails40/config/initializers/session_store.rb +3 -0
- data/spec/fixtures/rails40/config/initializers/wrap_parameters.rb +9 -0
- data/spec/fixtures/rails40/config/locales/en.yml +23 -0
- data/spec/fixtures/rails40/config/routes.rb +62 -0
- data/spec/fixtures/rails40/public/404.html +58 -0
- data/spec/fixtures/rails40/public/422.html +58 -0
- data/spec/fixtures/rails40/public/500.html +57 -0
- data/spec/fixtures/rails40/public/favicon.ico +0 -0
- data/spec/fixtures/rails40/public/robots.txt +5 -0
- data/spec/integration/rails/railtie_spec.rb +64 -0
- data/spec/integration/rails/request_spec.rb +42 -0
- data/spec/integration/sinatra_spec.rb +54 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/helpers.rb +16 -0
- data/spec/support/request_spec.rb +183 -0
- data/spec/support/timecop_shared.rb +7 -0
- data/spec/unit/loga/configuration_spec.rb +123 -0
- data/spec/unit/loga/event_spec.rb +20 -0
- data/spec/unit/loga/formatter_spec.rb +186 -0
- data/spec/unit/loga/parameter_filter_spec.rb +76 -0
- data/spec/unit/loga/rack/logger_spec.rb +114 -0
- data/spec/unit/loga/rack/request_spec.rb +70 -0
- data/spec/unit/loga/utilities_spec.rb +16 -0
- data/spec/unit/loga_spec.rb +41 -0
- metadata +357 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Loga::Configuration do
|
|
4
|
+
subject do
|
|
5
|
+
described_class.new.tap { |config| config.device = STDOUT }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
describe 'initialize' do
|
|
9
|
+
subject { described_class.new }
|
|
10
|
+
context 'defaults' do
|
|
11
|
+
specify { expect(subject.host).to eq(hostname_anchor) }
|
|
12
|
+
specify { expect(subject.level).to eq(:info) }
|
|
13
|
+
specify { expect(subject.device).to eq(nil) }
|
|
14
|
+
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) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
context 'when hostname cannot be resolved' do
|
|
21
|
+
before do
|
|
22
|
+
allow(Socket).to receive(:gethostname).and_raise(Exception)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'uses a default hostname' do
|
|
26
|
+
expect(subject.host).to eq('unknown.host')
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
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"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
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!
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context 'when service_version is :git' do
|
|
48
|
+
before { subject.service_version = :git }
|
|
49
|
+
it 'computes the service_version with git' do
|
|
50
|
+
subject.initialize!
|
|
51
|
+
expect(subject.service_version).to match(/\h+/)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe 'logger' do
|
|
56
|
+
let(:logdev) { subject.logger.instance_variable_get(:@logdev) }
|
|
57
|
+
|
|
58
|
+
context 'when device is nil' do
|
|
59
|
+
before do
|
|
60
|
+
subject.device = nil
|
|
61
|
+
allow(STDERR).to receive(:write)
|
|
62
|
+
end
|
|
63
|
+
let(:error_message) { /Loga could not be initialized/ }
|
|
64
|
+
it 'uses STDERR' do
|
|
65
|
+
subject.initialize!
|
|
66
|
+
expect(logdev.dev).to eq(STDERR)
|
|
67
|
+
end
|
|
68
|
+
it 'logs an error to STDERR' do
|
|
69
|
+
expect(STDERR).to receive(:write).with(error_message)
|
|
70
|
+
subject.initialize!
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
debug: 0,
|
|
76
|
+
info: 1,
|
|
77
|
+
warn: 2,
|
|
78
|
+
error: 3,
|
|
79
|
+
fatal: 4,
|
|
80
|
+
unknown: 5,
|
|
81
|
+
}.each do |sym, level|
|
|
82
|
+
context "when log level is #{sym}" do
|
|
83
|
+
before { subject.level = sym }
|
|
84
|
+
it "uses log level #{sym}" do
|
|
85
|
+
subject.initialize!
|
|
86
|
+
expect(subject.logger.level).to eq(level)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
context 'when sync is false' do
|
|
92
|
+
before { subject.sync = false }
|
|
93
|
+
it 'uses warn log level' do
|
|
94
|
+
subject.initialize!
|
|
95
|
+
expect(logdev.dev.sync).to eq(false)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe '#logger' do
|
|
102
|
+
context 'when initialized' do
|
|
103
|
+
before { subject.initialize! }
|
|
104
|
+
it 'returns a logger' do
|
|
105
|
+
expect(subject.logger).to be_a(Logger)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'returns a tagged logger' do
|
|
109
|
+
expect(subject.logger).to respond_to(:tagged)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context 'when not initialized' do
|
|
114
|
+
specify { expect(subject.logger).to be_nil }
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
describe '#configure' do
|
|
119
|
+
it 'yields self' do
|
|
120
|
+
expect { |b| subject.configure(&b) }.to yield_with_args(subject)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Loga::Event do
|
|
4
|
+
describe 'initialize' do
|
|
5
|
+
context 'no message is passed' do
|
|
6
|
+
it 'sets message to an empty string' do
|
|
7
|
+
expect(subject.message).to eq ''
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
context 'message is passed' do
|
|
12
|
+
let(:message) { "stuff \xC2".force_encoding 'ASCII-8BIT' }
|
|
13
|
+
let(:subject) { described_class.new message: message }
|
|
14
|
+
|
|
15
|
+
it 'sanitizes the input to be UTF-8 convertable' do
|
|
16
|
+
expect(subject.message.to_json).to eq '"stuff ?"'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Loga::Formatter do
|
|
4
|
+
let(:service_name) { 'loga' }
|
|
5
|
+
let(:service_version) { '725e032a' }
|
|
6
|
+
let(:host) { 'www.example.com' }
|
|
7
|
+
let(:params) do
|
|
8
|
+
{
|
|
9
|
+
service_name: service_name,
|
|
10
|
+
service_version: service_version,
|
|
11
|
+
host: host,
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
subject { described_class.new(params) }
|
|
16
|
+
|
|
17
|
+
shared_examples 'valid GELF message' do
|
|
18
|
+
it 'includes the required fields' do
|
|
19
|
+
expect(json).to include('version' => '1.1',
|
|
20
|
+
'host' => host,
|
|
21
|
+
'short_message' => be_a(String),
|
|
22
|
+
'timestamp' => be_a(Float),
|
|
23
|
+
'level' => 6,
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'includes Loga additional fields' do
|
|
28
|
+
expect(json).to include('_service.name' => service_name,
|
|
29
|
+
'_service.version' => service_version,
|
|
30
|
+
'_tags' => [],
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'outputs the timestamp in seconds since UNIX epoch' do
|
|
35
|
+
expect(json).to include('timestamp' => time_anchor_unix)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe '#call(severity, time, _progname, message)' do
|
|
40
|
+
subject { super().call(severity, time_anchor, nil, message) }
|
|
41
|
+
|
|
42
|
+
let(:severity) { 'INFO' }
|
|
43
|
+
let(:message) { 'Tree house magic' }
|
|
44
|
+
let(:json) { JSON.parse(subject) }
|
|
45
|
+
|
|
46
|
+
context 'when the message parameter is a String' do
|
|
47
|
+
it 'the short_message is that String' do
|
|
48
|
+
expect(json['short_message']).to eq(message)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
include_examples 'valid GELF message'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context 'when the message parameter is a nil' do
|
|
55
|
+
let(:message) { nil }
|
|
56
|
+
it 'the short_message is empty' do
|
|
57
|
+
expect(json['short_message']).to eq('')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
include_examples 'valid GELF message'
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
context 'when message parameter is a Hash' do
|
|
64
|
+
let(:message) { { message: 'Wooden house' } }
|
|
65
|
+
|
|
66
|
+
it 'the short_message is a String reprentation of that Hash' do
|
|
67
|
+
expect(json['short_message']).to eq('{:message=>"Wooden house"}')
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
include_examples 'valid GELF message'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context 'when message parameter is an Object' do
|
|
74
|
+
let(:message) { Object.new }
|
|
75
|
+
|
|
76
|
+
it 'the short_message is a String reprentation of that Object' do
|
|
77
|
+
expect(json['short_message']).to match(/#<Object:\dx\h+>/)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
include_examples 'valid GELF message'
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context 'when the message parameter is a Loga::Event' do
|
|
84
|
+
let(:options) { { message: 'Wooden house' } }
|
|
85
|
+
let(:message) { Loga::Event.new(options) }
|
|
86
|
+
|
|
87
|
+
include_examples 'valid GELF message'
|
|
88
|
+
|
|
89
|
+
it 'the short_message is the Event message' do
|
|
90
|
+
expect(json['short_message']).to eq(message.message)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
context 'when the Event has a timestamp' do
|
|
94
|
+
let(:time) { Time.new(2010, 12, 15, 9, 30, 5.323, '+02:00') }
|
|
95
|
+
let(:time_in_unix) { BigDecimal.new('1292398205.323') }
|
|
96
|
+
let(:options) { { timestamp: time } }
|
|
97
|
+
|
|
98
|
+
it 'uses the Event timestamp' do
|
|
99
|
+
expect(json['timestamp']).to eq(time_in_unix)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
context 'when the Event has a type' do
|
|
104
|
+
let(:options) { { type: 'request' } }
|
|
105
|
+
|
|
106
|
+
specify { expect(json['_type']).to eq(message.type) }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context 'when the Event no type' do
|
|
110
|
+
specify { expect(json).to_not include('_type') }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context 'when the Event has an exception' do
|
|
114
|
+
let(:backtrace) { %w(a b) }
|
|
115
|
+
let(:exception) do
|
|
116
|
+
StandardError.new('Foo Error').tap { |e| e.set_backtrace backtrace }
|
|
117
|
+
end
|
|
118
|
+
let(:options) { { exception: exception } }
|
|
119
|
+
|
|
120
|
+
specify { expect(json['_exception.klass']).to eq('StandardError') }
|
|
121
|
+
specify { expect(json['_exception.message']).to eq('Foo Error') }
|
|
122
|
+
specify { expect(json['_exception.backtrace']).to eq("a\nb") }
|
|
123
|
+
|
|
124
|
+
context 'when the backtrace is larger than 10 lines' do
|
|
125
|
+
let(:backtrace) { ('a'..'z').to_a }
|
|
126
|
+
it 'truncates the backtrace' do
|
|
127
|
+
expect(json['_exception.backtrace']).to eq("a\nb\nc\nd\ne\nf\ng\nh\ni\nj")
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
context 'when the Event has no exception' do
|
|
133
|
+
specify { expect(json).to_not include(/_exception.+/) }
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
context 'when the Event has data' do
|
|
137
|
+
let(:options) do
|
|
138
|
+
{
|
|
139
|
+
data: {
|
|
140
|
+
user_id: 1,
|
|
141
|
+
user: {
|
|
142
|
+
email: 'hello@world.com',
|
|
143
|
+
address: {
|
|
144
|
+
postcode: 'ABCD',
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
specify { expect(json['_user_id']).to eq(1) }
|
|
152
|
+
specify { expect(json['_user.email']).to eq('hello@world.com') }
|
|
153
|
+
specify { expect(json['_user.address']).to eq('postcode' => 'ABCD') }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
context 'when the Event data contains fiels identical to the formatter fields' do
|
|
157
|
+
let(:options) do
|
|
158
|
+
{
|
|
159
|
+
data: {
|
|
160
|
+
service: { name: 'Malicious Tags' },
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
include_examples 'valid GELF message'
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
{
|
|
170
|
+
'DEBUG' => 7,
|
|
171
|
+
'INFO' => 6,
|
|
172
|
+
'WARN' => 4,
|
|
173
|
+
'ERROR' => 3,
|
|
174
|
+
'FATAL' => 2,
|
|
175
|
+
'UNKNOWN' => 1,
|
|
176
|
+
}.each do |ruby_severity, syslog_level|
|
|
177
|
+
context "with severity #{ruby_severity}" do
|
|
178
|
+
let(:severity) { ruby_severity }
|
|
179
|
+
|
|
180
|
+
it "maps to level #{syslog_level}" do
|
|
181
|
+
expect(json['level']).to eq(syslog_level)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Loga::ParameterFilter do
|
|
4
|
+
let(:filters) { [:password, /token/] }
|
|
5
|
+
|
|
6
|
+
shared_examples 'compiled filter' do
|
|
7
|
+
let(:compiled_filters) { described_class::CompiledFilter }
|
|
8
|
+
|
|
9
|
+
before do
|
|
10
|
+
allow(compiled_filters).to receive(:compile).and_call_original
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'compiles filters only once' do
|
|
14
|
+
expect(compiled_filters).to receive(:compile).once
|
|
15
|
+
2.times { subject.filter(params) }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe '#filter(params)' do
|
|
20
|
+
subject { described_class.new(filters) }
|
|
21
|
+
|
|
22
|
+
let(:params) do
|
|
23
|
+
{
|
|
24
|
+
password: 'password123',
|
|
25
|
+
email: 'hello@world.com',
|
|
26
|
+
token: 'ABC',
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
let(:result) do
|
|
31
|
+
{
|
|
32
|
+
password: '[FILTERED]',
|
|
33
|
+
email: 'hello@world.com',
|
|
34
|
+
token: '[FILTERED]',
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context 'when no filters are applied' do
|
|
39
|
+
let(:filters) { [] }
|
|
40
|
+
|
|
41
|
+
it 'returns params' do
|
|
42
|
+
expect(subject.filter(params)).to match(params)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
include_examples 'compiled filter'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context 'when params is shallow' do
|
|
49
|
+
it 'returns filtered params' do
|
|
50
|
+
expect(subject.filter(params)).to match(result)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
include_examples 'compiled filter'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context 'when params has a nested Hash' do
|
|
57
|
+
let(:params) { { user: super() } }
|
|
58
|
+
|
|
59
|
+
it 'returns filtered params' do
|
|
60
|
+
expect(subject.filter(params)).to match(user: result)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
include_examples 'compiled filter'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
context 'when params has a nested Array' do
|
|
67
|
+
let(:params) { { users: [super(), super()] } }
|
|
68
|
+
|
|
69
|
+
it 'returns filtered params' do
|
|
70
|
+
expect(subject.filter(params)).to match(users: [result, result])
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
include_examples 'compiled filter'
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'rack/test'
|
|
3
|
+
|
|
4
|
+
describe Loga::Rack::Logger do
|
|
5
|
+
let(:env) { Rack::MockRequest.env_for('/about_us?limit=1', options) }
|
|
6
|
+
let(:options) { {} }
|
|
7
|
+
let(:app) { ->(_env) { [response_status, {}, ''] } }
|
|
8
|
+
let(:logger) { double(:logger) }
|
|
9
|
+
|
|
10
|
+
subject { described_class.new(app, logger) }
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
allow(logger).to receive(:info)
|
|
14
|
+
allow(logger).to receive(:error)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
shared_examples 'logs the event' do |details|
|
|
18
|
+
let(:level) { details[:level] }
|
|
19
|
+
|
|
20
|
+
before do
|
|
21
|
+
allow(subject).to receive(:started_at).and_return(:timestamp)
|
|
22
|
+
allow(subject).to receive(:duration_in_ms).with(any_args).and_return(5)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'instantiates a Loga::Event' do
|
|
26
|
+
expect(Loga::Event).to receive(:new).with(
|
|
27
|
+
data: {
|
|
28
|
+
request: {
|
|
29
|
+
'status' => response_status,
|
|
30
|
+
'method' => 'GET',
|
|
31
|
+
'path' => '/about_us',
|
|
32
|
+
'params' => { 'limit' => '1' },
|
|
33
|
+
'request_id' => nil,
|
|
34
|
+
'request_ip' => nil,
|
|
35
|
+
'user_agent' => nil,
|
|
36
|
+
'duration' => 5,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
exception: logged_exception,
|
|
40
|
+
message: %r{^GET \/about_us\?limit=1 #{response_status} in \d+ms$},
|
|
41
|
+
timestamp: :timestamp,
|
|
42
|
+
type: 'request',
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
subject.call(env)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "logs the Loga::Event with severity #{details[:level]}" do
|
|
49
|
+
expect(logger).to receive(level).with(an_instance_of(Loga::Event))
|
|
50
|
+
subject.call(env)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe '#call(env)' do
|
|
55
|
+
let(:exception) { StandardError.new }
|
|
56
|
+
let(:logged_exception) { nil }
|
|
57
|
+
let(:response_status) { 200 }
|
|
58
|
+
|
|
59
|
+
context 'when an exception is raised' do
|
|
60
|
+
let(:app) { ->(_env) { fail exception } }
|
|
61
|
+
|
|
62
|
+
it 'does not rescue the exception' do
|
|
63
|
+
expect { subject.call(env) }.to raise_error(StandardError)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context 'when an exception wrapped by ActionDispatch' do
|
|
68
|
+
let(:response_status) { 500 }
|
|
69
|
+
let(:logged_exception) { exception }
|
|
70
|
+
let(:options) { { 'action_dispatch.exception' => exception } }
|
|
71
|
+
|
|
72
|
+
include_examples 'logs the event', level: :error
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
context 'when an exception wrapped by Sinatra' do
|
|
76
|
+
let(:response_status) { 500 }
|
|
77
|
+
let(:logged_exception) { exception }
|
|
78
|
+
let(:options) { { 'sinatra.error' => exception } }
|
|
79
|
+
|
|
80
|
+
include_examples 'logs the event', level: :error
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context 'when the exception is ActionController::RoutingError' do
|
|
84
|
+
let(:response_status) { 404 }
|
|
85
|
+
let(:exception) { double(class: 'ActionController::RoutingError') }
|
|
86
|
+
let(:options) { { 'action_dispatch.exception' => exception } }
|
|
87
|
+
|
|
88
|
+
include_examples 'logs the event', level: :info
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
context 'when no exception is raised' do
|
|
92
|
+
include_examples 'logs the event', level: :info
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
context 'when the logger is tagged' do
|
|
96
|
+
let(:logger) { double(:logger, tagged: true) }
|
|
97
|
+
|
|
98
|
+
before do
|
|
99
|
+
allow(subject).to receive(:call_app).with(any_args).and_return(:response)
|
|
100
|
+
allow(subject).to receive(:compute_tags).with(any_args).and_return(:tag)
|
|
101
|
+
allow(logger).to receive(:tagged).with('hello') do |&block|
|
|
102
|
+
block.call
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
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)
|
|
109
|
+
end
|
|
110
|
+
subject.call(env)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|