logtail-rails 0.1.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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +144 -0
  3. data/.gitignore +23 -0
  4. data/.rspec +3 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE.md +13 -0
  7. data/README.md +10 -0
  8. data/Rakefile +6 -0
  9. data/gemfiles/rails-3.0.gemfile +8 -0
  10. data/gemfiles/rails-3.1.gemfile +8 -0
  11. data/gemfiles/rails-3.2.gemfile +8 -0
  12. data/gemfiles/rails-4.0.gemfile +12 -0
  13. data/gemfiles/rails-4.1.gemfile +12 -0
  14. data/gemfiles/rails-4.2.gemfile +12 -0
  15. data/gemfiles/rails-5.0.gemfile +12 -0
  16. data/gemfiles/rails-5.1.gemfile +12 -0
  17. data/gemfiles/rails-5.2.gemfile +12 -0
  18. data/gemfiles/rails-6.0.gemfile +12 -0
  19. data/gemfiles/rails-6.1.gemfile +12 -0
  20. data/gemfiles/rails-edge.gemfile +10 -0
  21. data/lib/logtail-rails.rb +64 -0
  22. data/lib/logtail-rails/action_controller.rb +17 -0
  23. data/lib/logtail-rails/action_controller/log_subscriber.rb +25 -0
  24. data/lib/logtail-rails/action_controller/log_subscriber/logtail_log_subscriber.rb +46 -0
  25. data/lib/logtail-rails/action_dispatch.rb +20 -0
  26. data/lib/logtail-rails/action_dispatch/debug_exceptions.rb +51 -0
  27. data/lib/logtail-rails/action_view.rb +17 -0
  28. data/lib/logtail-rails/action_view/log_subscriber.rb +25 -0
  29. data/lib/logtail-rails/action_view/log_subscriber/logtail_log_subscriber.rb +84 -0
  30. data/lib/logtail-rails/active_record.rb +17 -0
  31. data/lib/logtail-rails/active_record/log_subscriber.rb +20 -0
  32. data/lib/logtail-rails/active_record/log_subscriber/logtail_log_subscriber.rb +52 -0
  33. data/lib/logtail-rails/active_support_log_subscriber.rb +39 -0
  34. data/lib/logtail-rails/config.rb +12 -0
  35. data/lib/logtail-rails/config/action_controller.rb +29 -0
  36. data/lib/logtail-rails/config/action_view.rb +29 -0
  37. data/lib/logtail-rails/config/active_record.rb +29 -0
  38. data/lib/logtail-rails/error_event.rb +62 -0
  39. data/lib/logtail-rails/logger.rb +20 -0
  40. data/lib/logtail-rails/overrides.rb +12 -0
  41. data/lib/logtail-rails/overrides/active_support_3_tagged_logging.rb +111 -0
  42. data/lib/logtail-rails/overrides/active_support_buffered_logger.rb +22 -0
  43. data/lib/logtail-rails/overrides/active_support_tagged_logging.rb +66 -0
  44. data/lib/logtail-rails/overrides/lograge.rb +18 -0
  45. data/lib/logtail-rails/overrides/rails_stdout_logging.rb +20 -0
  46. data/lib/logtail-rails/rack_logger.rb +59 -0
  47. data/lib/logtail-rails/railtie.rb +27 -0
  48. data/lib/logtail-rails/session_context.rb +37 -0
  49. data/lib/logtail-rails/version.rb +7 -0
  50. data/logtail-rails.gemspec +60 -0
  51. metadata +263 -0
