loga 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +1 -0
  3. data/.codeclimate.yml +3 -1
  4. data/.gitignore +2 -0
  5. data/.rubocop.yml +29 -2
  6. data/Appraisals +4 -0
  7. data/CHANGELOG.md +4 -0
  8. data/Guardfile +14 -0
  9. data/README.md +4 -0
  10. data/gemfiles/sidekiq51.gemfile +11 -0
  11. data/lib/loga.rb +4 -3
  12. data/lib/loga/ext/core/tempfile.rb +1 -1
  13. data/lib/loga/formatters/simple_formatter.rb +1 -3
  14. data/lib/loga/rack/request.rb +2 -2
  15. data/lib/loga/sidekiq.rb +16 -0
  16. data/lib/loga/sidekiq/job_logger.rb +62 -0
  17. data/lib/loga/utilities.rb +1 -1
  18. data/lib/loga/version.rb +1 -1
  19. data/loga.gemspec +4 -2
  20. data/spec/fixtures/rails32.rb +1 -0
  21. data/spec/integration/rails/railtie_spec.rb +9 -8
  22. data/spec/integration/rails/request_spec.rb +2 -2
  23. data/spec/integration/sidekiq_spec.rb +131 -0
  24. data/spec/integration/sinatra_spec.rb +17 -16
  25. data/spec/loga/sidekiq/job_logger_spec.rb +115 -0
  26. data/spec/loga/sidekiq_spec.rb +53 -0
  27. data/spec/spec_helper.rb +21 -0
  28. data/spec/support/helpers.rb +8 -1
  29. data/spec/support/request_spec.rb +4 -4
  30. data/spec/support/timecop_shared.rb +1 -0
  31. data/spec/unit/loga/configuration_spec.rb +7 -4
  32. data/spec/unit/loga/event_spec.rb +2 -2
  33. data/spec/unit/loga/formatters/gelf_formatter_spec.rb +7 -5
  34. data/spec/unit/loga/formatters/simple_formatter_spec.rb +3 -0
  35. data/spec/unit/loga/log_subscribers/action_mailer_spec.rb +14 -13
  36. data/spec/unit/loga/parameter_filter_spec.rb +1 -1
  37. data/spec/unit/loga/rack/logger_spec.rb +10 -6
  38. data/spec/unit/loga/rack/request_spec.rb +4 -3
  39. data/spec/unit/loga/utilities_spec.rb +3 -3
  40. data/spec/unit/loga_spec.rb +6 -3
  41. 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).to_not include('_request.controller')
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
- context 'get request' do
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
- context 'request with redirect' do
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
@@ -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
@@ -7,6 +7,13 @@ module Helpers
7
7
  end
8
8
 
9
9
  def time_anchor_unix
10
- BigDecimal.new('1450150205.123')
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
- context 'get request' do
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
- context 'post request' do
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
- context 'request with redirect' do
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', focus: true do
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')
@@ -3,5 +3,6 @@ require 'timecop'
3
3
  shared_context 'timecop', timecop: true do
4
4
  # Allows fixed timestamps
5
5
  before(:all) { Timecop.freeze(time_anchor) }
6
+
6
7
  after(:all) { Timecop.return }
7
8
  end
@@ -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 eql(false) }
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 eql(true) }
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.new('1292398205.323') }
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).to_not include('_type') }
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).to_not include(/_exception.+/) }
133
+ specify { expect(json).not_to include(/_exception.+/) }
132
134
  end
133
135
 
134
136
  context 'when the Event has data' do