tp-common 0.0.2__tar.gz → 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: tp-common
3
- Version: 0.0.2
3
+ Version: 0.0.4
4
4
  Summary:
5
5
  Author: Developer
6
6
  Author-email: front-gold@mail.ru
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tp-common"
3
- version = "0.0.2"
3
+ version = "0.0.4"
4
4
  description = ""
5
5
  authors = [
6
6
  {name = "Developer",email = "front-gold@mail.ru"}
@@ -18,6 +18,9 @@ dependencies = [
18
18
  requires = ["poetry-core>=2.0.0,<3.0.0"]
19
19
  build-backend = "poetry.core.masonry.api"
20
20
 
21
+ #[tool.poetry.packages]
22
+ #packages = [{include = "tp_common", from = "src"}]
23
+
21
24
  # === настройки проверки типизации ===
22
25
  [tool.isort]
23
26
  profile = "black"
@@ -63,6 +66,10 @@ follow_imports = "silent"
63
66
  module = "tp_common.logging"
64
67
  disable_error_code = ["import-untyped"]
65
68
 
69
+ [[tool.mypy.overrides]]
70
+ module = "tp_helper.*"
71
+ disable_error_code = ["import-untyped"]
72
+
66
73
  [tool.pyright]
67
74
  extraPaths = ["src"]
68
75
 
@@ -15,10 +15,10 @@ from aiohttp import (
15
15
  ServerTimeoutError,
16
16
  )
17
17
 
18
- from tp_common.base.base_client.client_exceptions import (
19
- BaseClientException,
18
+ from tp_common.base_client.exceptions import (
20
19
  ClientConnectionException,
21
20
  ClientDNSException,
21
+ ClientException,
22
22
  ClientProxyException,
23
23
  ClientResponseErrorException,
24
24
  ClientTimeoutException,
@@ -471,7 +471,7 @@ class BaseClient:
471
471
  "response": response_body_text or "",
472
472
  },
473
473
  )
474
- raise BaseClientException(
474
+ raise ClientException(
475
475
  f"Неожиданная HTTP-ошибка: {str(e)}",
476
476
  url=url,
477
477
  ) from e
@@ -0,0 +1,136 @@
1
+ """Исключения для базового HTTP клиента."""
2
+
3
+
4
+ class ClientException(Exception):
5
+ """Базовое исключение для всех ошибок клиента."""
6
+
7
+ def __init__(
8
+ self,
9
+ message: str,
10
+ url: str | None = None,
11
+ status_code: int | None = None,
12
+ response_body: str | None = None,
13
+ ) -> None:
14
+ super().__init__(message)
15
+ self.url = url
16
+ self.status_code = status_code
17
+ self.response_body = response_body
18
+
19
+
20
+ class ClientResponseErrorException(ClientException):
21
+ """Исключение при неуспешном HTTP статусе (>=400)."""
22
+
23
+ def __init__(
24
+ self,
25
+ message: str,
26
+ url: str | None = None,
27
+ status_code: int | None = None,
28
+ response_body: str | None = None,
29
+ ) -> None:
30
+ super().__init__(message, url, status_code, response_body)
31
+
32
+
33
+ class ClientConnectionException(ClientException):
34
+ """Исключение при ошибке соединения (не удалось установить соединение)."""
35
+
36
+ def __init__(
37
+ self,
38
+ message: str,
39
+ url: str | None = None,
40
+ ) -> None:
41
+ super().__init__(message, url)
42
+
43
+
44
+ class ClientTimeoutException(ClientConnectionException):
45
+ """Исключение при таймауте соединения."""
46
+
47
+ def __init__(
48
+ self,
49
+ message: str,
50
+ url: str | None = None,
51
+ ) -> None:
52
+ super().__init__(message, url)
53
+
54
+
55
+ class ClientProxyException(ClientConnectionException):
56
+ """Исключение при ошибке прокси."""
57
+
58
+ def __init__(
59
+ self,
60
+ message: str,
61
+ url: str | None = None,
62
+ proxy: str | None = None,
63
+ ) -> None:
64
+ super().__init__(message, url)
65
+ self.proxy = proxy
66
+
67
+
68
+ class ClientDNSException(ClientConnectionException):
69
+ """Исключение при ошибке DNS (не удалось разрешить доменное имя)."""
70
+
71
+ def __init__(
72
+ self,
73
+ message: str,
74
+ url: str | None = None,
75
+ ) -> None:
76
+ super().__init__(message, url)
77
+
78
+
79
+ class ClientBusinessErrorException(ClientException):
80
+ """Базовое исключение для бизнес-ошибок (400, 401, 403, 404, 422)."""
81
+
82
+ pass
83
+
84
+
85
+ class ClientServerErrorException(ClientException):
86
+ """Базовое исключение для ошибок сервера (500, 502, 503, 504)."""
87
+
88
+ pass
89
+
90
+
91
+ class ClientNetworkErrorException(ClientException):
92
+ """Базовое исключение для сетевых ошибок (таймауты, соединение, DNS)."""
93
+
94
+ pass
95
+
96
+
97
+ class ClientProxyErrorException(ClientException):
98
+ """Базовое исключение для ошибок прокси."""
99
+
100
+ pass
101
+
102
+
103
+ class ClientAuthorizationException(ClientBusinessErrorException):
104
+ """Исключение при ошибке авторизации (401, 403)."""
105
+
106
+ pass
107
+
108
+
109
+ class ClientValidationException(ClientBusinessErrorException):
110
+ """Исключение при ошибке валидации данных (400, 422)."""
111
+
112
+ pass
113
+
114
+
115
+ class ClientResourceNotFoundException(ClientBusinessErrorException):
116
+ """Исключение при отсутствии ресурса (404)."""
117
+
118
+ pass
119
+
120
+
121
+ class ClientServerException(ClientServerErrorException):
122
+ """Исключение при ошибке сервера (500, 502, 503, 504)."""
123
+
124
+ pass
125
+
126
+
127
+ class ClientTooManyRequestsException(ClientProxyErrorException):
128
+ """Исключение при превышении лимита запросов (429)."""
129
+
130
+ pass
131
+
132
+
133
+ class ClientServiceUnavailableException(ClientServerErrorException):
134
+ """Исключение при недоступности сервиса (502, 503, 504)."""
135
+
136
+ pass
@@ -2,6 +2,15 @@ from pydantic import BaseModel, ConfigDict
2
2
  from pydantic.alias_generators import to_camel
3
3
 
4
4
 
5
+ class BaseResponse(BaseModel):
6
+ model_config = ConfigDict(
7
+ alias_generator=to_camel,
8
+ validate_by_name=True,
9
+ validate_by_alias=True,
10
+ serialize_by_alias=True,
11
+ )
12
+
13
+
5
14
  class BaseRequest(BaseModel):
6
15
  model_config = ConfigDict(
7
16
  alias_generator=to_camel,
@@ -1,52 +0,0 @@
1
- from tp_common.base.base_client.base_client import BaseClient
2
- from tp_common.base.base_client.base_exception import BaseInfrastructureException
3
- from tp_common.base.base_client.base_request import BaseRequest
4
- from tp_common.base.base_client.base_response import BaseResponse
5
- from tp_common.base.base_client.client_exceptions import (
6
- BaseClientException,
7
- ClientConnectionException,
8
- ClientDNSException,
9
- ClientProxyException,
10
- ClientResponseErrorException,
11
- ClientTimeoutException,
12
- )
13
- from tp_common.base.base_client.domain_exceptions import (
14
- AuthorizationException,
15
- BaseBusinessErrorException,
16
- BaseNetworkErrorException,
17
- BaseProxyErrorException,
18
- BaseServerErrorException,
19
- ResourceNotFoundException,
20
- ServerException,
21
- ServiceUnavailableException,
22
- TooManyRequestsException,
23
- ValidationException,
24
- )
25
- from tp_common.logging import Logger, TracingLogger
26
-
27
- __all__ = [
28
- # BaseClient
29
- "BaseClient",
30
- "BaseInfrastructureException",
31
- "BaseRequest",
32
- "BaseResponse",
33
- "BaseClientException",
34
- "ClientResponseErrorException",
35
- "ClientTimeoutException",
36
- "ClientProxyException",
37
- "ClientConnectionException",
38
- "ClientDNSException",
39
- "BaseBusinessErrorException",
40
- "BaseServerErrorException",
41
- "BaseNetworkErrorException",
42
- "BaseProxyErrorException",
43
- "AuthorizationException",
44
- "ValidationException",
45
- "ResourceNotFoundException",
46
- "ServerException",
47
- "TooManyRequestsException",
48
- "ServiceUnavailableException",
49
- # Logging
50
- "TracingLogger",
51
- "Logger",
52
- ]
@@ -1,2 +0,0 @@
1
- class BaseInfrastructureException(Exception):
2
- pass
@@ -1,11 +0,0 @@
1
- from pydantic import BaseModel, ConfigDict
2
- from pydantic.alias_generators import to_camel
3
-
4
-
5
- class BaseResponse(BaseModel):
6
- model_config = ConfigDict(
7
- alias_generator=to_camel,
8
- validate_by_name=True,
9
- validate_by_alias=True,
10
- serialize_by_alias=True,
11
- )
@@ -1,76 +0,0 @@
1
- """Исключения для базового HTTP клиента."""
2
-
3
-
4
- class BaseClientException(Exception):
5
- """Базовое исключение для всех ошибок клиента."""
6
-
7
- def __init__(
8
- self,
9
- message: str,
10
- url: str | None = None,
11
- status_code: int | None = None,
12
- response_body: str | None = None,
13
- ) -> None:
14
- super().__init__(message)
15
- self.url = url
16
- self.status_code = status_code
17
- self.response_body = response_body
18
-
19
-
20
- class ClientResponseErrorException(BaseClientException):
21
- """Исключение при неуспешном HTTP статусе (>=400)."""
22
-
23
- def __init__(
24
- self,
25
- message: str,
26
- url: str | None = None,
27
- status_code: int | None = None,
28
- response_body: str | None = None,
29
- ) -> None:
30
- super().__init__(message, url, status_code, response_body)
31
-
32
-
33
- class ClientTimeoutException(BaseClientException):
34
- """Исключение при таймауте соединения."""
35
-
36
- def __init__(
37
- self,
38
- message: str,
39
- url: str | None = None,
40
- ) -> None:
41
- super().__init__(message, url)
42
-
43
-
44
- class ClientProxyException(BaseClientException):
45
- """Исключение при ошибке прокси."""
46
-
47
- def __init__(
48
- self,
49
- message: str,
50
- url: str | None = None,
51
- proxy: str | None = None,
52
- ) -> None:
53
- super().__init__(message, url)
54
- self.proxy = proxy
55
-
56
-
57
- class ClientConnectionException(BaseClientException):
58
- """Исключение при ошибке соединения (не удалось установить соединение)."""
59
-
60
- def __init__(
61
- self,
62
- message: str,
63
- url: str | None = None,
64
- ) -> None:
65
- super().__init__(message, url)
66
-
67
-
68
- class ClientDNSException(BaseClientException):
69
- """Исключение при ошибке DNS (не удалось разрешить доменное имя)."""
70
-
71
- def __init__(
72
- self,
73
- message: str,
74
- url: str | None = None,
75
- ) -> None:
76
- super().__init__(message, url)
@@ -1,63 +0,0 @@
1
- """Доменные исключения для обработки ошибок в воркерах."""
2
-
3
- from tp_common.base.base_client.client_exceptions import BaseClientException
4
-
5
-
6
- class BaseBusinessErrorException(BaseClientException):
7
- """Базовое исключение для бизнес-ошибок (400, 401, 403, 404, 422)."""
8
-
9
- pass
10
-
11
-
12
- class BaseServerErrorException(BaseClientException):
13
- """Базовое исключение для ошибок сервера (500, 502, 503, 504)."""
14
-
15
- pass
16
-
17
-
18
- class BaseNetworkErrorException(BaseClientException):
19
- """Базовое исключение для сетевых ошибок (таймауты, соединение, DNS)."""
20
-
21
- pass
22
-
23
-
24
- class BaseProxyErrorException(BaseClientException):
25
- """Базовое исключение для ошибок прокси."""
26
-
27
- pass
28
-
29
-
30
- class AuthorizationException(BaseBusinessErrorException):
31
- """Исключение при ошибке авторизации (401, 403)."""
32
-
33
- pass
34
-
35
-
36
- class ValidationException(BaseBusinessErrorException):
37
- """Исключение при ошибке валидации данных (400, 422)."""
38
-
39
- pass
40
-
41
-
42
- class ResourceNotFoundException(BaseBusinessErrorException):
43
- """Исключение при отсутствии ресурса (404)."""
44
-
45
- pass
46
-
47
-
48
- class ServerException(BaseServerErrorException):
49
- """Исключение при ошибке сервера (500, 502, 503, 504)."""
50
-
51
- pass
52
-
53
-
54
- class TooManyRequestsException(BaseProxyErrorException):
55
- """Исключение при превышении лимита запросов (429)."""
56
-
57
- pass
58
-
59
-
60
- class ServiceUnavailableException(BaseServerErrorException):
61
- """Исключение при недоступности сервиса (502, 503, 504)."""
62
-
63
- pass
@@ -1,339 +0,0 @@
1
- """
2
- Централизованный модуль для получения логгеров в проекте.
3
-
4
- Единая точка входа — класс Logger(env, job=...).get_logger(name).
5
- Все настройки форматирования и обработки логов находятся здесь.
6
- Очередь и поток для асинхронного логирования общие для всех экземпляров (синглтоны).
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- import logging
12
- import queue
13
- import sys
14
- import threading
15
- import traceback
16
- import uuid
17
- from typing import Any
18
-
19
- # from logging_loki import LokiHandler
20
- from pythonjsonlogger import json
21
- from tp_helper.types.environment_type import EnvironmentType
22
-
23
- # ============================================================================
24
- # ЕДИНАЯ КОНФИГУРАЦИЯ ФОРМАТТЕРА
25
- # ============================================================================
26
- # Все изменения формата логов должны производиться здесь
27
-
28
-
29
- def _create_formatter(
30
- env: EnvironmentType, job: str | int | uuid.UUID | None = None
31
- ) -> json.JsonFormatter:
32
- """
33
- Создаёт JSON-форматтер для логов.
34
-
35
- Единая точка настройки формата логов для всего проекта.
36
-
37
- Args:
38
- env: Окружение (для статического поля в логах).
39
- job: Идентификатор задачи (для статических полей, опционально).
40
-
41
- Returns:
42
- Настроенный JSON форматтер.
43
- """
44
- static_fields = {"env": env.value}
45
- if job is not None:
46
- static_fields["job"] = str(job)
47
-
48
- return json.JsonFormatter(
49
- "{asctime}{levelname}{message}{env}{trace_id}",
50
- style="{",
51
- json_ensure_ascii=False,
52
- rename_fields={
53
- "asctime": "timestamp",
54
- "levelname": "level",
55
- "message": "msg",
56
- },
57
- static_fields=static_fields,
58
- )
59
-
60
-
61
- # ============================================================================
62
- # ФИЛЬТР ДЛЯ ОБРАБОТКИ ИСКЛЮЧЕНИЙ
63
- # ============================================================================
64
-
65
-
66
- class ExceptionFormatterFilter(logging.Filter):
67
- """Filter that formats exceptions into JSON structure and adds to extra."""
68
-
69
- def filter(self, record: logging.LogRecord) -> bool:
70
- """Format exception info into structured JSON if present."""
71
- if record.exc_info and record.exc_info[0] is not None:
72
- e_type, e, exc_traceback = record.exc_info
73
-
74
- if exc_traceback:
75
- tb = traceback.extract_tb(exc_traceback)
76
- if tb:
77
- last = tb[-1]
78
- record.last_tb_line = (
79
- f"{last.filename}:{last.lineno} — {last.name} → {last.line}"
80
- )
81
-
82
- record.error = e_type if e_type else ""
83
- record.error_message = str(e) if e else ""
84
-
85
- return True
86
-
87
-
88
- # ============================================================================
89
- # АСИНХРОННАЯ ОБРАБОТКА ЛОГОВ
90
- # ============================================================================
91
-
92
-
93
- class QueueHandler(logging.Handler):
94
- """Handler that sends log records to a queue for async processing."""
95
-
96
- def __init__(self, log_queue: queue.Queue[logging.LogRecord | None]) -> None:
97
- super().__init__()
98
- self.queue = log_queue
99
-
100
- def emit(self, record: logging.LogRecord) -> None:
101
- """Put the record into the queue."""
102
- try:
103
- self.queue.put_nowait(record)
104
- except queue.Full:
105
- self.handleError(record)
106
- except Exception:
107
- self.handleError(record)
108
-
109
-
110
- def _log_listener_thread(
111
- log_queue: queue.Queue[logging.LogRecord | None],
112
- env: EnvironmentType,
113
- ) -> None:
114
- """Поток для обработки логов из очереди."""
115
-
116
- formatter = _create_formatter(env)
117
- stdout_handler = logging.StreamHandler(sys.stdout)
118
- stdout_handler.setFormatter(formatter)
119
-
120
- while True:
121
- try:
122
- record = log_queue.get()
123
- if record is None: # Сигнал для завершения
124
- break
125
- stdout_handler.emit(record)
126
- except (KeyboardInterrupt, SystemExit):
127
- break
128
- except Exception:
129
- import traceback
130
-
131
- traceback.print_exc(file=sys.stderr)
132
-
133
-
134
- # -----------------------------------------------------------------------------
135
- # Общая очередь и поток (синглтоны)
136
- # -----------------------------------------------------------------------------
137
- # Все экземпляры Logger с use_queue=True используют одну очередь и один поток.
138
- # Это стандартная практика для асинхронного логирования: один listener на всё
139
- # приложение — меньше памяти и контекстных переключений, единый формат вывода.
140
-
141
- _log_queue: queue.Queue[logging.LogRecord | None] | None = None
142
- _log_thread: threading.Thread | None = None
143
- _log_lock = threading.Lock()
144
-
145
-
146
- def _get_log_queue(env: EnvironmentType) -> queue.Queue[logging.LogRecord | None]:
147
- """Получает или создаёт глобальную очередь для логов (один раз на приложение)."""
148
- global _log_queue, _log_thread
149
-
150
- if _log_queue is None:
151
- with _log_lock:
152
- if _log_queue is None:
153
- _log_queue = queue.Queue(-1) # -1 = без ограничения размера
154
- _log_thread = threading.Thread(
155
- target=_log_listener_thread,
156
- args=(_log_queue, env),
157
- daemon=True,
158
- name="LogListenerThread",
159
- )
160
- _log_thread.start()
161
-
162
- return _log_queue
163
-
164
-
165
- # ============================================================================
166
- # ОБЁРТКА ЛОГГЕРА С TRACE_ID
167
- # ============================================================================
168
-
169
-
170
- class TracingLogger:
171
- """
172
- Обёртка над logging.Logger с методами set_trace_id / clear_trace_id.
173
-
174
- trace_id хранится в экземпляре: простой вариант, не зависит от contextvars.
175
- Передавайте этот логгер по цепочке вызовов — везде будет один и тот же trace_id.
176
-
177
- Ограничение: один экземпляр логгера не должен использоваться из нескольких
178
- параллельных asyncio-задач с разными trace_id (будут перезаписывать друг друга).
179
- Для цикла «одна задача за раз» — подходит идеально.
180
- """
181
-
182
- def __init__(self, logger: logging.Logger) -> None:
183
- self._logger = logger
184
- self._trace_id: str | uuid.UUID | None = None
185
-
186
- def set_trace_id(self, value: str | uuid.UUID | None) -> None:
187
- """Устанавливает trace_id для этого экземпляра логгера."""
188
- self._trace_id = value
189
-
190
- def clear_trace_id(self) -> None:
191
- """Сбрасывает trace_id."""
192
- self._trace_id = None
193
-
194
- def _merge_extra(self, kwargs: dict) -> dict:
195
- extra = dict(kwargs.get("extra") or {})
196
- if self._trace_id is not None:
197
- extra["trace_id"] = str(self._trace_id)
198
- return {**kwargs, "extra": extra}
199
-
200
- def debug(self, msg: str, *args: Any, **kwargs: Any) -> None:
201
- self._logger.debug(msg, *args, **self._merge_extra(kwargs))
202
-
203
- def info(self, msg: str, *args: Any, **kwargs: Any) -> None:
204
- self._logger.info(msg, *args, **self._merge_extra(kwargs))
205
-
206
- def warning(self, msg: str, *args: Any, **kwargs: Any) -> None:
207
- self._logger.warning(msg, *args, **self._merge_extra(kwargs))
208
-
209
- def error(self, msg: str, *args: Any, **kwargs: Any) -> None:
210
- self._logger.error(msg, *args, **self._merge_extra(kwargs))
211
-
212
- def exception(self, msg: str, *args: Any, **kwargs: Any) -> None:
213
- self._logger.exception(msg, *args, **self._merge_extra(kwargs))
214
-
215
- def critical(self, msg: str, *args: Any, **kwargs: Any) -> None:
216
- self._logger.critical(msg, *args, **self._merge_extra(kwargs))
217
-
218
- def log(self, level: int, msg: str, *args: Any, **kwargs: Any) -> None:
219
- self._logger.log(level, msg, *args, **self._merge_extra(kwargs))
220
-
221
- @property
222
- def logger(self) -> logging.Logger:
223
- """Доступ к исходному logging.Logger при необходимости."""
224
- return self._logger
225
-
226
-
227
- # ============================================================================
228
- # КЛАСС LOGGER — ЕДИНАЯ ТОЧКА ВХОДА ДЛЯ ЛОГГЕРОВ
229
- # ============================================================================
230
-
231
-
232
- class Logger:
233
- """
234
- Фабрика логгеров с фиксированной средой (env) и общей очередью.
235
-
236
- Создаётся один раз на уровне приложения с нужной средой (DEV/PROD/...).
237
- В разных местах из неё получают логгеры через get_logger(name, job=...).
238
- job передаётся в get_logger, а не в конструктор — у каждого логгера свой job.
239
-
240
- Все экземпляры Logger с use_queue=True пишут в одну общую очередь и один
241
- фоновый поток (синглтоны на уровне модуля).
242
-
243
- Использование:
244
- from tp_helper.logging import Logger
245
- from tp_helper.types.environment_type import EnvironmentType
246
-
247
- # Один раз на уровне приложения — фабрика с желаемой средой
248
- log_factory = Logger(EnvironmentType.DEV)
249
-
250
- # В разных местах получаем логгеры (job — при необходимости)
251
- logger = log_factory.get_logger("api_service")
252
- worker_logger = log_factory.get_logger("task_worker", job="task_123")
253
-
254
- # trace_id для сквозной трассировки (например, в воркере по одной задаче)
255
- # logger.set_trace_id(uuid.uuid4())
256
- # logger.debug("Получена задача", extra=task) # в логе будет trace_id
257
-
258
- # Настройка uvicorn/fastapi при старте
259
- Logger.setup_standard_loggers(EnvironmentType.DEV)
260
- """
261
-
262
- def __init__(self, env: EnvironmentType) -> None:
263
- """
264
- Args:
265
- env: Окружение (local/dev/staging/prod) для поля в логах.
266
- """
267
- self._env = env
268
-
269
- def get_logger(
270
- self,
271
- name: str | int,
272
- job: str | uuid.UUID | int | None = None,
273
- # loki_handler: LokiHandler | None = None,
274
- use_queue: bool = True,
275
- ) -> TracingLogger:
276
- """
277
- Возвращает логгер с именем name и опциональным job.
278
-
279
- Возвращаемый объект — TracingLogger (обёртка с set_trace_id/clear_trace_id).
280
- trace_id хранится в экземпляре и попадает во все записи лога; можно
281
- передавать логгер по цепочке вызовов.
282
-
283
- Args:
284
- name: Имя логгера (идентификация в логах).
285
- job: Идентификатор задачи (опционально), попадает в статические поля.
286
- loki_handler: Опциональный handler для отправки в Loki.
287
- use_queue: True — асинхронная запись через общую очередь (по умолчанию).
288
-
289
- Returns:
290
- TracingLogger с методами set_trace_id/clear_trace_id.
291
- """
292
- log_queue = _get_log_queue(self._env) if use_queue else None
293
-
294
- logger = logging.getLogger(f"app_logger_{name}")
295
- logger.setLevel(logging.DEBUG)
296
- logger.propagate = False
297
- logger.addFilter(ExceptionFormatterFilter())
298
-
299
- if use_queue and log_queue:
300
- logger.addHandler(QueueHandler(log_queue))
301
- else:
302
- formatter = _create_formatter(self._env, job)
303
- stdout_handler = logging.StreamHandler(sys.stdout)
304
- stdout_handler.setFormatter(formatter)
305
- logger.addHandler(stdout_handler)
306
-
307
- # if loki_handler:
308
- # formatter = _create_formatter(self._env, job)
309
- # loki_handler.setFormatter(formatter)
310
- # logger.addHandler(loki_handler)
311
-
312
- return TracingLogger(logger)
313
-
314
- @staticmethod
315
- def setup_standard_loggers(env: EnvironmentType) -> None:
316
- """
317
- Настраивает логирование для uvicorn и fastapi (единый JSON-формат).
318
-
319
- Вызывать при инициализации API-приложения.
320
- """
321
- json_formatter = _create_formatter(env)
322
- standard_handler = logging.StreamHandler(sys.stdout)
323
- standard_handler.setFormatter(json_formatter)
324
-
325
- uvicorn_logger = logging.getLogger("uvicorn")
326
- uvicorn_logger.setLevel(logging.INFO)
327
- uvicorn_logger.handlers = [standard_handler]
328
- uvicorn_logger.propagate = False
329
-
330
- uvicorn_access_logger = logging.getLogger("uvicorn.access")
331
- uvicorn_access_logger.setLevel(logging.WARNING)
332
- uvicorn_access_logger.propagate = False
333
-
334
- fastapi_logger = logging.getLogger("fastapi")
335
- fastapi_logger.setLevel(logging.INFO)
336
- fastapi_logger.handlers = [standard_handler]
337
- fastapi_logger.propagate = False
338
-
339
- logging.getLogger("src.infrastructure.clients").setLevel(logging.INFO)
File without changes