loga 2.2.0 → 2.3.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.
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