Реализация транспорта для трейсинга

This commit is contained in:
2026-03-04 12:17:07 +03:00
parent c5a78d80d4
commit 4a0646bb14
6 changed files with 126 additions and 4 deletions

View File

@@ -368,6 +368,8 @@ Control plane и HTTP-канал управления.
pip install "plba @ git+ssh://git@git.lesha.spb.ru/alex/plba.git" pip install "plba @ git+ssh://git@git.lesha.spb.ru/alex/plba.git"
``` ```
При такой установке `pip` ставит не только сам пакет `plba`, но и все его зависимости, объявленные в [pyproject.toml](/Users/alex/Dev_projects_v2/apps/plba/pyproject.toml), например `fastapi`, `uvicorn` и `PyYAML`.
Если нужна установка из конкретной ветки: Если нужна установка из конкретной ветки:
```bash ```bash
@@ -395,6 +397,41 @@ pip install --upgrade pip
pip install "plba @ git+ssh://git@git.lesha.spb.ru/alex/plba.git" pip install "plba @ git+ssh://git@git.lesha.spb.ru/alex/plba.git"
``` ```
### Подключение `plba` в бизнес-приложении
Чтобы при установке бизнес-приложения автоматически подтягивались зависимости `plba`, нужно добавить `plba` в зависимости самого бизнес-приложения как Git dependency.
Пример для `requirements.txt`:
```txt
plba @ git+ssh://git@git.lesha.spb.ru/alex/plba.git
```
Пример для `pyproject.toml`:
```toml
[project]
dependencies = [
"plba @ git+ssh://git@git.lesha.spb.ru/alex/plba.git",
]
```
Если бизнес-приложение собирается в Docker, достаточно чтобы на этапе сборки выполнялся обычный `pip install`, например:
```dockerfile
COPY pyproject.toml .
RUN pip install .
```
или при использовании `requirements.txt`:
```dockerfile
COPY requirements.txt .
RUN pip install -r requirements.txt
```
В обоих случаях `pip` установит `plba` из Git и автоматически подтянет его транзитивные зависимости.
### Локальная разработка ### Локальная разработка
Если пакет нужно не только использовать, но и разрабатывать: Если пакет нужно не только использовать, но и разрабатывать:

View File

@@ -10,6 +10,7 @@ readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"fastapi>=0.129.0", "fastapi>=0.129.0",
"PyMySQL>=1.1",
"PyYAML>=6.0.3", "PyYAML>=6.0.3",
"uvicorn>=0.41.0", "uvicorn>=0.41.0",
] ]

View File

@@ -1,10 +1,11 @@
from app_runtime.contracts.trace import TraceContext, TraceContextRecord, TraceLogMessage, TraceTransport from app_runtime.contracts.trace import TraceContext, TraceContextRecord, TraceLogMessage, TraceTransport
from app_runtime.tracing.service import TraceManager, TraceService from app_runtime.tracing.service import TraceManager, TraceService
from app_runtime.tracing.store import ActiveTraceContext, TraceContextStore from app_runtime.tracing.store import ActiveTraceContext, TraceContextStore
from app_runtime.tracing.transport import NoOpTraceTransport from app_runtime.tracing.transport import MySqlTraceTransport, NoOpTraceTransport
__all__ = [ __all__ = [
"ActiveTraceContext", "ActiveTraceContext",
"MySqlTraceTransport",
"NoOpTraceTransport", "NoOpTraceTransport",
"TraceContext", "TraceContext",
"TraceContextRecord", "TraceContextRecord",

View File

@@ -1,5 +1,9 @@
from __future__ import annotations from __future__ import annotations
import json
from datetime import date, datetime
from app_runtime.contracts.trace import TraceContextRecord, TraceLogMessage, TraceTransport from app_runtime.contracts.trace import TraceContextRecord, TraceLogMessage, TraceTransport
@@ -9,3 +13,81 @@ class NoOpTraceTransport(TraceTransport):
def write_message(self, record: TraceLogMessage) -> None: def write_message(self, record: TraceLogMessage) -> None:
del record del record
class MySqlTraceTransport(TraceTransport):
def __init__(
self,
*,
host: str,
port: int,
database: str,
user: str,
password: str,
) -> None:
self._host = host
self._port = port
self._database = database
self._user = user
self._password = password
def write_context(self, record: TraceContextRecord) -> None:
query = """
INSERT INTO trace_contexts (trace_id, parent_id, alias, type, event_time, attrs_json)
VALUES (%s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
parent_id = VALUES(parent_id),
alias = VALUES(alias),
type = VALUES(type),
event_time = VALUES(event_time),
attrs_json = VALUES(attrs_json)
"""
params = (
record.trace_id,
record.parent_id,
record.alias,
record.type,
record.event_time.replace(tzinfo=None),
self._dumps(record.attrs),
)
self._execute(query, params)
def write_message(self, record: TraceLogMessage) -> None:
query = """
INSERT INTO trace_messages (trace_id, event_time, step, status, level, message, attrs_json)
VALUES (%s, %s, %s, %s, %s, %s, %s)
"""
params = (
record.trace_id,
record.event_time.replace(tzinfo=None),
record.step,
record.status,
record.level,
record.message,
self._dumps(record.attrs),
)
self._execute(query, params)
def _execute(self, query: str, params: tuple[object, ...]) -> None:
import pymysql
with pymysql.connect(
host=self._host,
port=self._port,
user=self._user,
password=self._password,
database=self._database,
charset="utf8mb4",
autocommit=True,
cursorclass=pymysql.cursors.DictCursor,
) as connection:
with connection.cursor() as cursor:
cursor.execute(query, params)
def _dumps(self, payload: dict[str, object]) -> str:
return json.dumps(payload, ensure_ascii=False, default=self._json_default)
def _json_default(self, value: object) -> str:
if isinstance(value, (datetime, date)):
return value.isoformat()
return str(value)

View File

@@ -20,7 +20,7 @@ from plba.core import ConfigurationManager, RuntimeManager, ServiceContainer
from plba.health import HealthRegistry from plba.health import HealthRegistry
from plba.logging import LogManager from plba.logging import LogManager
from plba.queue import InMemoryTaskQueue from plba.queue import InMemoryTaskQueue
from plba.tracing import NoOpTraceTransport, TraceService from plba.tracing import MySqlTraceTransport, NoOpTraceTransport, TraceService
from plba.workers import QueueWorker, WorkerSupervisor from plba.workers import QueueWorker, WorkerSupervisor
__all__ = [ __all__ = [
@@ -38,6 +38,7 @@ __all__ = [
"HttpControlChannel", "HttpControlChannel",
"InMemoryTaskQueue", "InMemoryTaskQueue",
"LogManager", "LogManager",
"MySqlTraceTransport",
"NoOpTraceTransport", "NoOpTraceTransport",
"QueueWorker", "QueueWorker",
"RuntimeManager", "RuntimeManager",

View File

@@ -1,4 +1,4 @@
from app_runtime.tracing.service import TraceService from app_runtime.tracing.service import TraceService
from app_runtime.tracing.transport import NoOpTraceTransport from app_runtime.tracing.transport import MySqlTraceTransport, NoOpTraceTransport
__all__ = ["NoOpTraceTransport", "TraceService"] __all__ = ["MySqlTraceTransport", "NoOpTraceTransport", "TraceService"]