logtail-rails 0.2.13 → 0.2.15

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 199f2f2bf589ef69bbe057b16224aa71999e45e991e30eeda9533f11ce233c79
4
- data.tar.gz: 26603038e5aa56d21112ce259084999f4cf29df2eab4b1b411f13d0cef6ae246
3
+ metadata.gz: 69e0f9b99939d4b4ab4c3b5b185f09fcc824120618371f8881e10b671b49a218
4
+ data.tar.gz: 47c66f5323fbab6dd1b1fe2e559cce6097c13c540cd5ce7b5c2b8cb857a65b4b
5
5
  SHA512:
6
- metadata.gz: fb36a1279648ec39acebf782e59b26441824b1222152ba76c44cc7454ae0593eaece3beca230d3e305ae803e4215c958a3f51e5cadb962ee3592060b45ed8357
7
- data.tar.gz: e267ac9f6488ec482c1b84035cb119e6062f3f335e03bedb8eff3cae3fd318a5a5e61dacc560acca8eb53870080a89add3006b7299454b4b2025d1b738d2a4a4
6
+ metadata.gz: 3378d7cf4c494bc061759ca956239472c8ad9d1f4066d47cba0e0ef7c975ea50e0be2ecc91ec9a778a9d00d78e3f79e8347092505b5a4b8a03de03c902e7fa67
7
+ data.tar.gz: 6b873bad4830cfe29f52958d5b9493e0c3b0c7ddb634eabe3f6719860722e947548f710ac9b99aecd0e997105a3a1350933df4e85436889b9c0e3db08296a5f5
data/README.md CHANGED
@@ -14,6 +14,8 @@ Experience SQL-compatible structured log management based on ClickHouse. [Learn
14
14
 
15
15
  [Getting started ⇗](https://betterstack.com/docs/logs/ruby-and-rails/#logging-from-rails)
16
16
 
17
+ To turn the integration off in an environment, e.g. development or test, set `config.logtail.enabled = false` in `config/environments/<environment>.rb`. Rails then logs to `log/<environment>.log` as usual, even if `config/application.rb` creates the Better Stack logger.
18
+
17
19
  ## Need help?
18
20
  Please let us know at [hello@betterstack.com](mailto:hello@betterstack.com). We're happy to help!
19
21
 
@@ -76,9 +76,12 @@ module Logtail
76
76
  super
77
77
 
78
78
  if ::Rails::VERSION::MAJOR > 7 || ::Rails::VERSION::MAJOR == 7 && ::Rails::VERSION::MINOR >= 1
79
- # Clean extra listeners subscribed in parent's attach_to method
80
- ::ActiveSupport::Notifications.notifier.listeners_for("render_template.action_view")
81
- .concat(::ActiveSupport::Notifications.notifier.listeners_for("render_layout.action_view")).flatten
79
+ # Clean extra listeners subscribed in parent's attach_to method. Since Rails 7.2 they count as
80
+ # silenced while ActionView::Base.logger is nil, as it is at boot, and listeners_for skips those.
81
+ # all_listeners_for returns the notifier's cached array, so don't concat onto it.
82
+ notifier = ::ActiveSupport::Notifications.notifier
83
+ %w(render_template.action_view render_layout.action_view)
84
+ .flat_map { |name| notifier.respond_to?(:all_listeners_for) ? notifier.all_listeners_for(name) : notifier.listeners_for(name) }
82
85
  .filter { |listener| listener.delegate.class == ::ActionView::LogSubscriber::Start }
83
86
  .each { |listener| ActiveSupport::Notifications.unsubscribe(listener) }
84
87
  end
@@ -10,3 +10,22 @@ Logtail::Config.instance.define_singleton_method(:logrageify!) do
10
10
  integrations.active_record.silence = true
11
11
  integrations.rack.http_events.collapse_into_single_event = true
12
12
  end
13
+
14
+ module Logtail
15
+ class Config
16
+ # Turns the Rails integration on or off, it's on by default. Set it in `config/application.rb`
17
+ # or `config/environments/*.rb`, see {Logtail::Frameworks::Rails::Railtie} for what it turns off.
18
+ #
19
+ # @example Keep the standard Rails logging in development
20
+ # # config/environments/development.rb
21
+ # config.logtail.enabled = false
22
+ def enabled=(value)
23
+ @enabled = value
24
+ end
25
+
26
+ # Accessor method for {#enabled=}
27
+ def enabled?
28
+ @enabled != false
29
+ end
30
+ end
31
+ end
@@ -18,19 +18,15 @@ module Logtail
18
18
  # We determine this when the app loads to avoid the overhead on a per request basis.
19
19
  EXCEPTION_WRAPPER_TAKES_CLEANER = defined?(::ActionDispatch::ExceptionWrapper) &&
20
20
  !::ActionDispatch::ExceptionWrapper.instance_methods.include?(:env)
21
+ # config.action_dispatch.log_rescued_responses (Rails 7.0+), as ActionDispatch::DebugExceptions
22
+ # reads it. This gem silences its logging and logs the exception here instead.
23
+ LOG_RESCUED_RESPONSES_KEY = "action_dispatch.log_rescued_responses".freeze
21
24
 
22
25
  def call(env)
23
26
  begin
24
27
  status, headers, body = @app.call(env)
25
28
  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
29
+ log_exception(env, exception)
34
30
 
35
31
  raise exception
36
32
  end
@@ -38,6 +34,30 @@ module Logtail
38
34
 
39
35
  private
40
36
 
37
+ # Never raises, so that the exception of the app is the one that propagates.
38
+ def log_exception(env, exception)
39
+ return if !log_exception?(env, exception)
40
+
41
+ Config.instance.logger.fatal do
42
+ backtrace = extract_backtrace(env, exception)
43
+ Events::Error.new(
44
+ name: exception.class.name,
45
+ error_message: exception.message,
46
+ backtrace: backtrace
47
+ )
48
+ end
49
+ rescue StandardError => e
50
+ Config.instance.debug { "#{self.class.name} could not log #{exception.class}: #{e.inspect}" }
51
+ end
52
+
53
+ # Like DebugExceptions, skip rescued responses such as ActiveRecord::RecordNotFound when
54
+ # log_rescued_responses is false. Older Rails versions don't set it and log every exception.
55
+ def log_exception?(env, exception)
56
+ return true if !env.key?(LOG_RESCUED_RESPONSES_KEY) || env[LOG_RESCUED_RESPONSES_KEY]
57
+
58
+ !::ActionDispatch::ExceptionWrapper.rescue_responses.key?(exception.class.name)
59
+ end
60
+
41
61
  # Rails provides a backtrace cleaner, so we use it here.
42
62
  def extract_backtrace(env, exception)
43
63
  if defined?(::ActionDispatch::ExceptionWrapper)
@@ -6,7 +6,7 @@ module Logtail
6
6
  # them with all their data to Rails.logger, which sends them to Better Stack.
7
7
  class EventLogSubscriber
8
8
  # Rails logger instance
9
- attr_reader :logger
9
+ attr_accessor :logger
10
10
 
11
11
  # Log level to use for logging events
12
12
  mattr_accessor :log_level, default: :info
@@ -14,6 +14,17 @@ module Logtail
14
14
  # Allows to disable the subscriber
15
15
  mattr_accessor :enabled, default: true
16
16
 
17
+ # Subscribes to Rails.event once. Later calls only hand the events to the new logger, so that
18
+ # calling Logtail::Logger.create_default_logger again doesn't log every event twice.
19
+ def self.subscribe(logger)
20
+ if @subscriber
21
+ @subscriber.logger = logger
22
+ else
23
+ @subscriber = new(logger)
24
+ ::Rails.event.subscribe(@subscriber)
25
+ end
26
+ end
27
+
17
28
  # Initialize the subscriber with a logger instance
18
29
  def initialize(logger)
19
30
  @logger = logger
@@ -43,14 +54,16 @@ module Logtail
43
54
 
44
55
  private
45
56
 
46
- # Rails 8.2+ routes the framework's own instrumentation (controller calls, SQL queries,
47
- # template renders, ...) through Rails.event as well. The Rails log subscribers, and ours
48
- # replacing them, already log those, so only the application's own events are forwarded.
57
+ # Rails 8.1+ reports its own instrumentation (controller calls, SQL queries, template
58
+ # renders, ...) to Rails.event as well, through the ActiveSupport::StructuredEventSubscriber
59
+ # it attaches for each of its components. The Rails log subscribers, and ours replacing
60
+ # them, already log those, so only the application's own events are forwarded.
49
61
  def framework_event?(event)
50
- return false unless defined?(::ActiveSupport::EventReporter::LogSubscriber)
62
+ return false unless defined?(::ActiveSupport::StructuredEventSubscriber)
51
63
 
52
- ::ActiveSupport::EventReporter::LogSubscriber.descendants.any? do |log_subscriber|
53
- event[:name].start_with?("#{log_subscriber.namespace}.")
64
+ ::ActiveSupport::StructuredEventSubscriber.descendants.any? do |bridge|
65
+ # The namespace attach_to recorded is a private reader on ActiveSupport::Subscriber
66
+ event[:name].start_with?("#{bridge.send(:namespace)}.")
54
67
  end
55
68
  end
56
69
 
@@ -49,6 +49,10 @@ module Logtail
49
49
  def self.create_logger(*io_devices_and_loggers)
50
50
  logger = Logtail::Logger.new(*io_devices_and_loggers)
51
51
 
52
+ # Rails applies config.log_level to Rails.logger while booting, but not to a logger added with broadcast_to later
53
+ log_level = Rails.application.config.log_level if ENV['LOG_LEVEL'].blank? && Rails.application
54
+ logger.level = ::ActiveSupport::Logger.const_get(log_level.to_s.upcase) if log_level
55
+
52
56
  tagged_logging_supported = Rails::VERSION::MAJOR >= 7 || Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR >= 1
53
57
  logger = ::ActiveSupport::TaggedLogging.new(logger) if tagged_logging_supported
54
58
 
@@ -79,7 +83,7 @@ module Logtail
79
83
  end
80
84
 
81
85
  # For Rails 8.1 and above, subscribe to the event system
82
- Rails.event.subscribe(Logtail::Integrations::Rails::EventLogSubscriber.new(logger)) if Rails.respond_to?(:event)
86
+ Logtail::Integrations::Rails::EventLogSubscriber.subscribe(logger) if Rails.respond_to?(:event)
83
87
 
84
88
  logger
85
89
  end
@@ -13,6 +13,26 @@ module Logtail
13
13
  Logtail::Config.instance.logger = Proc.new { ::Rails.logger }
14
14
  end
15
15
 
16
+ # `config.logtail.enabled = false` turns the integration off. The environment files are loaded
17
+ # by now, and Rails hasn't set up Rails.logger from config.logger yet.
18
+ initializer(:logtail_enabled, after: :load_environment_config, before: :initialize_logger) do |app|
19
+ next if app.config.logtail.enabled?
20
+
21
+ # Before the :logtail initializer below picks the middlewares
22
+ Integrations::Rails.enabled = false
23
+
24
+ # The setup docs create the logger in config/application.rb, i.e. in every environment. Log to the
25
+ # default log file instead, with a Logtail::Logger so that `logger.info("message", key: value)` works.
26
+ if app.config.logger.is_a?(Logtail::Logger)
27
+ log_file = app.paths["log"].first
28
+ FileUtils.mkdir_p(File.dirname(log_file))
29
+ # Opened like Rails opens it: Logger only writes its "# Logfile created" header into files it creates
30
+ file = File.open(log_file, "a")
31
+ file.binmode
32
+ app.config.logger = Logtail::Logger.create_logger(file)
33
+ end
34
+ end
35
+
16
36
  # Must be loaded after initializers so that we respect any Logtail configuration set
17
37
  initializer(:logtail, before: :build_middleware_stack, after: :load_config_initializers) do
18
38
  Integrations::Rails.integrate!
@@ -1,7 +1,7 @@
1
1
  module Logtail
2
2
  module Integrations
3
3
  module Rails
4
- VERSION = "0.2.13"
4
+ VERSION = "0.2.15"
5
5
  end
6
6
  end
7
7
  end
data/lib/logtail-rails.rb CHANGED
@@ -39,6 +39,12 @@ module Logtail
39
39
  def self.integrate!
40
40
  return false if !enabled?
41
41
 
42
+ # Log the status Rails responds with when the app raises, the way ShowExceptions
43
+ # determines it: from config.action_dispatch.rescue_responses, 500 by default.
44
+ Logtail::Integrations::Rack::HTTPEvents.status_for_exception = lambda do |exception|
45
+ ::ActionDispatch::ExceptionWrapper.new(nil, exception).status_code
46
+ end
47
+
42
48
  ActionController.integrate!
43
49
  ActionDispatch.integrate!
44
50
  ActionView.integrate!
@@ -27,8 +27,8 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
29
 
30
- spec.add_runtime_dependency "logtail", "~> 0.1", ">= 0.1.14"
31
- spec.add_runtime_dependency "logtail-rack", "~> 0.1"
30
+ spec.add_runtime_dependency "logtail", "~> 0.1", ">= 0.1.21"
31
+ spec.add_runtime_dependency "logtail-rack", "~> 0.2", ">= 0.2.9"
32
32
 
33
33
  spec.add_runtime_dependency 'activerecord', '>= 5.0.0'
34
34
  spec.add_runtime_dependency 'railties', '>= 5.0.0'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logtail-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.13
4
+ version: 0.2.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Better Stack
@@ -18,7 +18,7 @@ dependencies:
18
18
  version: '0.1'
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: 0.1.14
21
+ version: 0.1.21
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -28,21 +28,27 @@ dependencies:
28
28
  version: '0.1'
29
29
  - - ">="
30
30
  - !ruby/object:Gem::Version
31
- version: 0.1.14
31
+ version: 0.1.21
32
32
  - !ruby/object:Gem::Dependency
33
33
  name: logtail-rack
34
34
  requirement: !ruby/object:Gem::Requirement
35
35
  requirements:
36
36
  - - "~>"
37
37
  - !ruby/object:Gem::Version
38
- version: '0.1'
38
+ version: '0.2'
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 0.2.9
39
42
  type: :runtime
40
43
  prerelease: false
41
44
  version_requirements: !ruby/object:Gem::Requirement
42
45
  requirements:
43
46
  - - "~>"
44
47
  - !ruby/object:Gem::Version
45
- version: '0.1'
48
+ version: '0.2'
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: 0.2.9
46
52
  - !ruby/object:Gem::Dependency
47
53
  name: activerecord
48
54
  requirement: !ruby/object:Gem::Requirement