debugbundle 1.4.0 → 1.4.1

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: b248dcbd96eccb5c6feb9720d0d68d91377469fc2a69e722f5ee665ac6b963b9
4
- data.tar.gz: dd58681087f077b40b9a1d9efd6608f0f8ef9680bf1ca08cca3a382bd07811a1
3
+ metadata.gz: cae31875b7ffe9d2005b62f2107fb4810061ec4362e7bf48c4dd521e43d415f6
4
+ data.tar.gz: 2e005a0112e3197aa9b19fed791b71cea7dbf1eefcfc6edebc5a2ce7c764f3d0
5
5
  SHA512:
6
- metadata.gz: e2c64054d0bee13e76b6e231102e9d8b13cd0806c03a82c7ba09b24e6baf8195ce2163022d7b7855041fd8939fb889dc0f3559f03188d340db793b7f320361d5
7
- data.tar.gz: 02d6185279a906a1d3ce16bca8a6bc14359eecee788f89f5e8cd5c2c5844a2390e08e403ed9e750362fdf89c9152ca246930f469622898f7b97bdcfc72723094
6
+ metadata.gz: e2f99cad665ebbcd10ea0965ed97f16c1930567344848ead03aa2ff9d1ac297bf9260ba2915a72454d6843e2805e41ebc648525ab0250077c29ea3f8897a291a
7
+ data.tar.gz: 25a171fc0e6cd96c74a8cc7a85fde1dbfe52837d4c46cef69926e2b4c736df49912ffce294613b3c7738d4f6f7b8cdc2f4121f6898a1563c4e7056f237d52a55
data/Makefile CHANGED
@@ -15,6 +15,12 @@ BUNDLE_ENV = BUNDLE_GEMFILE="$(BUNDLE_GEMFILE)"
15
15
  .PHONY: bundle-install test lint build shell compat-rack compat-rails compat-sidekiq compat
16
16
  .PHONY: smoke
17
17
  .PHONY: smoke-published
18
+ .PHONY: test-focused check-docker
19
+ check-docker:
20
+ docker run --rm -v "$(CURDIR):$(WORKDIR)" -w "$(WORKDIR)" $(RUBY_IMAGE) sh -lc 'BUNDLE_PATH=vendor/bundle bundle exec rubocop && BUNDLE_PATH=vendor/bundle bundle exec rspec && gem build debugbundle.gemspec'
21
+
22
+ test-focused:
23
+ docker run --rm -v "$(CURDIR):$(WORKDIR)" -w "$(WORKDIR)" $(RUBY_IMAGE) sh -lc 'BUNDLE_PATH=vendor/bundle SIMPLECOV_MINIMUM_COVERAGE=0 bundle exec rspec $(TEST_FILES)'
18
24
 
19
25
  bundle-install:
20
26
  $(DOCKER_RUN) sh -lc "$(BUNDLE_ENV) bundle config set path vendor/bundle && $(BUNDLE_ENV) bundle install"
data/README.md CHANGED
@@ -4,6 +4,10 @@ Ruby SDK for DebugBundle.
4
4
 
5
5
  Use this gem to capture Ruby backend exceptions, request metadata, structured logs, runtime context, probe data, and browser relay traffic. It supports a singleton facade plus instance clients for Rack, Rails, Sidekiq, and explicit Ruby instrumentation.
6
6
 
7
+ ## Automatic capture and application filtering
8
+
9
+ `capture_logger(logger)` respects the logger level, Rails silence and disabled output. Lazy message blocks are evaluated by the native logger: zero times when suppressed and once when accepted. Native return values and application block exceptions are preserved. DebugBundle applies its own configured minimum capture level after the logger accepts a message.
10
+
7
11
  ## Installation
8
12
 
9
13
  ```ruby
@@ -314,7 +318,7 @@ This repository also ships a clean-install app-driven smoke harness that validat
314
318
 
315
319
  ```sh
316
320
  make smoke
