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.
@@ -0,0 +1,19 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module Loga
4
+ class ServiceVersionStrategies
5
+ # Redirect stderror to /dev/null when git binary or git directory not available
6
+ SCM_GIT = -> { `git rev-parse --short HEAD 2>/dev/null` }
7
+ REVISION_FILE = -> { begin; File.read('REVISION'); rescue Errno::ENOENT; nil; end }
8
+ DEFAULT = -> { 'unknown.sha' }
9
+ STRATEGIES = [SCM_GIT, REVISION_FILE, DEFAULT].freeze
10
+
11
+ def self.call
12
+ new.call
13
+ end
14
+
15
+ def call
16
+ STRATEGIES.map(&:call).find(&:presence).strip
17
+ end
18
+ end
19
+ end
@@ -1,3 +1,3 @@
1
1
  module Loga
2
- VERSION = '1.4.0'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
  end
@@ -10,6 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ['engineering@fundingcircle.com']
11
11
  spec.summary = 'Facilitate log aggregation via unified logging'
12
12
  spec.description = 'Log aggregation through unified logging middleware, while respecting the original log format.'
13
+ spec.license = 'BSD-3-Clause'
13
14
  spec.homepage = 'https://github.com/FundingCircle/loga'
14
15
 
15
16
  spec.files = `git ls-files -z`.split("\x0")
@@ -25,7 +26,10 @@ Gem::Specification.new do |spec|
25
26
  spec.add_development_dependency 'pry'
26
27
  spec.add_development_dependency 'rack-test'
27
28
  spec.add_development_dependency 'rake'
28
- spec.add_development_dependency 'rspec', '~> 3.0.0'
29
+ spec.add_development_dependency 'rspec', '~> 3.5.0'
29
30
  spec.add_development_dependency 'rubocop', '~> 0.40.0'
30
31
  spec.add_development_dependency 'timecop'
32
+ spec.add_development_dependency 'guard', '~> 2.13'
33
+ spec.add_development_dependency 'guard-rubocop', '~> 1.2'
34
+ spec.add_development_dependency 'guard-rspec', '~> 4.7.3'
31
35
  end
@@ -61,11 +61,11 @@ module Rails32
61
61
  # parameters by using an attr_accessible or attr_protected declaration.
62
62
  # config.active_record.whitelist_attributes = true
63
63
  config.log_tags = [ :uuid, 'TEST_TAG' ]
64
- config.loga.configure do |loga|
65
- loga.service_name = 'hello_world_app'
66
- loga.service_version = '1.0'
67
- loga.host = 'bird.example.com'
68
- loga.device = STREAM
69
- end
64
+ config.loga = {
65
+ device: STREAM,
66
+ host: 'bird.example.com',
67
+ service_name: 'hello_world_app',
68
+ service_version: '1.0',
69
+ }
70
70
  end
71
71
  end
@@ -27,11 +27,11 @@ module Rails40
27
27
  # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
28
28
  # config.i18n.default_locale = :de
29
29
  config.log_tags = [ :uuid, 'TEST_TAG' ]
30
- config.loga.configure do |loga|
31
- loga.service_name = 'hello_world_app'
32
- loga.service_version = '1.0'
33
- loga.host = 'bird.example.com'
34
- loga.device = STREAM
35
- end
30
+ config.loga = {
31
+ device: STREAM,
32
+ host: 'bird.example.com',
33
+ service_name: 'hello_world_app',
34
+ service_version: '1.0',
35
+ }
36
36
  end
37
37
  end
@@ -24,11 +24,11 @@ module Rails50
24
24
  # Application configuration should go into files in config/initializers
25
25
  # -- all .rb files in that directory are automatically loaded.
26
26
  config.log_tags = [ :request_id, 'TEST_TAG' ]
27
- config.loga.configure do |loga|
28
- loga.service_name = 'hello_world_app'
29
- loga.service_version = '1.0'
30
- loga.host = 'bird.example.com'
31
- loga.device = STREAM
32
- end
27
+ config.loga = {
28
+ device: STREAM,
29
+ host: 'bird.example.com',
30
+ service_name: 'hello_world_app',
31
+ service_version: '1.0',
32
+ }
33
33
  end
34
34
  end
@@ -3,85 +3,93 @@ require 'ostruct'
3
3
  RSpec.describe Loga::Railtie do
4
4
  let(:app) { Rails.application }
5
5
  let(:middlewares) { app.middleware.middlewares }
6
- let(:initializers) { described_class.initializers }
7
6
 
8
- describe 'loga_initialize_logger' do
9
- let(:initializer) { initializers.find { |i| i.name == :loga_initialize_logger } }
10
-
11
- let(:app) { OpenStruct.new(config: config) }
12
- let(:config) { OpenStruct.new(loga: loga, log_level: :info) }
13
-
14
- before { initializer.run(app) }
15
-
16
- context 'when loga is disabled' do
17
- let(:loga) { Loga::Configuration.new.tap { |c| c.enabled = false } }
18
-
19
- it 'is not initialized' do
20
- expect(config.logger).to be_nil
7
+ context 'development', if: Rails.env.development? do
8
+ describe 'loga_initialize_logger' do
9
+ let(:formatter) do
10
+ if ActiveSupport::VERSION::MAJOR == 3
11
+ Logger::SimpleFormatter
12
+ else
13
+ ActiveSupport::Logger::SimpleFormatter
14
+ end
21
15
  end
22
- end
23
16
 
24
- context 'when loga is enabled' do
25
- let(:loga) { Loga::Configuration.new }
17
+ it 'assign Loga logger to Rails logger' do
18
+ expect(Loga.logger).to equal(Rails.logger)
19
+ end
26
20
 
27
- it 'initializes the logger' do
28
- expect(config.logger).to be_a(Logger)
21
+ it 'configures Loga with a simple formatter' do
22
+ expect(Loga.configuration.logger.formatter).to be_a(formatter)
29
23
  end
30
24
  end
31
25
  end
32
26
 
33
- it 'inserts Loga::Rack::Logger middleware after Rails::Rack::Logger' do
34
- expect(middlewares.index(Loga::Rack::Logger))
35
- .to eq(middlewares.index(Rails::Rack::Logger) + 1)
36
- end
27
+ context 'production', if: Rails.env.production? do
28
+ describe 'loga_initialize_logger' do
29
+ it 'assign Loga logger to Rails logger' do
30
+ expect(Loga.logger).to equal(Rails.logger)
31
+ end
37
32
 
38
- it 'disables colorized logging' do
39
- expect(app.config.colorize_logging).to eq(false)
40
- end
33
+ it 'configures Loga with a structured formatter' do
34
+ expect(Loga.configuration.logger.formatter).to be_a(Loga::Formatter)
35
+ end
41
36
 
42
- describe 'instrumentation' do
43
- let(:listeners) do
44
- ActiveSupport::Notifications.notifier.listeners_for(notification)
37
+ it 'disables colorized logging' do
38
+ expect(app.config.colorize_logging).to eq(false)
39
+ end
45
40
  end
46
- let(:subscribers) do
47
- listeners.map { |l| l.instance_variable_get(:@delegate).class }
41
+
42
+ describe 'loga_initialize_middleware' do
43
+ it 'inserts Loga::Rack::Logger middleware after Rails::Rack::Logger' do
44
+ expect(middlewares.index(Loga::Rack::Logger))
45
+ .to eq(middlewares.index(Rails::Rack::Logger) + 1)
46
+ end
48
47
  end
49
48
 
50
- context 'ActionView' do
51
- [
52
- 'render_collection.action_view',
53
- 'render_partial.action_view',
54
- 'render_template.action_view',
55
- ].each do |notification|
56
- let(:notification) { notification }
49
+ describe 'instrumentation' do
50
+ let(:listeners) do
51
+ ActiveSupport::Notifications.notifier.listeners_for(notification)
52
+ end
53
+ let(:subscribers) do
54
+ listeners.map { |l| l.instance_variable_get(:@delegate).class }
55
+ end
57
56
 
58
- it 'removes ActionView::LogSubscriber' do
59
- expect(subscribers).to_not include(ActionView::LogSubscriber)
57
+ context 'ActionView' do
58
+ [
59
+ 'render_collection.action_view',
60
+ 'render_partial.action_view',
61
+ 'render_template.action_view',
62
+ ].each do |notification|
63
+ let(:notification) { notification }
64
+
65
+ it 'removes ActionView::LogSubscriber' do
66
+ expect(subscribers).to_not include(ActionView::LogSubscriber)
67
+ end
60
68
  end
