Перейти к основному содержимому

Python API — обзор

Пакет dagstack-logger реализует контракт логгера для Python (3.11+). Phase 1 (опубликована как v0.1.x на PyPI) поставляет основное API плюс три приёмника; Phase 2 добавит OTLP-экспортёр и цепочку LogProcessor.

Публичные экспорты

__all__ пакета выставляет:

СимволВидМодуль
Loggerклассdagstack.logger.logger
LogRecorddataclassdagstack.logger.records
Resourcedataclassdagstack.logger.records
InstrumentationScopedataclassdagstack.logger.records
SeverityIntEnumdagstack.logger.severity
SinkProtocoldagstack.logger.sinks
ConsoleSinkклассdagstack.logger.sinks
FileSinkклассdagstack.logger.sinks
InMemorySinkклассdagstack.logger.sinks
Subscriptiondataclassdagstack.logger.subscription
configureфункцияdagstack.logger.configuration
__version__strdagstack.logger._version

Импортируй всё с верхнего уровня:

from dagstack.logger import (
Logger,
LogRecord,
Resource,
InstrumentationScope,
Severity,
Sink,
ConsoleSink,
FileSink,
InMemorySink,
Subscription,
configure,
)

configure(...) — глобальный bootstrap

def configure(
*,
root_level: str | int = "INFO",
sinks: list[Sink] | None = None,
per_logger_levels: dict[str, str | int] | None = None,
resource_attributes: dict[str, Value] | None = None,
) -> None

Инициализирует глобальное состояние логгера. Задаёт нижнюю границу уровня root-логгера, прикрепляет приёмники, применяет переопределения по логгерам и засевает OTel Resource атрибутами уровня процесса. Вызывай один раз при старте приложения, до того как бизнес-код обратится к Logger.get(name).

Полный пошаговый пример смотри в guide Настройка логгера.

Logger — реестр именованных логгеров

class Logger:
@classmethod
def get(cls, name: str = "", version: str | None = None) -> Logger: ...

# Emit'ы по уровню
def trace(self, body: Value, *, attributes: Mapping[str, Value] | None = None) -> None: ...
def debug(self, body: Value, *, attributes: Mapping[str, Value] | None = None) -> None: ...
def info(self, body: Value, *, attributes: Mapping[str, Value] | None = None) -> None: ...
def warn(self, body: Value, *, attributes: Mapping[str, Value] | None = None) -> None: ...
def error(self, body: Value, *, attributes: Mapping[str, Value] | None = None) -> None: ...
def fatal(self, body: Value, *, attributes: Mapping[str, Value] | None = None) -> None: ...
def log(self, severity_number: int, body: Value, *, attributes: Mapping[str, Value] | None = None) -> None: ...
def exception(self, err: BaseException, *, body: Value | None = None, attributes: Mapping[str, Value] | None = None) -> None: ...

# Мутаторы конфигурации
def set_sinks(self, sinks: list[Sink]) -> None: ...
def set_min_severity(self, severity_number: int) -> None: ...
def set_resource(self, resource: Resource | None) -> None: ...

# Локальные переопределения
def with_sinks(self, sinks: list[Sink]) -> Logger: ...
def append_sinks(self, extra_sinks: list[Sink]) -> Logger: ...
def without_sinks(self) -> Logger: ...
def scope_sinks(self, sinks: list[Sink]) -> AbstractContextManager[Logger]: ...
def child(self, attributes: Mapping[str, Value]) -> Logger: ...

# Жизненный цикл
def flush(self, timeout: float = 5.0) -> None: ...
def close(self) -> None: ...

# Интроспекция
def effective_sinks(self) -> list[Sink]: ...
def effective_min_severity(self) -> int: ...
def effective_resource(self) -> Resource | None: ...

# Подписка (Phase 1 — неактивна)
def on_reconfigure(self, callback: Callable[[], None]) -> Subscription: ...

Конструируется только через Logger.get(name, version); реестр кеширует один экземпляр на имя. Потомки, созданные child(...), with_sinks(...), append_sinks(...), without_sinks(...), отделены от реестра — они наследуют конфигурацию от родителя, но в глобальном реестре не участвуют.

Severity — bucket-константы

class Severity(IntEnum):
TRACE = 1
DEBUG = 5
INFO = 9
WARN = 13
ERROR = 17
FATAL = 21

Шесть baseline-значений bucket'ов. int(Severity.INFO) == 9. Полное перечисление 1-24 смотри в справочной таблице уровней.

Приёмники

Три конкретных приёмника плюс протокол Sink:

class Sink(Protocol):
id: str
def emit(self, record: LogRecord) -> None: ...
def flush(self, timeout: float = 5.0) -> None: ...
def close(self) -> None: ...
def supports_severity(self, severity_number: int) -> bool: ...


class ConsoleSink:
def __init__(self, *, mode: Literal["auto", "json", "pretty"] = "auto",
stream: TextIO | None = None,
min_severity: int = 1) -> None: ...


class FileSink:
def __init__(self, path: str | Path, *,
max_bytes: int = 0, keep: int = 0,
min_severity: int = 1) -> None: ...


class InMemorySink:
def __init__(self, *, capacity: int = 1000, min_severity: int = 1) -> None: ...
def records(self) -> list[LogRecord]: ...
def clear(self) -> None: ...
@property
def capacity(self) -> int: ...

Использование смотри в Приёмниках; реализацию протокола — в guide по custom-sink.

LogRecord — типизированный dataclass

@dataclass(slots=True)
class LogRecord:
time_unix_nano: int
severity_number: int
severity_text: str
body: Value
attributes: dict[str, Value] = field(default_factory=dict)
instrumentation_scope: InstrumentationScope | None = None
resource: Resource | None = None
trace_id: bytes | None = None
span_id: bytes | None = None
trace_flags: int = 0
observed_time_unix_nano: int | None = None

По спеке §1 набор полей совпадает с OTel Log Data Model v1.24. Полную таблицу с семантикой и владением смотри в Полях LogRecord.

Subscription — Phase 1 неактивна

Subscription зеркалит dataclass Subscription из config-specpath, active, inactive_reason. В Phase 1 логгер не подписывается на runtime-изменения конфигурации; Logger.on_reconfigure(callback) возвращает inactive-подписку с inactive_reason="Phase 1 logger does not support watch-based reconfigure" и одноразово выводит warning. Phase 2 активирует watch-путь.

См. также