Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require "delegate"

module Google
module Logging
# A delegator that wraps a logger or its duck-type instance to add dynamic
# filtering based on GOOGLE_SDK_RUBY_LOGGING_GEMS environment variable, and
# error supression based on suppress_logger_errors parameter
class GoogleSdkLoggerDelegator < SimpleDelegator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider calling it "Proxy" or "Wrapper" instead of "Delegator".

# @private
# The environment variable that controls which gems have logging enabled.
ENV_VAR = "GOOGLE_SDK_RUBY_LOGGING_GEMS".freeze

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"LOGGING_GEMS_ENV_VAR"


# @private
# The full list of methods a compatible logger must implement.
REQUIRED_LOGGER_METHODS = [
:level, :level=, :sev_threshold, :sev_threshold=,
:progname, :progname=, :formatter, :formatter=,
:datetime_format, :datetime_format=,
:debug?, :info?, :warn?, :error?, :fatal?,
:debug!, :info!, :warn!, :error!, :fatal!,
:close, :reopen,
:<<, :add, :debug, :info, :warn, :error, :fatal, :unknown
].freeze

private_constant :ENV_VAR, :REQUIRED_LOGGER_METHODS

# @param gem_name [String] The name of the gem this logger is for.
# @param logger [Logger] The custom logger instance to wrap.
# @param suppress_logger_errors [Boolean] Whether to swallow exceptions in the wrapped logger.
def initialize gem_name, logger, suppress_logger_errors: true

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should make it same as Logger by giving it a list of Errors to reraise instead of a blanket variable.

unless is_logger_type? logger
raise ArgumentError, "A valid, compatible logger instance is required."
end

super logger
@gem_name = gem_name
@suppress_logger_errors = suppress_logger_errors
end

# @!group Logging Methods with Filtering

# Override each logging method to insert the filter check before
# delegating to the corresponding method on the wrapped logger.

def << msg
return true unless logging_enabled?
suppress_errors { super }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something like handle_logging_errors would be a better name, similar to handle_writing_errors in Logger's LogDevice

end

def add severity, message = nil, progname = nil
return true unless logging_enabled?
suppress_errors { super }
end

def debug message = nil, &block

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since it's Ruby 3.1 we can probably drop the block variable name and just use &

return true unless logging_enabled?
suppress_errors { super }
end

def info message = nil, &block
return true unless logging_enabled?
suppress_errors { super }
end

def warn message = nil, &block
return true unless logging_enabled?
suppress_errors { super }
end

def error message = nil, &block
return true unless logging_enabled?
suppress_errors { super }
end

def fatal message = nil, &block
return true unless logging_enabled?
suppress_errors { super }
end

def unknown message = nil, &block
return true unless logging_enabled?
suppress_errors { super }
end
# @!endgroup

private

def suppress_errors
yield
rescue StandardError
raise unless @suppress_logger_errors
true
end

def is_logger_type? logger
return false if logger.nil?
REQUIRED_LOGGER_METHODS.all? { |m| logger.respond_to? m }
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a class-level (static) method


# Checks if logging is enabled for the current gem.
def logging_enabled?
current_env_var = ENV[ENV_VAR]

# Return the cached result if the ENV var hasn't changed.
if @cached_env_var_value == current_env_var
return @cached_logging_enabled_result

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cached instance-level variables should be explicitly initialized in the constructor.

end

# If the ENV var has changed (or on the first run), re-evaluate.
new_result = false
begin
if current_env_var == "none"
new_result = false
elsif current_env_var == "all"
new_result = true
else
packages = current_env_var&.gsub(/\s+/, "")&.split(",") || []
new_result = packages.include? @gem_name
end
rescue StandardError => e
Kernel.warn "Failed to determine logging configuration for #{@gem_name}. " \
"Logging disabled. Error: #{e.class}: #{e.message}"
new_result = false
end

# Update the cache with the new values.
@cached_env_var_value = current_env_var
@cached_logging_enabled_result = new_result

new_result
end
end
end
end
2 changes: 2 additions & 0 deletions google-logging-utils/lib/google/logging/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
require "google/logging/message"
require "google/logging/source_location"
require "google/logging/structured_formatter"
require "google/logging/google_sdk_filtered_logger"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

require "google/logging/google_sdk_logger_delegator"

##
# Google products namespace
Expand Down