-
Notifications
You must be signed in to change notification settings - Fork 88
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
58 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |