loga 1.4.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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