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.
data/circle.yml CHANGED
@@ -1,16 +1,16 @@
1
1
  dependencies:
2
2
  override:
3
- - rvm-exec 2.2.2 bundle install -j 4
4
- - rvm-exec 2.2.2 bundle exec appraisal install -j 4
3
+ - rvm-exec 2.2.5 bundle install -j 4
4
+ - rvm-exec 2.2.5 bundle exec appraisal install -j 4
5
5
  - rvm-exec 2.3.1 bundle install -j 4
6
6
  - rvm-exec 2.3.1 bundle exec appraisal install -j 4
7
7
  test:
8
8
  override:
9
- - RACK_ENV=development rvm-exec 2.2.2 bundle exec appraisal rspec
10
- - RACK_ENV=production rvm-exec 2.2.2 bundle exec appraisal rspec
9
+ - RACK_ENV=development rvm-exec 2.2.5 bundle exec appraisal rspec
10
+ - RACK_ENV=production rvm-exec 2.2.5 bundle exec appraisal rspec
11
11
  - RACK_ENV=development rvm-exec 2.3.1 bundle exec appraisal rspec
12
12
  - RACK_ENV=production rvm-exec 2.3.1 bundle exec appraisal rspec
13
- - rvm-exec 2.2.2 bundle exec rubocop
13
+ - rvm-exec 2.2.5 bundle exec rubocop
14
14
  deployment:
15
15
  gemfury:
16
16
  tag: /.*/
@@ -5,23 +5,27 @@ require 'loga/utilities'
5
5
  require 'loga/event'
6
6
  require 'loga/formatter'
7
7
  require 'loga/parameter_filter'
8
- require 'loga/revision_strategy'
9
8
  require 'loga/rack/logger'
10
9
  require 'loga/rack/request'
11
10
  require 'loga/rack/request_id'
12
11
  require 'loga/railtie' if defined?(Rails)
13
12
 
14
13
  module Loga
15
- def self.configuration
16
- @configuration ||= Configuration.new
17
- end
14
+ ConfigurationError = Class.new(StandardError)
18
15
 
19
- def self.configure
20
- yield configuration
16
+ def self.configuration
17
+ if @configuration.nil?
18
+ raise ConfigurationError,
19
+ 'Loga has not been configured. Configure with Loga.configure(options)'
20
+ end
21
+ @configuration
21
22
  end
22
23
 
23
- def self.initialize!
24
- configuration.initialize!
24
+ def self.configure(options, framework_options = {})
25
+ unless @configuration.nil?
26
+ raise ConfigurationError, 'Loga has already been configured'
27
+ end
28
+ @configuration ||= Configuration.new(options, framework_options)
25
29
  end
26
30
 
27
31
  def self.logger
@@ -1,76 +1,127 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'active_support/version'
3
+ require 'loga/service_version_strategies'
1
4
  require 'logger'
2
5
  require 'socket'
3
6
 
4
7
  module Loga
5
8
  class Configuration
6
- attr_accessor :service_name,
7
- :service_version,
8
- :device,
9
- :sync,
10
- :filter_parameters,
11
- :level,
12
- :host,
13
- :enabled,
14
- :silence_rails_rack_logger
15
-
16
- attr_reader :logger
17
-
18
- def initialize
19
- @host = gethostname
20
- @device = nil
21
- @sync = true
22
- @level = :info
23
- @filter_parameters = []
24
- @service_version = :git
25
-
26
- # Rails specific configuration
27
- @enabled = true
28
- @silence_rails_rack_logger = true
9
+ DEFAULT_KEYS = %i(
10
+ device
11
+ filter_exceptions
12
+ filter_parameters
13
+ format
14
+ host
15
+ level
16
+ service_name
17
+ service_version
18
+ sync
19
+ tags
20
+ ).freeze
21
+
22
+ FRAMEWORK_EXCEPTIONS = %w(
23
+ ActionController::RoutingError
24
+ ActiveRecord::RecordNotFound
25
+ Sinatra::NotFound
26
+ ).freeze
27
+
28
+ attr_accessor(*DEFAULT_KEYS)
29
+ attr_reader :logger, :service_version
30
+ private_constant :DEFAULT_KEYS
31
+
32
+ def initialize(user_options = {}, framework_options = {})
33
+ options = default_options.merge(framework_options)
34
+ .merge(environment_options)
35
+ .merge(user_options)
36
+
37
+ DEFAULT_KEYS.each do |attribute|
38
+ public_send("#{attribute}=", options[attribute])
39
+ end
40
+
41
+ raise ConfigurationError, 'Service name cannot be blank' if service_name.blank?
42
+ raise ConfigurationError, 'Device cannot be blank' if device.blank?
43
+
44
+ @service_version = initialize_service_version
45
+ @logger = initialize_logger
29
46
  end