61
69
  end
62
- end
63
70
 
64
- context 'ActionController' do
65
- [
66
- 'exist_fragment?.action_controller',
67
- 'expire_fragment.action_controller',
68
- 'expire_page.action_controller',
69
- 'halted_callback.action_controller',
70
- 'logger.action_controller',
71
- 'process_action.action_controller',
72
- 'read_fragment.action_controller',
73
- 'redirect_to.action_controller',
74
- 'send_data.action_controller',
75
- 'send_file.action_controller',
76
- 'start_processing.action_controller',
77
- 'unpermitted_parameters.action_controller',
78
- 'write_fragment.action_controller',
79
- 'write_page.action_controller',
80
- ].each do |notification|
81
- let(:notification) { notification }
71
+ context 'ActionController' do
72
+ [
73
+ 'exist_fragment?.action_controller',
74
+ 'expire_fragment.action_controller',
75
+ 'expire_page.action_controller',
76
+ 'halted_callback.action_controller',
77
+ 'logger.action_controller',
78
+ 'process_action.action_controller',
79
+ 'read_fragment.action_controller',
80
+ 'redirect_to.action_controller',
81
+ 'send_data.action_controller',
82
+ 'send_file.action_controller',
83
+ 'start_processing.action_controller',
84
+ 'unpermitted_parameters.action_controller',
85
+ 'write_fragment.action_controller',
86
+ 'write_page.action_controller',
87
+ ].each do |notification|
88
+ let(:notification) { notification }
82
89
 
83
- it 'removes ActionController::LogSubscriber' do
84
- expect(subscribers).to_not include(ActionController::LogSubscriber)
90
+ it 'removes ActionController::LogSubscriber' do
91
+ expect(subscribers).to_not include(ActionController::LogSubscriber)
92
+ end
85
93
  end
86
94
  end
87
95
  end
@@ -1,23 +1,22 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe 'Integration with Rails', timecop: true do
3
+ RSpec.describe 'Structured logging with Rails', timecop: true,
4
+ if: Rails.env.production? do
4
5
  let(:app) { Rails.application }
5
6
 
6
- let(:json_entries) do
7
- [].tap do |entries|
8
- STREAM.tap do |s|
9
- s.rewind
10
- s.read.split("\n").each do |line|
11
- entries << JSON.parse(line)
12
- end
13
- s.close
14
- s.reopen
15
- end
7
+ let(:log_entries) do
8
+ entries = []
9
+ STREAM.tap do |s|
10
+ s.rewind
11
+ entries = s.read.split("\n").map { |line| JSON.parse(line) }
12
+ s.close
13
+ s.reopen
16
14
  end
15
+ entries
17
16
  end
18
17
 
19
- let(:json) { json_entries.last }
20
- let(:json_response) { JSON.parse(last_response.body) }
18
+ let(:last_log_entry) { log_entries.last }
19
+ let(:json_response) { JSON.parse(last_response.body) }
21
20
 
22
21
  include_examples 'request logger'
23
22
 
@@ -28,13 +27,13 @@ describe 'Integration with Rails', timecop: true do
28
27
 
29
28
  it 'includes the controller name and action' do
30
29
  get '/ok'
31
- expect(json).to include('_request.controller' => 'ApplicationController#ok')
30
+ expect(last_log_entry).to include('_request.controller' => 'ApplicationController#ok')
32
31
  end
33
32
 
34
33
  describe 'LogSubscriber' do
35
34
  context 'ActionController' do
36
35
  let(:action_controller_notifications) do
37
- json_entries.select { |e| e.to_json =~ /Processing by|Completed/ }
36
+ log_entries.select { |e| e.to_json =~ /Processing by|Completed/ }
38
37
  end
39
38
 
40
39
  it 'silences ActionController::LogSubscriber' do
@@ -45,7 +44,7 @@ describe 'Integration with Rails', timecop: true do
45
44
 
46
45
  context 'ActionView' do
47
46
  let(:action_view_notifications) do
48
- json_entries.select { |e| e.to_json =~ /Rendered/ }
47
+ log_entries.select { |e| e.to_json =~ /Rendered/ }
49
48
  end
