Skip to content

Commit

Permalink
feat: Support ActiveSupport::BroadcastLogger
Browse files Browse the repository at this point in the history
Rails 7.1+ uses ActiveSupport::BroadcastLogger. This needs to protect
against emitting duplicate logs in a different way than
ActiveSupport::Logger.broadcast.

Emits the log record for the first logger in the broadcast,
skip the others. Reset everything at the end of the method call.
  • Loading branch information
kaylareopelle committed Sep 17, 2024
1 parent 7f8dce1 commit e5cc573
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 2 deletions.
2 changes: 1 addition & 1 deletion instrumentation/logger/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ group :test do
end

# Temporary for testing, Appraisal does not work with gems installed from git source
gem 'rails', '~> 7.0.0'
gem 'rails', '~> 7.1.0'
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base

def patch
::Logger.prepend(Patches::Logger)
active_support_broadcast_logger_patch
active_support_patch
end

Expand All @@ -37,11 +38,18 @@ def require_dependencies
end

def active_support_patch
return unless defined?(::ActiveSupport::Logger)
return unless defined?(::ActiveSupport::Logger) && !defined?(::ActiveSupport::BroadcastLogger)

require_relative 'patches/active_support_logger'
::ActiveSupport::Logger.singleton_class.prepend(Patches::ActiveSupportLogger)
end

def active_support_broadcast_logger_patch
return unless defined?(::ActiveSupport::BroadcastLogger)

require_relative 'patches/active_support_broadcast_logger'
::ActiveSupport::BroadcastLogger.prepend(Patches::ActiveSupportBroadcastLogger)
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

# Copyright The OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

module OpenTelemetry
module Instrumentation
module Logger
module Patches
# Patches for the ActiveSupport::BroadcastLogger class included in Rails 7.1+
module ActiveSupportBroadcastLogger
def add(*args)
emit_one_broadcast(*args) { super }
end

def debug(*args)
emit_one_broadcast(*args) { super }
end

def info(*args)
emit_one_broadcast(*args) { super }
end

def warn(*args)
emit_one_broadcast(*args) { super }
end

def error(*args)
emit_one_broadcast(*args) { super }
end

def fatal(*args)
emit_one_broadcast(*args) { super }
end

def unknown(*args)
emit_one_broadcast(*args) { super }
end

private

# Emit logs from only one of the loggers in the broadcast.
# Set @skip_instrumenting to `true` to the rest of the loggers before emitting the logs.
# Set @skip_instrumenting to `false` after the log is emitted.
def emit_one_broadcast(*args)
broadcasts[1..-1].each { |broadcasted_logger| broadcasted_logger.instance_variable_set(:@skip_instrumenting, true) }
yield
broadcasts.each { |broadcasted_logger| broadcasted_logger.instance_variable_set(:@skip_instrumenting, false) }
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# frozen_string_literal: true

# Copyright The OpenTelemetry Authors
#
# SPDX-License-Identifier: Apache-2.0

require 'test_helper'

require_relative '../../../../../lib/opentelemetry/instrumentation/logger/patches/active_support_broadcast_logger'

describe OpenTelemetry::Instrumentation::Logger::Patches::ActiveSupportBroadcastLogger do
let(:instrumentation) { OpenTelemetry::Instrumentation::Logger::Instrumentation.instance }
let(:logger) { Logger.new(LOG_STREAM) }
let(:logger2) { Logger.new(BROADCASTED_STREAM) }
let(:broadcast) { ActiveSupport::BroadcastLogger.new(logger, logger2) }

before do
skip unless defined?(::ActiveSupport::BroadcastLogger)
EXPORTER.reset
instrumentation.install
end

after { instrumentation.instance_variable_set(:@installed, false) }

describe '#add' do
it 'emits the log to the broadcasted loggers' do
body = "Ground control to Major Tom"
broadcast.add(Logger::DEBUG, body)

assert_includes(LOG_STREAM.string, body)
assert_includes(BROADCASTED_STREAM.string, body)
end

it 'emits only one OpenTelemetry log record' do
body = "Wake up, you sleepyhead"
broadcast.add(Logger::DEBUG, body)
log_records = EXPORTER.emitted_log_records

assert_equal 1, log_records.size
assert_equal 'DEBUG', log_records.first.severity_text
assert_equal body, log_records.first.body
end
end

describe '#unknown' do
it 'emits the log to the broadcasted loggers' do
body = "I know when to go out"
broadcast.unknown(body)

assert_includes(LOG_STREAM.string, body)
assert_includes(BROADCASTED_STREAM.string, body)
end

it 'emits only one OpenTelemetry log record' do
body = "You've got your mother in a whirl"
broadcast.unknown(body)

log_records = EXPORTER.emitted_log_records

assert_equal 1, log_records.size
assert_equal 'ANY', log_records.first.severity_text
assert_equal body, log_records.first.body
end
end

%w[debug info warn error fatal].each do |severity|
describe "##{severity}" do
it 'emits the log to the broadcasted loggers' do
body = "Still don't know what I was waiting for...#{rand(7)}"
broadcast.send(severity.to_sym, body)

assert_includes(LOG_STREAM.string, body)
assert_includes(BROADCASTED_STREAM.string, body)
end

it 'emits only one OpenTelemetry log record' do
body = "They pulled in just behind the bridge...#{rand(7)}"
broadcast.send(severity.to_sym, body)

log_records = EXPORTER.emitted_log_records

assert_equal 1, log_records.size
assert_equal severity.upcase, log_records.first.severity_text
assert_equal body, log_records.first.body
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
let(:broadcasted_logger) { ActiveSupport::Logger.new(BROADCASTED_STREAM) }

before do
skip unless defined?(::ActiveSupport::Logger) && !defined?(::ActiveSupport::BroadcastLogger)
EXPORTER.reset
Rails.logger = main_logger.extend(ActiveSupport::Logger.broadcast(broadcasted_logger))
instrumentation.install
Expand Down

0 comments on commit e5cc573

Please sign in to comment.