30
47
 
31
- def initialize!
32
- @service_name = service_name.to_s.strip
33
- @service_version = compute_service_version
48
+ def format=(name)
49
+ @format = name.to_s.to_sym
50
+ end
34
51
 
35
- initialize_logger
52
+ def service_name=(name)
53
+ @service_name = name.to_s.strip
36
54
  end
37
55
 
38
- def configure
39
- yield self
56
+ def structured?
57
+ format == :gelf
40
58
  end
41
59
 
42
60
  private
43
61
 
44
- def compute_service_version
45
- RevisionStrategy.call(service_version)
62
+ def default_options
63
+ {
64
+ device: STDOUT,
65
+ filter_exceptions: FRAMEWORK_EXCEPTIONS,
66
+ filter_parameters: [],
67
+ format: :simple,
68
+ host: hostname,
69
+ level: :info,
70
+ sync: true,
71
+ tags: [],
72
+ }
46
73
  end
47
74
 
48
- def initialize_logger
49
- device.sync = sync
75
+ def environment_options
76
+ { format: ENV['LOGA_FORMAT'].presence }.reject { |_, v| v.nil? }
77
+ end
78
+
79
+ def initialize_service_version
80
+ service_version || ServiceVersionStrategies.call
81
+ end
50
82
 
83
+ def initialize_logger
84
+ device.sync = sync
51
85
  logger = Logger.new(device)
52
- logger.formatter = Formatter.new(
53
- service_name: service_name,
54
- service_version: service_version,
55
- host: host,
56
- )
86
+ logger.formatter = assign_formatter
57
87
  logger.level = constantized_log_level
58
- rescue
59
- logger = Logger.new(STDERR)
60
- logger.level = Logger::ERROR
61
- logger.error 'Loga could not be initialized'
62
- ensure
63
- @logger = TaggedLogging.new(logger)
88
+ TaggedLogging.new(logger)
64
89
  end
65
90
 
66
91
  def constantized_log_level
67
92
  Logger.const_get(level.to_s.upcase)
68
93
  end
69
94
 
70
- def gethostname
95
+ def hostname
71
96
  Socket.gethostname
72
- rescue Exception
97
+ rescue SystemCallError
73
98
  'unknown.host'
74
99
  end
100
+
101
+ def assign_formatter
102
+ if format == :gelf
103
+ Formatter.new(
104
+ service_name: service_name,
105
+ service_version: service_version,
106
+ host: host,
107
+ )
108
+ else
109
+ active_support_simple_formatter
110
+ end
111
+ end
112
+
113
+ def active_support_simple_formatter
114
+ case ActiveSupport::VERSION::MAJOR
115
+ when 3
116
+ require 'active_support/core_ext/logger'
117
+ Logger::SimpleFormatter.new
118
+ when 4..5
119
+ require 'active_support/logger'
120
+ ActiveSupport::Logger::SimpleFormatter.new
121
+ else
122
+ raise Loga::ConfigurationError,
123
+ "ActiveSupport #{ActiveSupport::VERSION::MAJOR} is unsupported"
124
+ end
125
+ end
75
126
  end
76
127
  end
@@ -10,6 +10,17 @@ module Loga
10
10
  @type = opts[:type]
11
11
  end
12
12
 
13
+ def to_s
14
+ output = ["#{timestamp.iso8601(3)} #{message}"]
15
+ if exception
16
+ output.push exception.to_s
17
+ output.push exception.backtrace.join("\n")
18
+ end
19
+ output.join("\n")
20
+ end
21
+
22
+ alias inspect to_s
23
+
13
24
  private
