-
Notifications
You must be signed in to change notification settings - Fork 179
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ai-server): improved logging (#16435)
# Overview Redo logging for this API ## Test Plan and Hands on Testing - [x] local logging output in the container is JSON - [x] local logging output with local environment is text Once merged and auto-deployed to staging, we will look at the output in DataDog ## Risk assessment - medium - we are not getting what we need in DataDog to build filters and alerts and so we must evolve with this this - there is also a risk with the middleware stacking, so we will test a lot in staging
- Loading branch information
Showing
14 changed files
with
736 additions
and
402 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
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
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
Large diffs are not rendered by default.
Oops, something went wrong.
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
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
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 |
---|---|---|
@@ -0,0 +1,133 @@ | ||
# Taken directly from https://gist.github.com/nymous/f138c7f06062b7c43c060bf03759c29e | ||
import logging | ||
import sys | ||
|
||
import ddtrace | ||
import structlog | ||
from ddtrace import tracer | ||
from structlog.types import EventDict, Processor | ||
|
||
|
||
# https://github.com/hynek/structlog/issues/35#issuecomment-591321744 | ||
def rename_event_key(_, __, event_dict: EventDict) -> EventDict: # type: ignore[no-untyped-def] | ||
""" | ||
Log entries keep the text message in the `event` field, but Datadog | ||
uses the `message` field. This processor moves the value from one field to | ||
the other. | ||
See https://github.com/hynek/structlog/issues/35#issuecomment-591321744 | ||
""" | ||
event_dict["message"] = event_dict.pop("event") | ||
return event_dict | ||
|
||
|
||
def drop_color_message_key(_, __, event_dict: EventDict) -> EventDict: # type: ignore[no-untyped-def] | ||
""" | ||
Uvicorn logs the message a second time in the extra `color_message`, but we don't | ||
need it. This processor drops the key from the event dict if it exists. | ||
""" | ||
event_dict.pop("color_message", None) | ||
return event_dict | ||
|
||
|
||
def tracer_injection(_, __, event_dict: EventDict) -> EventDict: # type: ignore[no-untyped-def] | ||
# get correlation ids from current tracer context | ||
span = tracer.current_span() | ||
trace_id, span_id = (str((1 << 64) - 1 & span.trace_id), span.span_id) if span else (None, None) | ||
|
||
# add ids to structlog event dictionary | ||
event_dict["dd.trace_id"] = str(trace_id or 0) | ||
event_dict["dd.span_id"] = str(span_id or 0) | ||
|
||
# add the env, service, and version configured for the tracer | ||
event_dict["dd.env"] = ddtrace.config.env or "" | ||
event_dict["dd.service"] = ddtrace.config.service or "" | ||
event_dict["dd.version"] = ddtrace.config.version or "" | ||
|
||
return event_dict | ||
|
||
|
||
def setup_logging(json_logs: bool = False, log_level: str = "INFO") -> None: | ||
timestamper = structlog.processors.TimeStamper(fmt="iso") | ||
|
||
shared_processors: list[Processor] = [ | ||
structlog.contextvars.merge_contextvars, | ||
structlog.stdlib.add_logger_name, | ||
structlog.stdlib.add_log_level, | ||
structlog.stdlib.PositionalArgumentsFormatter(), | ||
structlog.stdlib.ExtraAdder(), | ||
drop_color_message_key, | ||
tracer_injection, | ||
timestamper, | ||
structlog.processors.StackInfoRenderer(), | ||
] | ||
|
||
if json_logs: | ||
# We rename the `event` key to `message` only in JSON logs, as Datadog looks for the | ||
# `message` key but the pretty ConsoleRenderer looks for `event` | ||
shared_processors.append(rename_event_key) | ||
# Format the exception only for JSON logs, as we want to pretty-print them when | ||
# using the ConsoleRenderer | ||
shared_processors.append(structlog.processors.format_exc_info) | ||
|
||
structlog.configure( | ||
processors=shared_processors | ||
+ [ | ||
# Prepare event dict for `ProcessorFormatter`. | ||
structlog.stdlib.ProcessorFormatter.wrap_for_formatter, | ||
], | ||
logger_factory=structlog.stdlib.LoggerFactory(), | ||
cache_logger_on_first_use=True, | ||
) | ||
|
||
log_renderer: structlog.types.Processor | ||
if json_logs: | ||
log_renderer = structlog.processors.JSONRenderer() | ||
else: | ||
log_renderer = structlog.dev.ConsoleRenderer() | ||
|
||
formatter = structlog.stdlib.ProcessorFormatter( | ||
# These run ONLY on `logging` entries that do NOT originate within | ||
# structlog. | ||
foreign_pre_chain=shared_processors, | ||
# These run on ALL entries after the pre_chain is done. | ||
processors=[ | ||
# Remove _record & _from_structlog. | ||
structlog.stdlib.ProcessorFormatter.remove_processors_meta, | ||
log_renderer, | ||
], | ||
) | ||
|
||
handler = logging.StreamHandler() | ||
# Use OUR `ProcessorFormatter` to format all `logging` entries. | ||
handler.setFormatter(formatter) | ||
root_logger = logging.getLogger() | ||
root_logger.addHandler(handler) | ||
root_logger.setLevel(log_level.upper()) | ||
|
||
for _log in ["uvicorn", "uvicorn.error"]: | ||
# Clear the log handlers for uvicorn loggers, and enable propagation | ||
# so the messages are caught by our root logger and formatted correctly | ||
# by structlog | ||
logging.getLogger(_log).handlers.clear() | ||
logging.getLogger(_log).propagate = True | ||
|
||
# Since we re-create the access logs ourselves, to add all information | ||
# in the structured log (see the `logging_middleware` in main.py), we clear | ||
# the handlers and prevent the logs to propagate to a logger higher up in the | ||
# hierarchy (effectively rendering them silent). | ||
logging.getLogger("uvicorn.access").handlers.clear() | ||
logging.getLogger("uvicorn.access").propagate = False | ||
|
||
def handle_exception(exc_type, exc_value, exc_traceback): # type: ignore[no-untyped-def] | ||
""" | ||
Log any uncaught exception instead of letting it be printed by Python | ||
(but leave KeyboardInterrupt untouched to allow users to Ctrl+C to stop) | ||
See https://stackoverflow.com/a/16993115/3641865 | ||
""" | ||
if issubclass(exc_type, KeyboardInterrupt): | ||
sys.__excepthook__(exc_type, exc_value, exc_traceback) | ||
return | ||
|
||
root_logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) | ||
|
||
sys.excepthook = handle_exception |
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
Oops, something went wrong.