317
- make smoke-published VERSION=1.4.0
321
+ make smoke-published VERSION=1.4.1
318
322
  ```
319
323
 
320
324
  `make smoke` builds the gem, installs it into a fresh RubyGems home, drives a Rack request plus a browser relay batch through the public SDK surface, validates event envelope shape, and confirms the mock ingestion endpoint receives the expected service, environment, SDK metadata, and correlation fields.
@@ -16,24 +16,23 @@ module DebugBundle
16
16
 
17
17
  def self.install_stdlib_logger(logger, client:)
18
18
  interceptor = Module.new do
19
- define_method(:add) do |severity, message = nil, progname = nil, &block|
20
- was_capturing = Thread.current[RECURSION_GUARD_KEY]
21
- unless was_capturing
22
- Thread.current[RECURSION_GUARD_KEY] = true
23
- resolved_message = message
24
- resolved_message = block.call if resolved_message.nil? && block
25
- resolved_message = progname if resolved_message.nil?
19
+ %i[add log].each do |method_name|
20
+ define_method(method_name) do |severity, message = nil, progname = nil, &block|
21
+ normalized_severity = severity || ::Logger::UNKNOWN
22
+ # Match Logger's early return, including Rails' thread-local level.
23
+ disabled = normalized_severity < level || (is_a?(::Logger) && instance_variable_get(:@logdev).nil?)
24
+ next super(severity, message, progname, &block) if disabled || Thread.current[RECURSION_GUARD_KEY]
26
25
 
27
- client.capture_log(
28
- resolved_message.to_s,
29
- level: LOGGER_LEVEL_NAMES.fetch(severity || ::Logger::UNKNOWN, :warning),
30
- context: { logger_name: logger.progname }
31
- )
26
+ resolved_message = message
27
+ resolved_message = progname.nil? ? self.progname : progname if message.nil?
28
+ result = if message.nil? && block
29
+ super(severity, message, progname) { resolved_message = block.call }
30
+ else
31
+ super(severity, message, progname)
32
+ end
33
+ DebugBundle::Logging.capture_stdlib(client, resolved_message, normalized_severity, self.progname)
34
+ result
32
35
  end
33
-
34
- super(severity, message, progname, &block)
35
- ensure
36
- Thread.current[RECURSION_GUARD_KEY] = was_capturing
37
36
  end
38
37
  end
39
38
 
@@ -41,6 +40,21 @@ module DebugBundle
41
40
  interceptor
42
41
  end
43
42
 
43
+ def self.capture_stdlib(client, message, severity, progname)
44
+ was_capturing = Thread.current[RECURSION_GUARD_KEY]
45
+ Thread.current[RECURSION_GUARD_KEY] = true
46
+ client.capture_log(
47
+ message.to_s,
48
+ level: LOGGER_LEVEL_NAMES.fetch(severity, :warning),
49
+ context: { logger_name: progname }
50
+ )
51
+ rescue StandardError
52
+ # SDK failures must not change the result of the application's logger.
53
+ nil
54
+ ensure
55
+ Thread.current[RECURSION_GUARD_KEY] = was_capturing
56
+ end
57
+
44
58
  def self.install_semantic_logger(client:)
45
59
  return nil unless defined?(::SemanticLogger)
46
60
  return nil unless ::SemanticLogger.respond_to?(:add_appender)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DebugBundle
4
- VERSION = '1.4.0'
4
+ VERSION = '1.4.1'
5
5
  end
@@ -4,6 +4,90 @@ require 'spec_helper'
4
4
  require 'stringio'
5
5
 
6
6
  RSpec.describe 'stdlib logger integration' do
7
+ def instrument_logger(logger, &capture)
8
+ client = Object.new
9
+ client.define_singleton_method(:capture_log, &capture)
10
+ DebugBundle::Logging.install_stdlib_logger(logger, client: client)
11
+ end
12
+
13
+ it 'respects the current logger level without evaluating suppressed blocks' do
14
+ output = StringIO.new
15
+ logger = Logger.new(output, level: Logger::ERROR)
16
+ captured = []
17
+ instrument_logger(logger) { |message, **_options| captured << message }
18
+ evaluations = 0
19
+ result = logger.warn do
20
+ evaluations += 1
21
+ 'suppressed'
22
+ end
23
+ expect(result).to be(true)
24
+ expect(captured).to be_empty
25
+ expect(evaluations).to eq(0)
26
+ logger.level = Logger::WARN
27
+ logger.warn do
28
+ evaluations += 1
29
+ 'accepted'
30
+ end
31
+ expect(evaluations).to eq(1)
32
+ expect(captured).to eq(['accepted'])
33
+ expect(output.string).to include('accepted')
34
+ expect(output.string).not_to include('suppressed')
35
+ end
36
+
37
+ it 'preserves logger message and progname semantics and evaluates accepted blocks once' do
38
+ output = StringIO.new
39
+ logger = Logger.new(output, progname: 'default-name')
40
+ captured = []
41
+ instrument_logger(logger) { |message, **_options| captured << message }
42
+ logger.add(Logger::WARN)
43
+ logger.add(Logger::WARN, nil, 'message-as-progname')
44
+ logger.add(nil, 'unknown-severity')
45
+ evaluations = 0
46
+ logger.warn('explicit-name') do
47
+ evaluations += 1
48
+ "block-#{evaluations}"
49
+ end
50
+ expect(captured).to eq(%w[default-name message-as-progname unknown-severity block-1])
51
+ expect(evaluations).to eq(1)
52
+ expect(output.string).to include('explicit-name: block-1')
53
+ end
54
+
55
+ it 'does not evaluate blocks or capture when the logger has no output device' do
56
+ logger = Logger.new(nil)
57
+ instrument_logger(logger) { raise 'should not capture' }
58
+ expect(logger.warn { raise 'should not evaluate' }).to be(true)
59
+ end
60
+
61
+ it 'captures the log alias and preserves false messages and Rails silence' do
62
+ require 'active_support'
63
+ require 'active_support/logger'
64
+ output = StringIO.new
65
+ logger = ActiveSupport::Logger.new(output)
66
+ captured = []
67
+ instrument_logger(logger) { |message, **_options| captured << message }
68
+ logger.silence(Logger::ERROR) { logger.warn { raise 'suppressed block' } }
69
+ logger.log(Logger::WARN, false)
70
+ expect(captured).to eq(['false'])
71
+ expect(output.string).to include('false')
72
+ end
73
+
74
+ it 'isolates capture failures and recursion while preserving application logging failures' do
75
+ output = StringIO.new
76
+ logger = Logger.new(output)
77
+ calls = 0
78
+ instrument_logger(logger) do |_message, **_options|
79
+ calls += 1
80
+ logger.warn('nested')
81
+ raise 'capture failed'
82
+ end
83
+ expect { logger.warn('outer') }.not_to raise_error
84
+ expect(calls).to eq(1)
85
+ expect(output.string).to include('outer', 'nested')
86
+ expect { logger.warn { raise 'application failure' } }.to raise_error('application failure')
87
+ expect { logger.warn('after failure') }.not_to raise_error
88
+ expect(calls).to eq(2)
89
+ end
90
+
7
91
  it 'captures log events without changing logger output' do
8
92
  transport_events = []
9
93
  transport = Class.new do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: debugbundle
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - DebugBundle
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-09-12 00:00:00.000000000 Z
10
+ date: 2026-09-15 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64