diff --git a/instrumentation/logger/Gemfile b/instrumentation/logger/Gemfile index c292f236c..82e256731 100644 --- a/instrumentation/logger/Gemfile +++ b/instrumentation/logger/Gemfile @@ -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' diff --git a/instrumentation/logger/lib/opentelemetry/instrumentation/logger/instrumentation.rb b/instrumentation/logger/lib/opentelemetry/instrumentation/logger/instrumentation.rb index 70ed5479d..44fc83d8c 100644 --- a/instrumentation/logger/lib/opentelemetry/instrumentation/logger/instrumentation.rb +++ b/instrumentation/logger/lib/opentelemetry/instrumentation/logger/instrumentation.rb @@ -29,6 +29,7 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base def patch ::Logger.prepend(Patches::Logger) + active_support_broadcast_logger_patch active_support_patch end @@ -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 diff --git a/instrumentation/logger/lib/opentelemetry/instrumentation/logger/patches/active_support_broadcast_logger.rb b/instrumentation/logger/lib/opentelemetry/instrumentation/logger/patches/active_support_broadcast_logger.rb new file mode 100644 index 000000000..c5623ebca --- /dev/null +++ b/instrumentation/logger/lib/opentelemetry/instrumentation/logger/patches/active_support_broadcast_logger.rb @@ -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 diff --git a/instrumentation/logger/test/opentelemetry/instrumentation/logger/patches/active_support_broadcast_logger_test.rb b/instrumentation/logger/test/opentelemetry/instrumentation/logger/patches/active_support_broadcast_logger_test.rb new file mode 100644 index 000000000..25e9c2738 --- /dev/null +++ b/instrumentation/logger/test/opentelemetry/instrumentation/logger/patches/active_support_broadcast_logger_test.rb @@ -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 diff --git a/instrumentation/logger/test/opentelemetry/instrumentation/logger/patches/active_support_logger_test.rb b/instrumentation/logger/test/opentelemetry/instrumentation/logger/patches/active_support_logger_test.rb index e67588569..9bd96ff3e 100644 --- a/instrumentation/logger/test/opentelemetry/instrumentation/logger/patches/active_support_logger_test.rb +++ b/instrumentation/logger/test/opentelemetry/instrumentation/logger/patches/active_support_logger_test.rb @@ -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