14
25
 
15
26
  # Guard against Encoding::UndefinedConversionError
@@ -3,11 +3,8 @@ module Loga
3
3
  class Logger
4
4
  include Utilities
5
5
 
6
- attr_reader :logger, :taggers
7
- def initialize(app, logger = nil, taggers = nil)
8
- @app = app
9
- @logger = logger
10
- @taggers = taggers || []
6
+ def initialize(app)
7
+ @app = app
11
8
  end
12
9
 
13
10
  def call(env)
@@ -37,6 +34,7 @@ module Loga
37
34
  send_message
38
35
  end
39
36
 
37
+ # rubocop:disable Metrics/LineLength
40
38
  def set_data
41
39
  data['method'] = request.request_method
42
40
  data['path'] = request.original_path
@@ -44,14 +42,15 @@ module Loga
44
42
  data['request_id'] = request.uuid
45
43
  data['request_ip'] = request.ip
46
44
  data['user_agent'] = request.user_agent
47
- data['controller'] = request.action_controller if request.action_controller_instance
45
+ data['controller'] = request.controller_action_name if request.controller_action_name
48
46
  data['duration'] = duration_in_ms(started_at, Time.now)
49
47
  end
48
+ # rubocop:enable Metrics/LineLength
50
49
 
51
50
  def send_message
