loga 2.2.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +1 -0
- data/.codeclimate.yml +3 -1
- data/.gitignore +2 -0
- data/.rubocop.yml +29 -2
- data/Appraisals +4 -0
- data/CHANGELOG.md +4 -0
- data/Guardfile +14 -0
- data/README.md +4 -0
- data/gemfiles/sidekiq51.gemfile +11 -0
- data/lib/loga.rb +4 -3
- data/lib/loga/ext/core/tempfile.rb +1 -1
- data/lib/loga/formatters/simple_formatter.rb +1 -3
- data/lib/loga/rack/request.rb +2 -2
- data/lib/loga/sidekiq.rb +16 -0
- data/lib/loga/sidekiq/job_logger.rb +62 -0
- data/lib/loga/utilities.rb +1 -1
- data/lib/loga/version.rb +1 -1
- data/loga.gemspec +4 -2
- data/spec/fixtures/rails32.rb +1 -0
- data/spec/integration/rails/railtie_spec.rb +9 -8
- data/spec/integration/rails/request_spec.rb +2 -2
- data/spec/integration/sidekiq_spec.rb +131 -0
- data/spec/integration/sinatra_spec.rb +17 -16
- data/spec/loga/sidekiq/job_logger_spec.rb +115 -0
- data/spec/loga/sidekiq_spec.rb +53 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/helpers.rb +8 -1
- data/spec/support/request_spec.rb +4 -4
- data/spec/support/timecop_shared.rb +1 -0
- data/spec/unit/loga/configuration_spec.rb +7 -4
- data/spec/unit/loga/event_spec.rb +2 -2
- data/spec/unit/loga/formatters/gelf_formatter_spec.rb +7 -5
- data/spec/unit/loga/formatters/simple_formatter_spec.rb +3 -0
- data/spec/unit/loga/log_subscribers/action_mailer_spec.rb +14 -13
- data/spec/unit/loga/parameter_filter_spec.rb +1 -1
- data/spec/unit/loga/rack/logger_spec.rb +10 -6
- data/spec/unit/loga/rack/request_spec.rb +4 -3
- data/spec/unit/loga/utilities_spec.rb +3 -3
- data/spec/unit/loga_spec.rb +6 -3
- metadata +44 -7
@@ -24,23 +24,10 @@ end
|
|
24
24
|
RSpec.describe 'Structured logging with Sinatra', :with_hostname, :timecop do
|
25
25
|
let(:io) { StringIO.new }
|
26
26
|
let(:format) {}
|
27
|
-
before do
|
28
|
-
Loga.reset
|
29
|
-
Loga.configure(
|
30
|
-
device: io,
|
31
|
-
filter_parameters: [:password],
|
32
|
-
format: format,
|
33
|
-
service_name: 'hello_world_app',
|
34
|
-
service_version: '1.0',
|
35
|
-
tags: [:uuid, 'TEST_TAG'],
|
36
|
-
)
|
37
|
-
end
|
38
|
-
|
39
27
|
let(:last_log_entry) do
|
40
28
|
io.rewind
|
41
29
|
JSON.parse(io.read)
|
42
30
|
end
|
43
|
-
|
44
31
|
let(:app) do
|
45
32
|
Rack::Builder.new do
|
46
33
|
use Loga::Rack::RequestId
|
@@ -49,13 +36,26 @@ RSpec.describe 'Structured logging with Sinatra', :with_hostname, :timecop do
|
|
49
36
|
end
|
50
37
|
end
|
51
38
|
|
39
|
+
before do
|
40
|
+
Loga.reset
|
41
|
+
Loga.configure(
|
42
|
+
device: io,
|
43
|
+
filter_parameters: [:password],
|
44
|
+
format: format,
|
45
|
+
service_name: 'hello_world_app',
|
46
|
+
service_version: '1.0',
|
47
|
+
tags: [:uuid, 'TEST_TAG'],
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
52
51
|
context 'when RACK_ENV is production', if: ENV['RACK_ENV'].eql?('production') do
|
53
52
|
let(:format) { :gelf }
|
53
|
+
|
54
54
|
include_examples 'request logger'
|
55
55
|
|
56
56
|
it 'does not include the controller name and action' do
|
57
57
|
get '/ok'
|
58
|
-
expect(last_log_entry).
|
58
|
+
expect(last_log_entry).not_to include('_request.controller')
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -85,7 +85,7 @@ RSpec.describe 'Structured logging with Sinatra', :with_hostname, :timecop do
|
|
85
85
|
allow(Process).to receive(:pid).and_return(999)
|
86
86
|
end
|
87
87
|
|
88
|
-
|
88
|
+
describe 'get request' do
|
89
89
|
it 'logs the request' do
|
90
90
|
get '/ok', { username: 'yoshi' }, 'HTTP_X_REQUEST_ID' => '700a6a01'
|
91
91
|
|
@@ -93,7 +93,7 @@ RSpec.describe 'Structured logging with Sinatra', :with_hostname, :timecop do
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
-
|
96
|
+
describe 'request with redirect' do
|
97
97
|
let(:data) do
|
98
98
|
super().merge(
|
99
99
|
'status' => 302,
|
@@ -101,6 +101,7 @@ RSpec.describe 'Structured logging with Sinatra', :with_hostname, :timecop do
|
|
101
101
|
'params' => {},
|
102
102
|
)
|
103
103
|
end
|
104
|
+
|
104
105
|
it 'specifies the original path' do
|
105
106
|
get '/new', {}, 'HTTP_X_REQUEST_ID' => '700a6a01'
|
106
107
|
expect(last_log_entry).to eql("I, #{time_pid_tags} GET /new 302 in 0ms type=request #{data_as_text}\n")
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Loga::Sidekiq::JobLogger do
|
4
|
+
subject(:job_logger) { described_class.new }
|
5
|
+
|
6
|
+
let(:target) { StringIO.new }
|
7
|
+
|
8
|
+
let(:json_line) do
|
9
|
+
target.rewind
|
10
|
+
JSON.parse(target.read.split("\n").last)
|
11
|
+
end
|
12
|
+
|
13
|
+
before do
|
14
|
+
Loga.reset
|
15
|
+
|
16
|
+
Loga.configure(
|
17
|
+
service_name: 'hello_world_app',
|
18
|
+
service_version: '1.0',
|
19
|
+
device: target,
|
20
|
+
format: :gelf,
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#call' do
|
25
|
+
context 'when the job passess successfully' do
|
26
|
+
let(:item_data) do
|
27
|
+
{
|
28
|
+
'class' => 'HardWorker',
|
29
|
+
'args' => ['asd'],
|
30
|
+
'retry' => true,
|
31
|
+
'queue' => 'default',
|
32
|
+
'jid' => '591f6f66ee0d218fb451dfb6',
|
33
|
+
'created_at' => 1_528_799_582.904939,
|
34
|
+
'enqueued_at' => 1_528_799_582.9049861,
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'has the the required attributes on call' do
|
39
|
+
job_logger.call(item_data, 'queue') do
|
40
|
+
# something
|
41
|
+
end
|
42
|
+
|
43
|
+
expected_body = {
|
44
|
+
'version' => '1.1',
|
45
|
+
'level' => 6,
|
46
|
+
'_type' => 'sidekiq',
|
47
|
+
'_created_at' => 1_528_799_582.904939,
|
48
|
+
'_enqueued_at' => 1_528_799_582.9049861,
|
49
|
+
'_jid' => '591f6f66ee0d218fb451dfb6',
|
50
|
+
'_retry' => true,
|
51
|
+
'_queue' => 'default',
|
52
|
+
'_service.name' => 'hello_world_app',
|
53
|
+
'_class' => 'HardWorker',
|
54
|
+
'_service.version' => '1.0',
|
55
|
+
'_tags' => '',
|
56
|
+
'_params' => ['asd'],
|
57
|
+
}
|
58
|
+
|
59
|
+
aggregate_failures do
|
60
|
+
expect(json_line).to include(expected_body)
|
61
|
+
expect(json_line['timestamp']).to be_a(Float)
|
62
|
+
expect(json_line['host']).to be_a(String)
|
63
|
+
expect(json_line['short_message']).to match(/HardWorker with jid:*/)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when the job fails' do
|
69
|
+
let(:item_data) do
|
70
|
+
{
|
71
|
+
'class' => 'HardWorker',
|
72
|
+
'args' => ['asd'],
|
73
|
+
'retry' => true,
|
74
|
+
'queue' => 'default',
|
75
|
+
'jid' => '591f6f66ee0d218fb451dfb6',
|
76
|
+
'created_at' => 1_528_799_582.904939,
|
77
|
+
'enqueued_at' => 1_528_799_582.9049861,
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'has the the required attributes on call' do
|
82
|
+
failed_job = lambda do
|
83
|
+
job_logger.call(item_data, 'queue') do
|
84
|
+
raise StandardError
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
expected_body = {
|
89
|
+
'version' => '1.1',
|
90
|
+
'level' => 4,
|
91
|
+
'_type' => 'sidekiq',
|
92
|
+
'_created_at' => 1_528_799_582.904939,
|
93
|
+
'_enqueued_at' => 1_528_799_582.9049861,
|
94
|
+
'_jid' => '591f6f66ee0d218fb451dfb6',
|
95
|
+
'_retry' => true,
|
96
|
+
'_queue' => 'default',
|
97
|
+
'_service.name' => 'hello_world_app',
|
98
|
+
'_class' => 'HardWorker',
|
99
|
+
'_service.version' => '1.0',
|
100
|
+
'_tags' => '',
|
101
|
+
'_params' => ['asd'],
|
102
|
+
'_exception' => 'StandardError',
|
103
|
+
}
|
104
|
+
|
105
|
+
aggregate_failures do
|
106
|
+
expect(&failed_job).to raise_error(StandardError)
|
107
|
+
expect(json_line).to include(expected_body)
|
108
|
+
expect(json_line['timestamp']).to be_a(Float)
|
109
|
+
expect(json_line['host']).to be_a(String)
|
110
|
+
expect(json_line['short_message']).to match(/HardWorker with jid:*/)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Loga::Sidekiq do
|
4
|
+
describe '.configure_logging' do
|
5
|
+
context 'when sidekiq version is 5.1' do
|
6
|
+
it 'gets invoked on Loga.configure' do
|
7
|
+
allow(described_class).to receive(:configure_logging)
|
8
|
+
|
9
|
+
Loga.reset
|
10
|
+
|
11
|
+
Loga.configure(
|
12
|
+
service_name: 'hello_world_app',
|
13
|
+
service_version: '1.0',
|
14
|
+
device: StringIO.new,
|
15
|
+
format: :gelf,
|
16
|
+
)
|
17
|
+
|
18
|
+
expect(described_class).to have_received(:configure_logging)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'assigns our custom sidekiq job logger' do
|
22
|
+
Loga.reset
|
23
|
+
|
24
|
+
Loga.configure(
|
25
|
+
service_name: 'hello_world_app',
|
26
|
+
service_version: '1.0',
|
27
|
+
device: StringIO.new,
|
28
|
+
format: :gelf,
|
29
|
+
)
|
30
|
+
|
31
|
+
expect(::Sidekiq.options[:job_logger]).to eq(Loga::Sidekiq::JobLogger)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
shared_examples 'a blank change' do
|
36
|
+
it 'does nothing' do
|
37
|
+
expect(described_class.configure_logging).to be_nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when sidekiq is not defined' do
|
42
|
+
before { hide_const('Sidekiq') }
|
43
|
+
|
44
|
+
it_behaves_like 'a blank change'
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'when sidekiq version is 4.2' do
|
48
|
+
before { stub_const('::Sidekiq::VERSION', '4.2') }
|
49
|
+
|
50
|
+
it_behaves_like 'a blank change'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -9,6 +9,9 @@ require 'simplecov'
|
|
9
9
|
|
10
10
|
SimpleCov.start do
|
11
11
|
command_name "ruby-#{RUBY_VERSION}-#{File.basename(ENV['BUNDLE_GEMFILE'], '.gemfile')}"
|
12
|
+
|
13
|
+
# Exclude specs from showing up in the code coverage report.
|
14
|
+
add_filter 'spec/'
|
12
15
|
end
|
13
16
|
|
14
17
|
case ENV['BUNDLE_GEMFILE']
|
@@ -26,6 +29,18 @@ when /sinatra/
|
|
26
29
|
when /unit/
|
27
30
|
rspec_pattern = 'unit/**/*_spec.rb'
|
28
31
|
require 'loga'
|
32
|
+
when /sidekiq/
|
33
|
+
sidekiq_specs = [
|
34
|
+
'integration/sidekiq_spec.rb',
|
35
|
+
'spec/loga/sidekiq/**/*_spec.rb',
|
36
|
+
'spec/loga/sidekiq_spec.rb',
|
37
|
+
]
|
38
|
+
|
39
|
+
rspec_pattern = sidekiq_specs.join(',')
|
40
|
+
|
41
|
+
require 'sidekiq'
|
42
|
+
require 'sidekiq/cli'
|
43
|
+
require 'loga'
|
29
44
|
else
|
30
45
|
raise 'BUNDLE_GEMFILE is unknown. Ensure the appraisal is present in Appraisals'
|
31
46
|
end
|
@@ -35,4 +50,10 @@ RSpec.configure do |config|
|
|
35
50
|
config.include Rack::Test::Methods
|
36
51
|
|
37
52
|
config.pattern = rspec_pattern
|
53
|
+
|
54
|
+
config.mock_with :rspec do |mocks|
|
55
|
+
mocks.allow_message_expectations_on_nil = false
|
56
|
+
mocks.transfer_nested_constants = true
|
57
|
+
mocks.verify_doubled_constant_names = true
|
58
|
+
end
|
38
59
|
end
|
data/spec/support/helpers.rb
CHANGED
@@ -7,6 +7,13 @@ module Helpers
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def time_anchor_unix
|
10
|
-
BigDecimal
|
10
|
+
BigDecimal('1450150205.123')
|
11
|
+
end
|
12
|
+
|
13
|
+
def stub_loga
|
14
|
+
loga = class_double(Loga).as_stubbed_const
|
15
|
+
logger = instance_double(Logger)
|
16
|
+
allow(loga).to receive(:logger).and_return(logger)
|
17
|
+
loga
|
11
18
|
end
|
12
19
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
RSpec.shared_examples 'request logger' do
|
2
|
-
|
2
|
+
describe 'get request' do
|
3
3
|
it 'logs the request' do
|
4
4
|
get '/ok',
|
5
5
|
{ username: 'yoshi' },
|
@@ -27,7 +27,7 @@ RSpec.shared_examples 'request logger' do
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
|
30
|
+
describe 'post request' do
|
31
31
|
let(:json_response) { JSON.parse(last_response.body) }
|
32
32
|
|
33
33
|
it 'logs the request' do
|
@@ -63,7 +63,7 @@ RSpec.shared_examples 'request logger' do
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
|
66
|
+
describe 'request with redirect' do
|
67
67
|
it 'specifies the original path' do
|
68
68
|
get '/new', {}, 'HTTP_USER_AGENT' => 'Chrome', 'HTTP_X_REQUEST_ID' => '471a34dc'
|
69
69
|
|
@@ -182,7 +182,7 @@ RSpec.shared_examples 'request logger' do
|
|
182
182
|
end
|
183
183
|
end
|
184
184
|
|
185
|
-
describe 'when the request uploads a binary file'
|
185
|
+
describe 'when the request uploads a binary file' do
|
186
186
|
it 'logs the request' do
|
187
187
|
post '/users?username=yoshi',
|
188
188
|
bob_file: Rack::Test::UploadedFile.new('spec/fixtures/random_bin')
|
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Loga::Configuration do
|
4
|
+
subject { described_class.new(options) }
|
5
|
+
|
4
6
|
let(:options) do
|
5
7
|
{ service_name: 'hello_world_app' }
|
6
8
|
end
|
7
9
|
|
8
|
-
subject { described_class.new(options) }
|
9
|
-
|
10
10
|
describe 'initialize' do
|
11
11
|
let(:framework_exceptions) do
|
12
12
|
%w[
|
@@ -75,6 +75,7 @@ describe Loga::Configuration do
|
|
75
75
|
expect(subject.service_version).to eq('unknown.sha')
|
76
76
|
end
|
77
77
|
end
|
78
|
+
|
78
79
|
context 'when initialized via user options' do
|
79
80
|
let(:options) { super().merge(service_version: 'v3.0.1') }
|
80
81
|
|
@@ -105,6 +106,7 @@ describe Loga::Configuration do
|
|
105
106
|
|
106
107
|
context 'when initialized via framework options' do
|
107
108
|
subject { described_class.new(options, framework_options) }
|
109
|
+
|
108
110
|
let(:framework_options) { { format: :gelf } }
|
109
111
|
|
110
112
|
it 'sets the format' do
|
@@ -126,6 +128,7 @@ describe Loga::Configuration do
|
|
126
128
|
|
127
129
|
context 'when initialized with ENV and framework options' do
|
128
130
|
subject { described_class.new(options, framework_options) }
|
131
|
+
|
129
132
|
let(:framework_options) { { format: :gelf } }
|
130
133
|
|
131
134
|
before do
|
@@ -200,13 +203,13 @@ describe Loga::Configuration do
|
|
200
203
|
context 'when format is :simple' do
|
201
204
|
let(:options) { super().merge(format: :simple) }
|
202
205
|
|
203
|
-
specify { expect(subject.structured?).to
|
206
|
+
specify { expect(subject.structured?).to be(false) }
|
204
207
|
end
|
205
208
|
|
206
209
|
context 'when format is :gelf' do
|
207
210
|
let(:options) { super().merge(format: :gelf) }
|
208
211
|
|
209
|
-
specify { expect(subject.structured?).to
|
212
|
+
specify { expect(subject.structured?).to be(true) }
|
210
213
|
end
|
211
214
|
end
|
212
215
|
end
|
@@ -2,13 +2,13 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.describe Loga::Event, timecop: true do
|
4
4
|
describe 'initialize' do
|
5
|
-
context 'no message is passed' do
|
5
|
+
context 'when no message is passed' do
|
6
6
|
it 'sets message to an empty string' do
|
7
7
|
expect(subject.message).to eq ''
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
context 'message is passed' do
|
11
|
+
context 'when message is passed' do
|
12
12
|
let(:message) { "stuff \xC2".force_encoding 'ASCII-8BIT' }
|
13
13
|
let(:subject) { described_class.new message: message }
|
14
14
|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Loga::Formatters::GELFFormatter do
|
4
|
+
subject { described_class.new(params) }
|
5
|
+
|
4
6
|
let(:service_name) { 'loga' }
|
5
7
|
let(:service_version) { '725e032a' }
|
6
8
|
let(:host) { 'www.example.com' }
|
@@ -12,8 +14,6 @@ describe Loga::Formatters::GELFFormatter do
|
|
12
14
|
}
|
13
15
|
end
|
14
16
|
|
15
|
-
subject { described_class.new(params) }
|
16
|
-
|
17
17
|
shared_examples 'valid GELF message' do
|
18
18
|
it 'includes the required fields' do
|
19
19
|
expect(json).to include('version' => '1.1',
|
@@ -51,6 +51,7 @@ describe Loga::Formatters::GELFFormatter do
|
|
51
51
|
|
52
52
|
context 'when the message parameter is a nil' do
|
53
53
|
let(:message) { nil }
|
54
|
+
|
54
55
|
it 'the short_message is empty' do
|
55
56
|
expect(json['short_message']).to eq('')
|
56
57
|
end
|
@@ -90,7 +91,7 @@ describe Loga::Formatters::GELFFormatter do
|
|
90
91
|
|
91
92
|
context 'when the Event has a timestamp' do
|
92
93
|
let(:time) { Time.new(2010, 12, 15, 9, 30, 5.323, '+02:00') }
|
93
|
-
let(:time_in_unix) { BigDecimal
|
94
|
+
let(:time_in_unix) { BigDecimal('1292398205.323') }
|
94
95
|
let(:options) { { timestamp: time } }
|
95
96
|
|
96
97
|
it 'uses the Event timestamp' do
|
@@ -105,7 +106,7 @@ describe Loga::Formatters::GELFFormatter do
|
|
105
106
|
end
|
106
107
|
|
107
108
|
context 'when the Event no type' do
|
108
|
-
specify { expect(json).
|
109
|
+
specify { expect(json).not_to include('_type') }
|
109
110
|
end
|
110
111
|
|
111
112
|
context 'when the Event has an exception' do
|
@@ -121,6 +122,7 @@ describe Loga::Formatters::GELFFormatter do
|
|
121
122
|
|
122
123
|
context 'when the backtrace is larger than 10 lines' do
|
123
124
|
let(:backtrace) { ('a'..'z').to_a }
|
125
|
+
|
124
126
|
it 'truncates the backtrace' do
|
125
127
|
expect(json['_exception.backtrace']).to eq("a\nb\nc\nd\ne\nf\ng\nh\ni\nj")
|
126
128
|
end
|
@@ -128,7 +130,7 @@ describe Loga::Formatters::GELFFormatter do
|
|
128
130
|
end
|
129
131
|
|
130
132
|
context 'when the Event has no exception' do
|
131
|
-
specify { expect(json).
|
133
|
+
specify { expect(json).not_to include(/_exception.+/) }
|
132
134
|
end
|
133
135
|
|
134
136
|
context 'when the Event has data' do
|