@@ -0,0 +1,29 @@
1
+ require "logtail-rails/action_controller"
2
+
3
+ module Logtail
4
+ class Config
5
+ # Convenience module for accessing the various `Logtail::Integrations::*` classes
6
+ # through the {Logtail::Config} object. Logtail couples configuration with the class
7
+ # responsible for implementing it. This provides for a tighter design, but also
8
+ # requires the user to understand and access the various classes. This module aims
9
+ # to provide a simple ruby-like configuration interface for internal Logtail classes.
10
+ #
11
+ # For example:
12
+ #
13
+ # config = Logtail::Config.instance
14
+ # config.integrations.active_record.silence = true
15
+ module Integrations
16
+ extend self
17
+
18
+ # Convenience method for accessing the {Logtail::Integrations::ActionController} class
19
+ # specific configuration.
20
+ #
21
+ # @example
22
+ # config = Logtail::Config.instance
23
+ # config.integrations.action_controller.silence = true
24
+ def action_controller
25
+ Logtail::Integrations::ActionController
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ require "logtail-rails/action_view"
2
+
3
+ module Logtail
4
+ class Config
5
+ # Convenience module for accessing the various `Logtail::Integrations::*` classes
6
+ # through the {Logtail::Config} object. Logtail couples configuration with the class
7
+ # responsible for implementing it. This provides for a tighter design, but also
8
+ # requires the user to understand and access the various classes. This module aims
9
+ # to provide a simple ruby-like configuration interface for internal Logtail classes.
10
+ #
11
+ # For example:
12
+ #
13
+ # config = Logtail::Config.instance
14
+ # config.integrations.active_record.silence = true
15
+ module Integrations
16
+ extend self
17
+
18
+ # Convenience method for accessing the {Logtail::Integrations::ActionView} class
19
+ # specific configuration.
20
+ #
21
+ # @example
22
+ # config = Logtail::Config.instance
23
+ # config.integrations.action_view.silence = true
24
+ def action_view
25
+ Logtail::Integrations::ActionView
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ require "logtail-rails/active_record"
2
+
3
+ module Logtail
4
+ class Config
5
+ # Convenience module for accessing the various `Logtail::Integrations::*` classes
6
+ # through the {Logtail::Config} object. Logtail couples configuration with the class
7
+ # responsible for implementing it. This provides for a tighter design, but also
8
+ # requires the user to understand and access the various classes. This module aims
9
+ # to provide a simple ruby-like configuration interface for internal Logtail classes.
10
+ #
11
+ # For example:
12
+ #
13
+ # config = Logtail::Config.instance
14
+ # config.integrations.active_record.silence = true
15
+ module Integrations
16
+ extend self
17
+
18
+ # Convenience method for accessing the {Logtail::Integrations::ActiveRecord} class
19
+ # specific configuration.
20
+ #
21
+ # @example
22
+ # config = Logtail::Config.instance
23
+ # config.integrations.active_record.silence = true
24
+ def active_record
25
+ Logtail::Integrations::ActiveRecord
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,62 @@
1
+ begin
2
+ # Rails 3.2 requires you to require all of Rails before requiring
3
+ # the exception wrapper.
4
+ require "action_dispatch/middleware/exception_wrapper"
5
+ rescue Exception
6
+ end
7
+
8
+ require "logtail/events/error"
9
+ require "logtail-rack/middleware"
10
+ require "logtail/util"
11
+
12
+ module Logtail
13
+ module Integrations
14
+ module Rails
15
+ # A Rack middleware that is responsible for capturing exception and error events
16
+ # {Logtail::Events::Error}.
17
+ class ErrorEvent < Logtail::Integrations::Rack::Middleware
18
+ # We determine this when the app loads to avoid the overhead on a per request basis.
19
+ EXCEPTION_WRAPPER_TAKES_CLEANER = defined?(::ActionDispatch::ExceptionWrapper) &&
20
+ !::ActionDispatch::ExceptionWrapper.instance_methods.include?(:env)
21
+
22
+ def call(env)
23
+ begin
24
+ status, headers, body = @app.call(env)
25
+ rescue Exception => exception
26
+ Config.instance.logger.fatal do
27
+ backtrace = extract_backtrace(env, exception)
28
+ Events::Error.new(
29
+ name: exception.class.name,
30
+ error_message: exception.message,
31
+ backtrace: backtrace
32
+ )
33
+ end
34
+
35
+ raise exception
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ # Rails provides a backtrace cleaner, so we use it here.
42
+ def extract_backtrace(env, exception)
43
+ if defined?(::ActionDispatch::ExceptionWrapper)
44
+ wrapper = if EXCEPTION_WRAPPER_TAKES_CLEANER
45
+ request = Util::Request.new(env)
46
+ backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner")
47
+ ::ActionDispatch::ExceptionWrapper.new(backtrace_cleaner, exception)
48
+ else
49
+ ::ActionDispatch::ExceptionWrapper.new(env, exception)
50
+ end
51
+
52
+ trace = wrapper.application_trace
53
+ trace = wrapper.framework_trace if trace.empty?
54
+ trace
55
+ else
56
+ exception.backtrace
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,20 @@
1
+ module Logtail
2
+ # The Logtail Logger behaves exactly like the standard Ruby `::Logger`, except that it supports a
3
+ # transparent API for logging structured data and events.
4
+ #
5
+ # @example Basic logging
6
+ # logger.info "Payment rejected for customer #{customer_id}"
7
+ #
8
+ # @example Logging an event
9
+ # logger.info "Payment rejected", payment_rejected: {customer_id: customer_id, amount: 100}
10
+ class Logger < ::Logger
11
+ include ::ActiveSupport::LoggerThreadSafeLevel if defined?(::ActiveSupport::LoggerThreadSafeLevel)
12
+
13
+ if defined?(::ActiveSupport::LoggerSilence)
14
+ include ::ActiveSupport::LoggerSilence
15
+ elsif defined?(::LoggerSilence)
16
+ include ::LoggerSilence
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,12 @@
1
+ # The order is relevant
2
+ require "logtail-rails/overrides/active_support_3_tagged_logging"
3
+ require "logtail-rails/overrides/active_support_tagged_logging"
4
+ require "logtail-rails/overrides/active_support_buffered_logger"
5
+ require "logtail-rails/overrides/lograge"
6
+ require "logtail-rails/overrides/rails_stdout_logging"
7
+
8
+ module Logtail
9
+ # @private
10
+ module Overrides
11
+ end
12
+ end
@@ -0,0 +1,111 @@
1
+ # Please note, this patch is merely an upgrade, backporting improved tagged logging code
2
+ # from newer versions of Rails:
3
+ # https://github.com/rails/rails/blob/5-1-stable/activesupport/lib/active_support/tagged_logging.rb
4
+ # The behavior of tagged logging will not change in any way.
5
+ #
6
+ # This patch is specifically for Rails 3. The legacy approach to wrapping the logger in
7
+ # ActiveSupport::TaggedLogging is rather poor, hence the reason it was changed entirely
8
+ # for Rails 4 and 5. The problem is that ActiveSupport::TaggedLogging is a wrapping
9
+ # class that entirely redefines the public API for the logger. As a result, any deviations
10
+ # from this API in the logger are not exposed (such as accepting event data as a second argument).
11
+ # This is assuming, so we're fixing it here.
12
+
13
+ begin
14
+ require "active_support/tagged_logging"
15
+
16
+ # Instead of patching the class we're pulling the code from Rails master. This brings in
17
+ # a number of improvements while also addressing the issue above.
18
+ if ActiveSupport::TaggedLogging.instance_of?(Class)
19
+ ActiveSupport.send(:remove_const, :TaggedLogging)
20
+
21
+ require "active_support/core_ext/module/delegation"
22
+ require "active_support/core_ext/object/blank"
23
+ require "logger"
24
+
25
+ module ActiveSupport
26
+ # Wraps any standard Logger object to provide tagging capabilities.
27
+ #
28
+ # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
29
+ # logger.tagged('BCX') { logger.info 'Stuff' } # Logs "[BCX] Stuff"
30
+ # logger.tagged('BCX', "Jason") { logger.info 'Stuff' } # Logs "[BCX] [Jason] Stuff"
31
+ # logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff"
32
+ #
33
+ # This is used by the default Rails.logger as configured by Railties to make
34
+ # it easy to stamp log lines with subdomains, request ids, and anything else
35
+ # to aid debugging of multi-user production applications.
36
+ module TaggedLogging
37
+ module Formatter # :nodoc:
38
+ # This method is invoked when a log event occurs.
39
+ def call(severity, timestamp, progname, msg)
40
+ super(severity, timestamp, progname, "#{tags_text}#{msg}")
41
+ end
42
+
43
+ def tagged(*tags)
44
+ new_tags = push_tags(*tags)
45
+ yield self
46
+ ensure
47
+ pop_tags(new_tags.size)
48
+ end
49
+
50
+ def push_tags(*tags)
51
+ tags.flatten.reject(&:blank?).tap do |new_tags|
52
+ current_tags.concat new_tags
53
+ end
54
+ end
55
+
56
+ def pop_tags(size = 1)
57
+ current_tags.pop size
58
+ end
59
+
60
+ def clear_tags!
61
+ current_tags.clear
62
+ end
63
+
64
+ def current_tags
65
+ # We use our object ID here to avoid conflicting with other instances
66
+ thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}".freeze
67
+ Thread.current[thread_key] ||= []
68
+ end
69
+
70
+ def tags_text
71
+ tags = current_tags
72
+ if tags.any?
73
+ tags.collect { |tag| "[#{tag}] " }.join
74
+ end
75
+ end
76
+ end
77
+
78
+ # Simple formatter which only displays the message.
79
+ class SimpleFormatter < ::Logger::Formatter
80
+ # This method is invoked when a log event occurs
81
+ def call(severity, timestamp, progname, msg)
82
+ "#{String === msg ? msg : msg.inspect}\n"
83
+ end
84
+ end
85
+
86
+ def self.new(logger)
87
+ if logger.respond_to?(:formatter=) && logger.respond_to?(:formatter)
88
+ # Ensure we set a default formatter so we aren't extending nil!
89
+ logger.formatter ||= SimpleFormatter.new
90
+ logger.formatter.extend Formatter
91
+ end
92
+
93
+ logger.extend(self)
94
+ end
95
+
96
+ delegate :push_tags, :pop_tags, :clear_tags!, to: :formatter
97
+
98
+ def tagged(*tags)
99
+ formatter.tagged(*tags) { yield self }
100
+ end
101
+
102
+ def flush
103
+ clear_tags!
104
+ super if defined?(super)
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ rescue Exception
111
+ end
@@ -0,0 +1,22 @@
1
+ # This adds a #formatter and #formatter= method to the legacy ActiveSupport::BufferedLogger
2
+ # class. This bug was never resolved due to it being phased out past Rails >= 4.
3
+
4
+ begin
5
+ require "active_support/buffered_logger"
6
+
7
+ class ActiveSupport::BufferedLogger
8
+ def formatter
9
+ if @log.respond_to?(:formatter)
10
+ @log.formatter
11
+ end
12
+ end
13
+
14
+ def formatter=(value)
15
+ if @log.respond_to?(:formatter=)
16
+ @log.formatter = value
17
+ end
18
+ end
19
+ end
20
+
21
+ rescue Exception
22
+ end
@@ -0,0 +1,66 @@
1
+ # This is an override instead of an integration because without this Logtail would not
2
+ # work properly if ActiveSupport::TaggedLogging is used.
3
+ #
4
+ # This is called after 'active_support_3_tagged_logging' where the constant is loaded and
5
+ # replaced. I want to make sure we don't attempt to load it again undoing the patches
6
+ # applied there.
7
+ if defined?(ActiveSupport::TaggedLogging)
8
+ module Logtail
9
+ module Overrides
10
+ # @private
11
+ module ActiveSupportTaggedLogging
12
+ # @private
13
+ module FormatterMethods
14
+ def self.included(mod)
15
+ mod.module_eval do
16
+ alias_method :_logtail_original_push_tags, :push_tags
17
+ alias_method :_logtail_original_pop_tags, :pop_tags
18
+
19
+ def call(severity, timestamp, progname, msg)
20
+ if is_a?(Logtail::Logger::Formatter)
21
+ # Don't convert the message into a string
22
+ super(severity, timestamp, progname, msg)
23
+ else
24
+ super(severity, timestamp, progname, "#{tags_text}#{msg}")
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ # @private
32
+ module LoggerMethods
33
+ def self.included(klass)
34
+ klass.class_eval do
35
+ def add(severity, message = nil, progname = nil, &block)
36
+ if message.nil?
37
+ if block_given?
38
+ message = block.call
39
+ else
40
+ message = progname
41
+ progname = nil #No instance variable for this like Logger
42
+ end
43
+ end
44
+ if @logger.is_a?(Logtail::Logger)
45
+ @logger.add(severity, message, progname)
46
+ else
47
+ @logger.add(severity, "#{tags_text}#{message}", progname)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ if defined?(::ActiveSupport::TaggedLogging::Formatter)
55
+ if !::ActiveSupport::TaggedLogging::Formatter.include?(FormatterMethods)
56
+ ::ActiveSupport::TaggedLogging::Formatter.send(:include, FormatterMethods)
57
+ end
58
+ else
59
+ if !::ActiveSupport::TaggedLogging.include?(LoggerMethods)
60
+ ::ActiveSupport::TaggedLogging.send(:include, LoggerMethods)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,18 @@
1
+ # Logtail and lograge are not compatible installed together. Using lograge
2
+ # with the Logtail.com *service* is perfectly fine, but not with the Logtail *gem*.
3
+ #
4
+ # Logtail does ship with a {Logtail::Config#logrageify!} option that configures
5
+ # Logtail to behave similarly to Lograge (silencing various logs). Check out
6
+ # the aforementioned method or the README for info.
7
+ begin
8
+ require "lograge"
9
+
10
+ module Lograge
11
+ module_function
12
+
13
+ def setup(app)
14
+ return true
15
+ end
16
+ end
17
+ rescue Exception
18
+ end
@@ -0,0 +1,20 @@
1
+ # See https://github.com/heroku/rails_stdout_logging
2
+ # I have no idea why this library was created, but most Heroku / Rails apps use it.
3
+ # This library completely obliterates any logger configuration you set.
4
+ # So this patch fixes that.
5
+
6
+ begin
7
+ require "rails_stdout_logging"
8
+
9
+ module RailsStdoutLogging
10
+ class Rails2 < Rails
11
+ def self.set_logger
12
+ end
13
+ end
14
+
15
+ class Rails3 < Rails
16
+ def self.set_logger(config) end
17
+ end
18
+ end
19
+ rescue Exception
20
+ end
@@ -0,0 +1,59 @@
1
+ module Logtail
2
+ module Integrations
3
+ module Rails
4
+ # Disables the default rail's rack logging. Note, we cannot simply uninstall this rack
5
+ # middleware because rails couples this with ActiveSupport instrumentation. As such,
6
+ # we simply disable the logger and let our Rack middleware handle the logging.
7
+ #
8
+ # See: https://github.com/rails/rails/blob/80e66cc4d90bf8c15d1a5f6e3152e90147f00772/railties/lib/rails/rack/logger.rb#L34
9
+ #
10
+ # @private
11
+ class RackLogger < Integrator
12
+
13
+ # @private
14
+ module InstanceMethods
15
+ LOGGER = ::Logger.new(nil)
16
+
17
+ def self.included(klass)
18
+ klass.class_eval do
19
+ private
20
+
21
+ if ::Rails::VERSION::MAJOR == 3
22
+ # Rails 3.2 calls Rails.logger directly in call_app, so we
23
+ # will just replace it with a version that doesn't
24
+ def call_app(_, env)
25
+ # Put some space between requests in development logs.
26
+ if ::Rails.env.development?
27
+ ::Rails.logger.info ''
28
+ ::Rails.logger.info ''
29
+ end
30
+ @app.call(env)
31
+ ensure
32
+ ActiveSupport::LogSubscriber.flush_all!
33
+ end
34
+ end
35
+
36
+ # Rails > 3.2 uses a logger method. Muting logs is accomplished by
37
+ # passing a dummy logger instance with a nil log device.
38
+ def logger
39
+ LOGGER
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ def initialize
46
+ require "rails/rack/logger"
47
+ rescue LoadError => e
48
+ raise RequirementNotMetError.new(e.message)
49
+ end
50
+
51
+ def integrate!
52
+ return true if ::Rails::Rack::Logger.include?(InstanceMethods)
53
+
54
+ ::Rails::Rack::Logger.send(:include, InstanceMethods)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end