52
51
  event = Loga::Event.new(
53
52
  data: { request: data },
54
- exception: fetch_exception,
53
+ exception: compute_exception,
55
54
  message: compute_message,
56
55
  timestamp: started_at,
57
56
  type: 'request',
@@ -59,6 +58,10 @@ module Loga
59
58
  logger.public_send(compute_level, event)
60
59
  end
61
60
 
61
+ def logger
62
+ Loga.logger
63
+ end
64
+
62
65
  def compute_message
63
66
  '%{method} %{filtered_full_path} %{status} in %{duration}ms' % {
64
67
  method: request.request_method,
@@ -69,25 +72,23 @@ module Loga
69
72
  end
70
73
 
71
74
  def compute_level
72
- fetch_exception ? :error : :info
75
+ compute_exception ? :error : :info
73
76
  end
74
77
 
75
- def fetch_exception
76
- framework_exception.tap do |e|
77
- return filtered_exceptions.include?(e.class.to_s) ? nil : e
78
- end
78
+ def compute_exception
79
+ filter_exceptions.include?(exception.class.to_s) ? nil : exception
79
80
  end
80
81
 
81
- def framework_exception
82
+ def exception
82
83
  env['loga.exception'] || env['action_dispatch.exception'] || env['sinatra.error']
83
84
  end
84
85
 
85
- def filtered_exceptions
86
- %w(ActionController::RoutingError Sinatra::NotFound)
86
+ def filter_exceptions
87
+ Loga.configuration.filter_exceptions
87
88
  end
88
89
 
89
90
  def compute_tags(request)
90
- taggers.collect do |tag|
91
+ tags.map do |tag|
91
92
  case tag
92
93
  when Proc
93
94
  tag.call(request)
@@ -98,6 +99,10 @@ module Loga
98
99
  end
99
100
  end
100
101
  end
102
+
103
+ def tags
104
+ Loga.configuration.tags
105
+ end
101
106
  end
102
107
  end
103
108
  end
@@ -18,22 +18,28 @@ module Loga
18
18
 
19
19
  alias request_id uuid
20
20
 
21
- def action_controller
22
- "#{action_controller_instance.class}##{action_controller_instance.action_name}"
23
- end
24
-
25
- def action_controller_instance
26
- @action_controller_instance ||= env[ACTION_CONTROLLER_INSTANCE]
21
+ # Builds a namespaced controller name and action name string.
22
+ #
23
+ # class Admin::UsersController
24
+ # def show
25
+ # end
26
+ # end
27
+ #
28
+ # => "Admin::UsersController#show"
29
+ def controller_action_name
30
+ aci && "#{aci.class.name}##{aci.action_name}"
27
31
  end
28
32
 
29
33
  def original_path
30
34
  env['loga.request.original_path']
31
35
  end
32
36
 
37
+ # rubocop:disable Metrics/LineLength
33
38
  def filtered_full_path
34
39
  @filtered_full_path ||=
35
40
  query_string.empty? ? original_path : "#{original_path}?#{filtered_query_string}"
36
41
  end
42
+ # rubocop:enable Metrics/LineLength
37
43
 
38
44
  def filtered_parameters
39
45
  @filtered_parameters ||= filtered_query_hash.merge(filtered_form_hash)
@@ -83,6 +89,12 @@ module Loga
83
89
  def action_dispatch_filter_params
84
90
  env['action_dispatch.parameter_filter'] || []
85
91
  end
92
+
93
+ def action_controller_instance
94
+ @action_controller_instance ||= env[ACTION_CONTROLLER_INSTANCE]
95
+ end
96
+
97
+ alias aci action_controller_instance
86
98
  end
87
99
  end
88
100
  end
@@ -2,41 +2,70 @@ require 'loga'
2
2
 
3
3
  module Loga
4
4
  class Railtie < Rails::Railtie
5
+ config.loga = {}
6
+
7
+ # This service class initializes Loga with user options, framework smart
8
+ # options and update Rails logger with Loga
5
9
  class InitializeLogger
10
+ def self.call(app)
11
+ new(app).call
12
+ end
13
+
6
14
  def initialize(app)
7
15
  @app = app
8
16
  end
9
17
 
10
18
  def call
11
- loga.tap do |config|
12
- config.sync = sync
13
- config.level = app.config.log_level
14
- end
15
- loga.initialize!
16
- app.config.logger = loga.logger
19
+ validate_user_options
20
+ Loga.configure(user_options, rails_options)
21
+ app.config.colorize_logging = false if Loga.configuration.structured?
22
+ app.config.logger = Loga.logger
17
23
  end
18
24
 
19
25
  private
20
26
 
21
27
  attr_reader :app
22
28
 
23
- def loga
29
+ def rails_options
30
+ {
31
+ format: format,
32
+ level: app.config.log_level,
33
+ sync: sync,
34
+ tags: app.config.log_tags || [],
35
+ }.merge(device_options)
36
+ end
37
+
38
+ def device_options
39
+ Rails.env.test? ? { device: File.open('log/test.log', 'a') } : {}
40
+ end
41
+
42
+ def user_options
24
43
  app.config.loga
25
44
  end
26
45
 
46
+ def format
47
+ Rails.env.production? ? :gelf : :simple
48
+ end
49
+
27
50
  def sync
28
51
  Rails::VERSION::MAJOR > 3 ? app.config.autoflush_log : true
29
52
  end
30
53
 
31
- def constantized_log_level
32
- Logger.const_get(app.config.log_level.to_s.upcase)
54
+ # rubocop:disable Metrics/LineLength
55
+ def validate_user_options
56
+ if user_options[:tags].present?
57
+ raise Loga::ConfigurationError, 'Configure tags with Rails config.log_tags'
58
+ elsif user_options[:level].present?
59
+ raise Loga::ConfigurationError, 'Configure level with Rails config.log_level'
60
+ elsif user_options[:filter_parameters].present?
61
+ raise Loga::ConfigurationError, 'Configure filter_parameters with Rails config.filter_parameters'
62
+ end
33
63
  end
64
+ # rubocop:enable Metrics/LineLength
34
65
  end
35
66
 
36
- config.loga = Loga::Configuration.new
37
-
38
67
  initializer :loga_initialize_logger, before: :initialize_logger do |app|
39
- InitializeLogger.new(app).call if app.config.loga.enabled
68
+ InitializeLogger.call(app)
40
69
  end
41
70
 
42
71
  class InitializeMiddleware
@@ -56,15 +85,19 @@ module Loga
56
85
  end
57
86
  end
58
87
 
88
+ def self.call(app)
89
+ new(app).call
90
+ end
91
+
59
92
  def initialize(app)
60
93
  @app = app
61
94
  end
62
95
 
63
96
  def call
64
97
  insert_loga_rack_logger
65
- disable_rails_rack_logger
98
+ silence_rails_rack_logger
66
99
  insert_exceptions_catcher
67
- disable_action_dispatch_debug_exceptions
100
+ silence_action_dispatch_debug_exceptions_logger
68
101
  end
69
102
 
70
103
  private
@@ -81,45 +114,51 @@ module Loga
81
114
 
82
115
  # Removes start of request log
83
116
  # (e.g. Started GET "/users" for 127.0.0.1 at 2015-12-24 23:59:00 +0000)
84
- def disable_rails_rack_logger
85
- return unless app.config.loga.silence_rails_rack_logger
86
-
117
+ def silence_rails_rack_logger
87
118
  case Rails::VERSION::MAJOR
88
- when 3 then require 'loga/ext/rails/rack/logger3.rb'
119
+ when 3 then require 'loga/ext/rails/rack/logger3.rb'
120
+ when 4..5 then require 'loga/ext/rails/rack/logger.rb'
89
121
  else
90
- require 'loga/ext/rails/rack/logger.rb'
122
+ raise Loga::ConfigurationError,
123
+ "Rails #{Rails::VERSION::MAJOR} is unsupported"
91
124
  end
92
125
  end
93
126
 
94
- def disable_action_dispatch_debug_exceptions
127
+ # Removes unstructured exception output. Exceptions are logged with
128
+ # Loga::Rack::Logger instead
129
+ def silence_action_dispatch_debug_exceptions_logger
95
130
  require 'loga/ext/rails/rack/debug_exceptions.rb'
96
131
  end
97
132
 
98
133
  def insert_loga_rack_logger
99
- app.middleware.insert_after Rails::Rack::Logger,
100
- Loga::Rack::Logger,
101
- app.config.logger,
102
- app.config.log_tags
134
+ app.middleware.insert_after Rails::Rack::Logger, Loga::Rack::Logger
103
135
  end
104
136
  end
105
137
 
106
138
  initializer :loga_initialize_middleware do |app|
107
- InitializeMiddleware.new(app).call if app.config.loga.enabled
108
- app.config.colorize_logging = false
139
+ InitializeMiddleware.call(app) if Loga.configuration.structured?
109
140
  end
110
141
 
111
142
  class InitializeInstrumentation
143
+ def self.call
144
+ new.call
145
+ end
146
+
112
147
  def call
113
- remove_existing_log_subscriptions
148
+ ensure_subscriptions_attached
149
+ remove_log_subscriptions
114
150
  end
115
151
 
116
152
  private
117
153
 
118
- # rubocop:disable Metrics/CyclomaticComplexity
119
- def remove_existing_log_subscriptions
154
+ # Ensure LogSubscribers are attached when available
155
+ def ensure_subscriptions_attached
120
156
  ActionView::Base if defined?(ActionView::Base)
121
157
  ActionController::Base if defined?(ActionController::Base)
158
+ end
122
159
 
160
+ # rubocop:disable Metrics/LineLength
161
+ def remove_log_subscriptions
123
162
  ActiveSupport::LogSubscriber.log_subscribers.each do |subscriber|
124
163
  case subscriber
125
164
  when defined?(ActionView::LogSubscriber) && ActionView::LogSubscriber
@@ -129,10 +168,12 @@ module Loga
129
168
  end
130
169
  end
131
170
  end
132
- # rubocop:enable Metrics/CyclomaticComplexity
171
+ # rubocop:enable Metrics/LineLength
133
172
 
134
173
  def unsubscribe(component, subscriber)
135
- events = subscriber.public_methods(false).reject { |method| method.to_s == 'call' }
174
+ events = subscriber
175
+ .public_methods(false)
176
+ .reject { |method| method.to_s == 'call' }
136
177
  events.each do |event|
137
178
  ActiveSupport::Notifications
138
179
  .notifier
@@ -146,8 +187,8 @@ module Loga
146
187
  end
147
188
  end
148
189
 
149
- config.after_initialize do |app|
150
- InitializeInstrumentation.new.call if app.config.loga.enabled
190
+ config.after_initialize do |_|
191
+ InitializeInstrumentation.call if Loga.configuration.structured?
151
192
  end
152
193
  end
153
194
  end