Skip to content

Commit

Permalink
updates as per new design
Browse files Browse the repository at this point in the history
  • Loading branch information
ohmayr committed Nov 22, 2024
1 parent 08ef13a commit f8cb7fc
Showing 1 changed file with 58 additions and 69 deletions.
127 changes: 58 additions & 69 deletions google/api_core/client_logging.py
Original file line number Diff line number Diff line change
@@ -1,84 +1,73 @@
import logging
import json
import re
import os

# NOTE: We can essentially just use `json.dumps` and make structured logging our default format.
class DefaultFormatter(logging.Formatter):
def format(self, record):
# Base format with time, severity, and main message
# base_message = f"{self.formatTime(record)} - {record.levelname} - {record.name} - {record.getMessage()}"
base_message = super().format(record)

# Add any extra fields below:
extra_info = []
if hasattr(record, 'client_id'):
extra_info.append(f"client_id={record.client_id}")
if hasattr(record, 'response_id'):
extra_info.append(f"client_id={record.client_id}")
if hasattr(record, 'httpRequest'):
extra_info.append(f"httpRequest={record.httpRequest}")
if hasattr(record, 'json_fields'):
for key, val in record.json_fields.items():
extra_info.append(f"{key}={val}")

# Combine base message with extra fields
if extra_info:
base_message += " | " + " - ".join(extra_info)

return base_message
LOGGING_INITIALIZED = False

# TODO(<add-link>): Update Request / Response messages.
REQUEST_MESSAGE = "Sending request ..."
RESPONSE_MESSAGE = "Receiving response ..."

# NOTE: Option 1: Allow users to configure log levels.
# def setup_logging(log_level, namespace="google"):

# # NOTE: A logger with namespace="google" is only configured if all of the below conditions hold true:
# # - A root logger is not configured.
# # - N/A: A logger with namespace="google" is not already configured (This statement is removed.)
# # - GOOGLE_SDK_PYTHON_LOGGING_LEVEL is set.
# if not logging.getLogger().hasHandlers() and log_level:
# TODO(<add-link>): Update this list to support additional logging fields
_recognized_logging_fields = ["httpRequest", "rpcName", "serviceName"] # Additional fields to be Logged.

# # define a module for our repositories
# logger = logging.getLogger(namespace)
# try:
# logger.setLevel(log_level)
# except ValueError:
# logger.setLevel("WARNING")
# logger.warning(f"Configured log level `{log_level}` is incorrect. Defaulting to WARNING.")
def logger_configured(logger):
return logger.hasHandlers() or logger.level != logging.NOTSET

# # Default settings
# console_handler = logging.StreamHandler()
# formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
# console_handler.setFormatter(formatter)
# logger.addHandler(console_handler)
def initialize_logging():
global LOGGING_INITIALIZED
if LOGGING_INITIALIZED:
return
scopes = os.getenv("GOOGLE_SDK_PYTHON_LOGGING_SCOPE")
setup_logging(scopes)
LOGGING_INITIALIZED = True

def parse_logging_scopes(scopes):
if not scopes:
return []
# TODO(<add-link>): check if the namespace is a valid namespace.
# TODO(<add-link>): parse a list of namespaces. Current flow expects a single string for now.
namespaces = [scopes]
return namespaces

# NOTE: Option 2: Allow users to configure log systems.
def setup_logging(namespace):

# Instantiate a base logger unconditionally to avoid propogating logs to the root logger.
def default_settings(logger):
if not logger_configured(logger):
console_handler = logging.StreamHandler()
logger.setLevel("DEBUG")
logger.propagate = False
formatter = StructuredLogFormatter()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)

def setup_logging(scopes):
# disable log propagation at base logger level to the root logger only if a base logger is not already configured via code changes.
base_logger = logging.getLogger("google")
base_logger.propagate = False
if not logger_configured(base_logger):
base_logger.propagate = False

# If a namespace is not provided, we don't need to do anything.
if not namespace:
return
# only returns valid logger scopes (namespaces)
# this list has at most one element.
loggers = parse_logging_scopes(scopes)

# If the provided namespace does not start with "google", we don't need to do anything.
# We can update the regex to be more strict about the format of the namespace.
match = re.search(f"google", namespace)
if not match:
# TODO: raise an error? silently ignore?
return

for namespace in loggers:
# This will either create a module level logger or get the reference of the base logger instantiated above.
logger = logging.getLogger(namespace)

# This will either create a module level logger or get the reference of the base logger instantiated above.
logger = logging.getLogger(namespace)
# Set default settings.
default_settings(logger)

# Set default settings.
if not logger.hasHandlers() and logger.level == logging.NOTSET:
console_handler = logging.StreamHandler()
logger.setLevel("DEBUG")
formatter = DefaultFormatter('%(asctime)s %(levelname)s %(name)s %(message)s')
# formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
class StructuredLogFormatter(logging.Formatter):
def format(self, record):
log_obj = {
'timestamp': self.formatTime(record),
'severity': record.levelname,
'name': record.name,
'message': record.getMessage(),
}

for field_name in _recognized_logging_fields:
value = getattr(record, field_name, None)
if value is not None:
log_obj[field_name] = value
return json.dumps(log_obj)

0 comments on commit f8cb7fc

Please sign in to comment.