diff --git a/README.md b/README.md index 55477bc..fe923da 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ - [InfluxDB 3](#influxdb-3) - [Aggregated Dashboards (Optional)](#aggregated-dashboards-optional) - [Other configuration](#other-configuration) + - [Logging](#logging) - [Development](#development) - [Development Environment](#development-environment) - [Setup](#setup) @@ -205,7 +206,33 @@ Also, following environment valuables can be used to configure the service behav |---|---|---| |TOPIC_MODEL||Specifies the name or path for the topic model. If the model is specified by name, it will be downloaded from the [Huggingface]( https://huggingface.co/). When unset or set to an empty string, the topic classification feature is disabled.| |TOPIC_EMBEDDINGS_MODEL||Specifies the name or path for the embeddings model used with the topic model. If the model is specified by name, it will be downloaded from the [Huggingface]( https://huggingface.co/). When unset or set to an empty string, the name will be used from the topic model config.| -|LOG_LEVEL|INFO|The server logging level. Use DEBUG for dev purposes and INFO in prod| +|LOG_LEVEL|INFO|Application log level. Use DEBUG for dev purposes and INFO in prod| + +### Logging + +Logging is provided by the DIAL SDK. The `LOG_LEVEL` variable sets the severity threshold for the application's own logs (`INFO` by default; use `DEBUG` for development). + +By default logs are emitted as human-readable text. +Set `DIAL_SDK_LOG_FORMAT=json` for structured JSON logging. +The format is controlled by `DIAL_SDK_TEXT_LOG_FORMAT` / `DIAL_SDK_JSON_LOG_FORMAT` (both optional), +which use Python's `%`-style [logging attributes](https://docs.python.org/3/library/logging.html#logrecord-attributes) +and default to the values shown below. + +Text logging (default): + +```txt +DIAL_SDK_LOG_FORMAT=text +DIAL_SDK_TEXT_LOG_FORMAT='%(levelprefix)s | %(asctime)s | %(name)s | %(process)d | %(message)s' +``` + +Structured JSON logging: + +```txt +DIAL_SDK_LOG_FORMAT=json +DIAL_SDK_JSON_LOG_FORMAT='{"level": "%(levelname)s", "time": "%(asctime)s", "logger": "%(name)s", "process": "%(process)d", "message": "%(message)s"}' +``` + +See the [full logging documentation](https://github.com/epam/ai-dial-sdk/blob/0.38.0/docs/logging.md) for details. ## Development diff --git a/aidial_analytics_realtime/app.py b/aidial_analytics_realtime/app.py index 55ab7b8..70b72c7 100644 --- a/aidial_analytics_realtime/app.py +++ b/aidial_analytics_realtime/app.py @@ -71,6 +71,8 @@ async def lifespan(app: FastAPI): init_telemetry(app, TelemetryConfig()) +# NOTE: configuring loggers after the DIAL telemetry is initialized, +# because it may have configured the root logger on its own. configure_loggers() check_deprecations() diff --git a/aidial_analytics_realtime/utils/logging.py b/aidial_analytics_realtime/utils/logging.py index c6d64c7..3351c0a 100644 --- a/aidial_analytics_realtime/utils/logging.py +++ b/aidial_analytics_realtime/utils/logging.py @@ -1,52 +1,28 @@ import logging import os -import sys from contextvars import ContextVar -from uvicorn.logging import DefaultFormatter +from aidial_sdk import LogConfig, configure_root_logger app_logger = logging.getLogger("app") LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO") def configure_loggers(): - # Delegate uvicorn logs to the root logger - # to achieve uniform log formatting - for name, log in logging.getLogger().manager.loggerDict.items(): - if isinstance(log, logging.Logger) and name.startswith("uvicorn"): - log.handlers = [] - log.propagate = True + # Delegate to the SDK to install a uniformly formatted root logger + # handler and to route uvicorn logs through it, preserving this + # repo's original text log format. + configure_root_logger( + LogConfig( + text_format="%(levelprefix)s | %(asctime)s | %(process)d | %(name)s | %(message)s" # noqa: E501 + ) + ) # Setting log levels for the analytics application app_logger.setLevel(LOG_LEVEL) - # Configuring the root logger - root = logging.getLogger() - - stderr_handler = next( - ( - handler - for handler in root.handlers - if isinstance(handler, logging.StreamHandler) - and handler.stream == sys.stderr - ), - None, - ) - - # Do not override the existing stderr handlers - # if they are already configured - if stderr_handler is None: - formatter = DefaultFormatter( - fmt="%(levelprefix)s | %(asctime)s | %(process)d | %(name)s | %(message)s", # noqa: E501 - use_colors=True, - ) - - handler = logging.StreamHandler(sys.stderr) - handler.setFormatter(formatter) + for handler in logging.getLogger().handlers: handler.addFilter(_PrefixFilter()) - root.addHandler(handler) - else: - stderr_handler.addFilter(_PrefixFilter()) _logger_prefix: ContextVar[str] = ContextVar("_logger_prefix", default="") diff --git a/poetry.lock b/poetry.lock index 4460c0e..f4d5529 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,15 +1,15 @@ -# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "aidial-sdk" -version = "0.33.1" +version = "0.38.0" description = "Framework to create applications and model adapters for AI DIAL" optional = false python-versions = "<4.0,>=3.10" groups = ["main"] files = [ - {file = "aidial_sdk-0.33.1-py3-none-any.whl", hash = "sha256:469ed97f41dc1f088536031e4281e9c382dcdff18d907f3b34be71e19845f2f8"}, - {file = "aidial_sdk-0.33.1.tar.gz", hash = "sha256:3b334d9ae8c247f7d08434bcde06f7d6dc9d65cf7da6a47f3e5d0865421ad241"}, + {file = "aidial_sdk-0.38.0-py3-none-any.whl", hash = "sha256:d67dec8e6133de3e0a75a1646c39d52a0f78860bba57b2e30fb1f158f6f13bd6"}, + {file = "aidial_sdk-0.38.0.tar.gz", hash = "sha256:ce3022f6e488a2f0de21db2a4ef87bc1e62558e8dd7e96816283b9b20214e1b7"}, ] [package.dependencies] @@ -3807,4 +3807,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.12,<3.13" -content-hash = "5cff830d76b117d2b865e54d319b7832c51f1f19a45e5675a3c053e3a0290b46" +content-hash = "d2f13b100e4f1295add453ea0870281a9fb85b0612263cbe0da8328fc40c1baf" diff --git a/pyproject.toml b/pyproject.toml index b3dc98c..bfb1033 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ bertopic = "^0.15.0" python-dotenv = "^1.2.2" python-dateutil = "^2.8.2" pydantic = "2.7.0" -aidial-sdk = { version = "0.33.1", extras = ["telemetry"] } +aidial-sdk = { version = "0.38.0", extras = ["telemetry"] } # Indirect dependencies torch = [