50
49
 
51
50
  it 'silences ActionView::LogSubscriber' do
@@ -58,8 +57,8 @@ describe 'Integration with Rails', timecop: true do
58
57
  describe 'when request causes ActionDispatch 404' do
59
58
  it 'does not log ActionDispatch::DebugExceptions' do
60
59
  get '/not_found', {}, 'HTTP_X_REQUEST_ID' => '471a34dc'
61
- expect(json_entries.count).to eq(1)
62
- expect(json['short_message']).to eq('GET /not_found 404 in 0ms')
60
+ expect(log_entries.count).to eq(1)
61
+ expect(last_log_entry['short_message']).to eq('GET /not_found 404 in 0ms')
63
62
  end
64
63
  end
65
64
  end
@@ -1,18 +1,20 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe 'Rack request logger with Sinatra', timecop: true do
3
+ RSpec.describe 'Structured logging with Sinatra', timecop: true do
4
4
  let(:io) { StringIO.new }
5
+ let(:format) {}
5
6
  before do
6
7
  Loga.reset
7
- Loga.configure do |config|
8
- config.service_name = 'hello_world_app'
9
- config.service_version = '1.0'
10
- config.filter_parameters = [:password]
11
- config.device = io
12
- end
13
- Loga.initialize!
8
+ Loga.configure(
9
+ device: io,
10
+ filter_parameters: [:password],
11
+ format: format,
12
+ service_name: 'hello_world_app',
13
+ service_version: '1.0',
14
+ tags: [:uuid, 'TEST_TAG'],
15
+ )
14
16
  end
15
- let(:json) do
17
+ let(:last_log_entry) do
16
18
  io.rewind
17
19
  JSON.parse(io.read)
18
20
  end
@@ -24,7 +26,7 @@ describe 'Rack request logger with Sinatra', timecop: true do
24
26
  set :show_exceptions, false
25
27
 
26
28
  use Loga::Rack::RequestId
27
- use Loga::Rack::Logger, Loga.logger, [:uuid, 'TEST_TAG']
29
+ use Loga::Rack::Logger
28
30
 
29
31
  error do
30
32
  status 500
@@ -50,10 +52,47 @@ describe 'Rack request logger with Sinatra', timecop: true do
50
52
  end
51
53
  end
52
54
 
53
- include_examples 'request logger'
55
+ context 'when RACK_ENV is production', if: ENV['RACK_ENV'].eql?('production') do
56
+ let(:format) { :gelf }
57
+ include_examples 'request logger'
58
+
59
+ it 'does not include the controller name and action' do
60
+ get '/ok'
61
+ expect(last_log_entry).to_not include('_request.controller')
62
+ end
63
+ end
64
+
65
+ context 'when RACK_ENV is production', if: ENV['RACK_ENV'].eql?('development') do
66
+ let(:format) { :simple }
67
+ let(:last_log_entry) do
68
+ io.rewind
69
+ io.read
70
+ end
71
+
72
+ context 'get request' do
73
+ it 'logs the request' do
74
+ get '/ok', username: 'yoshi'
75
+ expect(last_log_entry)
76
+ .to eq("#{time_anchor.iso8601(3)} GET /ok?username=yoshi 200 in 0ms\n")
77
+ end
78
+ end
79
+
80
+ context 'request with redirect' do
81
+ it 'specifies the original path' do
82
+ get '/new'
83
+ expect(last_log_entry).to eql("#{time_anchor.iso8601(3)} GET /new 302 in 0ms\n")
84
+ end
85
+ end
86
+
87
+ context 'when the request raises an exception' do
88
+ let(:log_entry_match) do
89
+ %r{GET /error 500 in 0ms.undefined method `name' for nil:NilClass..+sinatra_spec}m
90
+ end
54
91
 
55
- it 'does not include the controller name and action' do
56
- get '/ok'
57
- expect(json).to_not include('_request.controller')
92
+ it 'logs the request with the exception' do
93
+ get '/error'
94
+ expect(last_log_entry).to match(log_entry_match)
95
+ end
96
+ end
58
97
  end
59
98
  end
@@ -5,7 +5,7 @@ RSpec.shared_examples 'request logger' do
5
5
  { username: 'yoshi' },
6
6
  'HTTP_USER_AGENT' => 'Chrome', 'HTTP_X_REQUEST_ID' => '471a34dc'
7
7
 
8
- expect(json).to include(
8
+ expect(last_log_entry).to include(
9
9
  'version' => '1.1',
10
10
  'host' => 'bird.example.com',
11
11
  'short_message' => 'GET /ok?username=yoshi 200 in 0ms',
@@ -35,7 +35,7 @@ RSpec.shared_examples 'request logger' do
35
35
  { email: 'hello@world.com' },
36
36
  'HTTP_USER_AGENT' => 'Chrome', 'HTTP_X_REQUEST_ID' => '471a34dc'
37
37
 
38
- expect(json).to include(
38
+ expect(last_log_entry).to include(
39
39
  'version' => '1.1',
40
40
  'host' => 'bird.example.com',
41
41
  'short_message' => 'POST /users?username=yoshi 200 in 0ms',
@@ -58,7 +58,8 @@ RSpec.shared_examples 'request logger' do
58
58
 
59
59
  it 'preseves request parameters' do
60
60
  post '/users?username=yoshi', email: 'hello@world.com'
61
- expect(json_response).to include('email' => 'hello@world.com', 'username' => 'yoshi')
61
+ expect(json_response)
62
+ .to include('email' => 'hello@world.com', 'username' => 'yoshi')
62
63
  end
63
64
  end
64
65
 
@@ -66,7 +67,7 @@ RSpec.shared_examples 'request logger' do
66
67
  it 'specifies the original path' do
67
68
  get '/new', {}, 'HTTP_USER_AGENT' => 'Chrome', 'HTTP_X_REQUEST_ID' => '471a34dc'
68
69
 
69
- expect(json).to include(
70
+ expect(last_log_entry).to include(
70
71
  'version' => '1.1',
71
72
  'host' => 'bird.example.com',
72
73
  'short_message' => 'GET /new 302 in 0ms',
@@ -94,7 +95,7 @@ RSpec.shared_examples 'request logger' do
94
95
  { username: 'yoshi' },
95
96
  'HTTP_USER_AGENT' => 'Chrome', 'HTTP_X_REQUEST_ID' => '471a34dc'
96
97
 
97
- expect(json).to include(
98
+ expect(last_log_entry).to include(
98
99
  'version' => '1.1',
99
100
  'host' => 'bird.example.com',
100
101
  'short_message' => 'GET /error?username=yoshi 500 in 0ms',
@@ -123,7 +124,7 @@ RSpec.shared_examples 'request logger' do
123
124
  it 'does not log the framework exception' do
124
125
  get '/not_found', {}, 'HTTP_X_REQUEST_ID' => '471a34dc'
125
126
 
126
- expect(json).to include(
127
+ expect(last_log_entry).to include(
127
128
  'version' => '1.1',
128
129
  'host' => 'bird.example.com',
129
130
  'short_message' => 'GET /not_found 404 in 0ms',
@@ -152,13 +153,13 @@ RSpec.shared_examples 'request logger' do
152
153
  let(:params) { { password: 'password123' } }
153
154
 
154
155
  it 'filters the parameter from the params hash' do
155
- expect(json).to include(
156
+ expect(last_log_entry).to include(
156
157
  '_request.params' => { 'password' => '[FILTERED]' },
157
158
  )
158
159
  end
159
160
 
160
161
  it 'filters the parameter from the message' do
161
- expect(json).to include(
162
+ expect(last_log_entry).to include(
162
163
  'short_message' => 'GET /ok?password=[FILTERED] 200 in 0ms',
163
164
  )
164
165
  end
@@ -168,13 +169,13 @@ RSpec.shared_examples 'request logger' do
168
169
  let(:params) { { users: [password: 'password123'] } }
169
170
 
170
171
  it 'filters the parameter from the params hash' do
171
- expect(json).to include(
172
+ expect(last_log_entry).to include(
172
173
  '_request.params' => { 'users' => ['password' => '[FILTERED]'] },
173
174
  )
174
175
  end
175
176
 
176
177
  it 'filters the parameter from the message' do
177
- expect(json).to include(
178
+ expect(last_log_entry).to include(
178
179
  'short_message' => 'GET /ok?users[][password]=[FILTERED] 200 in 0ms',
179
180
  )
180
181
  end