logged 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2b46976fe22a5a244a609bdaf510609c99c28285
4
+ data.tar.gz: 1d26549594e07f0330e6f075f32d8bee8a713182
5
+ SHA512:
6
+ metadata.gz: 5646a200c4ccae0f0a73850a0af06ad4f144f62e91e77fec50ae5fcee7f9eee32eee52f9f59b56d895d9e909c18030e8bb64dcca0fe5dd98368220f74837c0e6
7
+ data.tar.gz: 252309919f1082f7513d66ff10deeb6a68b11c657f56991f804c9c3f360983908a2ef3085e125a4a4941215a160383097bd99f821622d5380888b65e9d12ee37
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,11 @@
1
+ AllCops:
2
+ Exclude:
3
+ - 'spec/**/*'
4
+ - '*.gemspec'
5
+ - 'lib/logged/version.rb'
6
+
7
+ LineLength:
8
+ Max: 120
9
+
10
+ Metrics/MethodLength:
11
+ Max: 16
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in logged.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Florian Schwab
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # logged: configurable Rails logging
2
+
3
+ Logged tries to make managing logging with Rails easier.
4
+
5
+ Heavily inspired by [lograge](https://github.com/roidrage/lograge) logged allows you to log to multiple destinations
6
+ in different formats e.g. one log for view rendering times, one for requests and one for slow queries.
7
+
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'logged'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install logged
24
+
25
+ ## Usage
26
+
27
+ ### Overview
28
+
29
+ ```ruby
30
+ # config/environments/*.rb or config/application.rb
31
+ Rails.application.configure do
32
+
33
+ # Enabling it
34
+ config.logged.enabled = true
35
+
36
+ # Adding a logger
37
+ config.logged.loggers.my.logger = Logger.new(Rails.root.join('log/my.log'))
38
+ config.logged.loggers.rails.logger = :rails
39
+
40
+ # Enabling a component
41
+ config.logged.action_controller.enabled = true
42
+
43
+ # Disable Rails logging for a component
44
+ config.logged.action_controller.disable_rails_logging = true
45
+
46
+ # Setting log level
47
+ config.logged.level = :debug
48
+
49
+ # Setting the formatter
50
+ config.logged.formatter = Logged::Formatter::JSON.new
51
+ config.logged.formatter = ->(data) {
52
+ JSON.dump(data)
53
+ }
54
+
55
+ # Setting tags
56
+ config.logged.tags = [ :uuid, 'my-tag' ]
57
+
58
+ # Ignore events
59
+ config.logged.ignore << 'process_action.action_controller'
60
+
61
+ # Custom ignore callback
62
+ config.logged.custom_ignore = ->(event) {
63
+ event.duration.to_f < 0.25
64
+ }
65
+
66
+ # Modifying the data
67
+ config.logged.custom_data = ->(event, data) {
68
+ data.merge({ foo: :bar })
69
+ }
70
+ end
71
+ ```
72
+
73
+ ### Lograge
74
+
75
+ You can replicate the output of lograge by using the following configuration:
76
+
77
+ ```ruby
78
+ # config/environments/*.rb or config/application.rb
79
+ Rails.application.configure do
80
+ config.logged.enabled = true
81
+ config.logged.action_controller.enabled = true
82
+ config.logged.action_controller.loggers.lograge.logger = Logger.new(Rails.root.join('log/request.log'))
83
+ config.logged.action_controller.custom_data = ->(event, data) {
84
+ data.reject { |k, _v| %i( event filter ).include?(k) }
85
+ }
86
+
87
+ config.logger = Logger.new('/dev/null') # optionally discard other logging
88
+
89
+ # to increase performance you can also add the following:
90
+ config.log_level = :unknown
91
+ config.logged.action_controller.disable_rails_logging = true
92
+ config.logged.action_view.disable_rails_logging = true
93
+ config.logged.action_mailer.disable_rails_logging = true
94
+ config.logged.active_record.disable_rails_logging = true
95
+ end
96
+ ```
97
+
98
+ ## Contributing
99
+
100
+ 1. Fork it ( https://github.com/ydkn/logged/fork )
101
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
102
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
103
+ 4. Push to the branch (`git push origin my-new-feature`)
104
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new
@@ -0,0 +1,56 @@
1
+ require 'active_support/ordered_options'
2
+
3
+ module Logged
4
+ # logged configuration
5
+ class Configuration < ::ActiveSupport::OrderedOptions
6
+ # Default values for configuration options
7
+ DEFAULT_VALUES = {
8
+ enabled: false,
9
+ level: nil,
10
+ formatter: nil,
11
+ ignore: -> { [] },
12
+ tags: -> { [] },
13
+ custom_ignore: nil,
14
+ custom_data: nil
15
+ }
16
+
17
+ def self.init_default_options(config, ignore_defaults = [])
18
+ DEFAULT_VALUES.each do |key, value|
19
+ next if ignore_defaults.include?(key)
20
+
21
+ if value.is_a?(Proc)
22
+ config[key] = value.call
23
+ else
24
+ config[key] = value
25
+ end
26
+ end
27
+ end
28
+
29
+ # Configuration for loggers
30
+ class LoggerOptions < ::ActiveSupport::OrderedOptions
31
+ def initialize
32
+ Configuration.init_default_options(self, [:tags])
33
+
34
+ self.enabled = true
35
+ end
36
+ end
37
+
38
+ # Configuration for components
39
+ class ComponentOptions < ::ActiveSupport::OrderedOptions
40
+ def initialize
41
+ Configuration.init_default_options(self)
42
+
43
+ self.disable_rails_logging = false
44
+ self.loggers = ::ActiveSupport::OrderedOptions.new { |hash, key| hash[key] = LoggerOptions.new }
45
+ end
46
+ end
47
+
48
+ def initialize
49
+ super { |hash, key| hash[key] = ComponentOptions.new }
50
+
51
+ Configuration.init_default_options(self)
52
+
53
+ self.loggers = ::ActiveSupport::OrderedOptions.new { |hash, key| hash[key] = LoggerOptions.new }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,8 @@
1
+ module Logged
2
+ module Formatter
3
+ # Definiation of a logged formatter
4
+ class Base
5
+ def call(_data); end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ require 'json'
2
+ require 'logged/formatter/base'
3
+
4
+ module Logged
5
+ module Formatter
6
+ # JSON formatter for logged
7
+ class JSON < Base
8
+ def call(data)
9
+ ::JSON.dump(data)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ require 'logged/formatter/base'
2
+
3
+ module Logged
4
+ module Formatter
5
+ # Key-Value formatter for logged
6
+ class KeyValue < Base
7
+ def call(data)
8
+ data
9
+ .reject { |_k, v| v.nil? || (v.is_a?(String) && v.blank?) }
10
+ .map { |k, v| format_key(k, v) }
11
+ .join(' ')
12
+ end
13
+
14
+ def format_key(key, value)
15
+ # encapsulate in single quotes if value is a string
16
+ value = "'#{value}'" if value.is_a?(String)
17
+
18
+ # ensure only two decimals
19
+ value = Kernel.format('%.2f', value) if value.is_a?(Float)
20
+
21
+ "#{key}=#{value}"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ require 'logged/formatter/base'
2
+ require 'logged/formatter/key_value'
3
+
4
+ module Logged
5
+ module Formatter
6
+ # Logstash formatter for logged
7
+ class Logstash < Base
8
+ def initialize(message_formatter = nil)
9
+ @message_formatter = message_formatter || KeyValue.new
10
+ end
11
+
12
+ def call(data)
13
+ load_dependencies
14
+
15
+ event = LogStash::Event.new(data)
16
+ event[:message] ||= @message_formatter.call(data)
17
+
18
+ event.to_json
19
+ end
20
+
21
+ private
22
+
23
+ def load_dependencies
24
+ require 'logstash-event'
25
+ rescue LoadError
26
+ STDERR.puts 'You need to install the logstash-event gem to use the logstash formatter.'
27
+ raise
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,12 @@
1
+ require 'logged/formatter/base'
2
+
3
+ module Logged
4
+ module Formatter
5
+ # Raw formatter for logged
6
+ class Raw < Base
7
+ def call(data)
8
+ data
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ require 'logged/formatter/base'
2
+
3
+ module Logged
4
+ module Formatter
5
+ # Single-Key formatter for logged
6
+ class SingleKey < Base
7
+ def initialize(key)
8
+ @key = key
9
+ end
10
+
11
+ def call(data)
12
+ data[@key]
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ module Logged
2
+ # Conversion between log level symbols and integers
3
+ module LevelConversion
4
+ def level_to_const(level)
5
+ ::Logger.const_get(level.to_s.upcase)
6
+ end
7
+
8
+ def level_to_sym(level)
9
+ case level
10
+ when ::Logger::FATAL then :fatal
11
+ when ::Logger::ERROR then :error
12
+ when ::Logger::WARN then :warn
13
+ when ::Logger::INFO then :info
14
+ when ::Logger::DEBUG then :debug
15
+ else :unknown
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,96 @@
1
+ require 'uri'
2
+ require 'logged/log_subscriber/base'
3
+
4
+ module Logged
5
+ module LogSubscriber
6
+ # Log subscriber for ActionController events
7
+ class ActionController < Base
8
+ component :action_controller
9
+
10
+ def process_action(event)
11
+ return if ignore?(event, :info)
12
+
13
+ payload = event.payload
14
+
15
+ data = {
16
+ event: event.name
17
+ }
18
+
19
+ data.merge!(extract_request(payload))
20
+ data.merge!(cached_event_data)
21
+ data.merge!(extract_path(payload))
22
+ data.merge!(extract_status(payload))
23
+ data.merge!(extract_runtimes(payload))
24
+
25
+ data[:duration] = event.duration.to_f.round(2)
26
+
27
+ info(event, data)
28
+ end
29
+
30
+ def redirect_to(event)
31
+ Thread.current[:logged_action_controller_location] = event.payload[:location]
32
+ end
33
+
34
+ def halted_callback(event)
35
+ Thread.current[:logged_action_controller_filter] = event.payload[:filter]
36
+ end
37
+
38
+ private
39
+
40
+ def extract_request(payload)
41
+ {
42
+ method: payload[:method].to_sym,
43
+ format: payload[:format],
44
+ controller: payload[:params]['controller'],
45
+ action: payload[:params]['action']
46
+ }.reject { |_k, v| v.blank? }
47
+ end
48
+
49
+ def extract_path(payload)
50
+ uri = URI.parse(payload[:path])
51
+
52
+ { path: uri.path }
53
+ end
54
+
55
+ def extract_status(payload)
56
+ status = payload[:status]
57
+ error = payload[:exception]
58
+
59
+ if status
60
+ { status: status.to_i }
61
+ elsif error
62
+ exception, message = error
63
+
64
+ { status: 500, error: "#{exception}:#{message}" }
65
+ else
66
+ { status: 0 }
67
+ end
68
+ end
69
+
70
+ def extract_runtimes(payload)
71
+ view_runtime, db_runtime = nil
72
+
73
+ view_runtime = payload[:view_runtime].to_f.round(2) if payload.key?(:view_runtime)
74
+ db_runtime = payload[:db_runtime].to_f.round(2) if payload.key?(:db_runtime)
75
+
76
+ {
77
+ view_runtime: view_runtime,
78
+ db_runtime: db_runtime
79
+ }.reject { |_k, v| v.blank? }
80
+ end
81
+
82
+ def cached_event_data
83
+ location = Thread.current[:logged_action_controller_location]
84
+ Thread.current[:logged_action_controller_location] = nil
85
+
86
+ filter = Thread.current[:logged_action_controller_filter]
87
+ Thread.current[:logged_action_controller_filter] = nil
88
+
89
+ {
90
+ location: location,
91
+ filter: filter
92
+ }.reject { |_k, v| v.blank? }
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,67 @@
1
+ require 'logged/log_subscriber/base'
2
+
3
+ module Logged
4
+ module LogSubscriber
5
+ # Log subscriber for ActionMailer events
6
+ class ActionMailer < Base
7
+ component :action_mailer
8
+
9
+ # An email was delivered.
10
+ def deliver(event)
11
+ return if ignore?(event, :debug)
12
+
13
+ process_duration = Thread.current[:logged_action_mailer_process_duration] || 0.0
14
+
15
+ data = {
16
+ event: event.name,
17
+ duration: (process_duration + event.duration.to_f).round(2)
18
+ }
19
+
20
+ data.merge!(extract_mail_deliver(event.payload))
21
+
22
+ Thread.current[:logged_action_mailer_process_duration] = nil
23
+
24
+ debug(event, data)
25
+ end
26
+
27
+ # An email was received.
28
+ def receive(event)
29
+ return unless logger.debug?
30
+ return if ignore?(event)
31
+
32
+ data = {
33
+ event: event.name,
34
+ duration: event.duration.to_f.round(2)
35
+ }
36
+
37
+ debug(event, data)
38
+ end
39
+
40
+ # An email was generated.
41
+ def process(event)
42
+ payload = event.payload
43
+
44
+ Thread.current[:logged_action_mailer_process_mailer] = payload[:mailer]
45
+ Thread.current[:logged_action_mailer_process_action] = payload[:action]
46
+ Thread.current[:logged_action_mailer_process_duration] = event.duration.to_f
47
+ end
48
+
49
+ private
50
+
51
+ def extract_mail_deliver(payload)
52
+ data = {
53
+ mailer: (payload[:mailer] || Thread.current[:logged_action_mailer_process_mailer]),
54
+ action: (payload[:action] || Thread.current[:logged_action_mailer_process_action]),
55
+ from: Array(payload[:from]).join(', '),
56
+ to: Array(payload[:to]).join(', '),
57
+ bcc: Array(payload[:bcc]).join(', ')
58
+ }
59
+
60
+ Thread.current[:logged_action_mailer_process_mailer] = nil
61
+ Thread.current[:logged_action_mailer_process_action] = nil
62
+
63
+ data
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,42 @@
1
+ require 'action_view/log_subscriber'
2
+ require 'logged/log_subscriber/base'
3
+
4
+ module Logged
5
+ module LogSubscriber
6
+ # Log subscriber for ActionView events
7
+ class ActionView < Base
8
+ component :action_view
9
+
10
+ def render_template(event)
11
+ return if ignore?(event, :debug)
12
+
13
+ payload = event.payload
14
+
15
+ data = {
16
+ event: event.name,
17
+ view: from_rails_root(payload[:identifier]),
18
+ layout: from_rails_root(payload[:layout]),
19
+ duration: event.duration.to_f.round(2)
20
+ }.reject { |_k, v| v.blank? }
21
+
22
+ debug(event, data)
23
+ end
24
+ alias_method :render_partial, :render_template
25
+ alias_method :render_collection, :render_template
26
+
27
+ protected
28
+
29
+ def from_rails_root(string)
30
+ return nil if string.blank?
31
+
32
+ string = string.sub(rails_root, ::ActionView::LogSubscriber::EMPTY)
33
+ string.sub!(::ActionView::LogSubscriber::VIEWS_PATTERN, ::ActionView::LogSubscriber::EMPTY)
34
+ string
35
+ end
36
+
37
+ def rails_root
38
+ @root ||= "#{::Rails.root}/"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,31 @@
1
+ require 'active_record/log_subscriber'
2
+ require 'logged/log_subscriber/base'
3
+
4
+ module Logged
5
+ module LogSubscriber
6
+ # Log subscriber for ActiveRecord events
7
+ class ActiveRecord < Base
8
+ # This query types will be ignored
9
+ IGNORE_PAYLOAD_NAMES = %w( SCHEMA EXPLAIN )
10
+
11
+ component :active_record
12
+
13
+ def sql(event)
14
+ return if ignore?(event, :debug)
15
+
16
+ payload = event.payload
17
+
18
+ return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
19
+
20
+ data = {
21
+ event: event.name,
22
+ name: payload[:name].presence,
23
+ sql: payload[:sql],
24
+ duration: event.duration.to_f.round(2)
25
+ }
26
+
27
+ debug(event, data)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,50 @@
1
+ require 'active_support/log_subscriber'
2
+
3
+ module Logged
4
+ module LogSubscriber
5
+ # Shared stuff for logged log subscribers
6
+ class Base < ::ActiveSupport::LogSubscriber
7
+ def self.component(component)
8
+ @component = component
9
+
10
+ Logged.register(component, self)
11
+ end
12
+
13
+ def logger
14
+ @logger ||= Logged.logger_by_component(component)
15
+ end
16
+
17
+ private
18
+
19
+ %w(info debug warn error fatal unknown).each do |level|
20
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
21
+ def #{level}(event, progname = nil, &block)
22
+ return unless logger
23
+
24
+ progname = yield if block_given? && progname.nil?
25
+
26
+ return unless progname
27
+
28
+ progname['@event'] = event
29
+
30
+ logger.#{level}(progname)
31
+ end
32
+ METHOD
33
+ end
34
+
35
+ def component
36
+ self.class.instance_variable_get('@component')
37
+ end
38
+
39
+ def ignore?(event, log_level = nil)
40
+ return true unless logger
41
+ return true unless !log_level || logger.send("#{log_level}?")
42
+
43
+ return true if Logged.ignore?(Logged.config, event)
44
+ return true if Logged.ignore?(Logged.config[component], event)
45
+
46
+ false
47
+ end
48
+ end
49
+ end
50
+ end