road24-artifacthub 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: road24-artifacthub
3
+ Version: 0.1.0
4
+ Summary: Shared logging and metrics library for Road24 FastAPI microservices
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: build>=1.4.0
7
+ Requires-Dist: prometheus-client>=0.20.0
8
+ Requires-Dist: twine>=6.2.0
9
+ Provides-Extra: all
10
+ Requires-Dist: fastapi>=0.100.0; extra == 'all'
11
+ Requires-Dist: httpx>=0.27.0; extra == 'all'
12
+ Requires-Dist: redis>=5.0.0; extra == 'all'
13
+ Requires-Dist: sqlalchemy>=2.0.45; extra == 'all'
14
+ Requires-Dist: starlette>=0.27.0; extra == 'all'
15
+ Provides-Extra: dev
16
+ Requires-Dist: faker>=25.0.0; extra == 'dev'
17
+ Requires-Dist: fastapi>=0.100.0; extra == 'dev'
18
+ Requires-Dist: httpx>=0.27.0; extra == 'dev'
19
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
20
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
21
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
22
+ Requires-Dist: redis>=5.0.0; extra == 'dev'
23
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
24
+ Requires-Dist: sqlalchemy>=2.0.45; extra == 'dev'
25
+ Requires-Dist: starlette>=0.27.0; extra == 'dev'
26
+ Provides-Extra: fastapi
27
+ Requires-Dist: fastapi>=0.100.0; extra == 'fastapi'
28
+ Requires-Dist: starlette>=0.27.0; extra == 'fastapi'
29
+ Provides-Extra: httpx
30
+ Requires-Dist: httpx>=0.27.0; extra == 'httpx'
31
+ Provides-Extra: redis
32
+ Requires-Dist: redis>=5.0.0; extra == 'redis'
33
+ Provides-Extra: sqlalchemy
34
+ Requires-Dist: sqlalchemy>=2.0.45; extra == 'sqlalchemy'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # road24-artifacthub
38
+
39
+ Shared logging and metrics SDK for Road24 microservices. Provides structured JSON logging, Prometheus metrics, and framework-specific integrations following a Sentry SDK-like pattern.
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ # Core library
45
+ pip install road24-artifacthub
46
+
47
+ # With all integrations
48
+ pip install road24-artifacthub[all]
49
+
50
+ # Development (includes test tools)
51
+ pip install road24-artifacthub[dev]
52
+ ```
53
+
54
+ ## Quick Start
55
+
56
+ ```python
57
+ import road24_sdk
58
+ from fastapi import FastAPI
59
+ from road24_sdk.integrations.fastapi import FastApiLoggingIntegration
60
+ from road24_sdk.integrations.httpx import HttpxLoggingIntegration
61
+ from road24_sdk.integrations.redis import RedisLoggingIntegration
62
+ from road24_sdk.integrations.sqlalchemy import SqlalchemyLoggingIntegration
63
+
64
+ road24_sdk.init(
65
+ service_name="my-service",
66
+ log_level="INFO",
67
+ integrations=[
68
+ FastApiLoggingIntegration(),
69
+ HttpxLoggingIntegration(),
70
+ SqlalchemyLoggingIntegration(),
71
+ RedisLoggingIntegration(),
72
+ ],
73
+ )
74
+
75
+ # FastAPI still requires .setup(app) since it needs the app instance
76
+ app = FastAPI()
77
+ FastApiLoggingIntegration().setup(app)
78
+
79
+ # All other clients are auto-instrumented — no .setup() needed
80
+ client = AsyncClient(timeout=Timeout(25.0)) # already instrumented
81
+ ```
82
+
83
+ ## Available Integrations
84
+
85
+ | Integration | Extra | Description |
86
+ |---|---|---|
87
+ | `FastApiLoggingIntegration` | `fastapi` | Inbound HTTP request/response logging and metrics |
88
+ | `HttpxLoggingIntegration` | `httpx` | Outbound HTTP request/response logging and metrics |
89
+ | `SqlalchemyLoggingIntegration` | `sqlalchemy` | Database query logging and metrics |
90
+ | `RedisLoggingIntegration` | `redis` | Redis command logging and metrics |
91
+
92
+ Pass integrations to `init()` for automatic class-level patching. All new client instances are auto-instrumented.
93
+
94
+ The `.setup(client)` method is still available for per-instance patching (backwards compatible):
95
+
96
+ ```python
97
+ # Per-instance patching (still works)
98
+ client = AsyncClient(timeout=Timeout(25.0))
99
+ HttpxLoggingIntegration().setup(client)
100
+ ```
101
+
102
+ ## Manual Testing
103
+
104
+ Standalone scripts to inspect structured JSON logs and metrics (no server needed):
105
+
106
+ ```bash
107
+ make test-logs-http-input # Inbound HTTP request log
108
+ make test-logs-http-output # Outbound HTTPX request log
109
+ make test-metrics-http # Prometheus metrics after HTTP calls
110
+ make test-logs-redis # Redis command logs (SET, GET, HSET, etc.)
111
+ make test-logs-db # DB query logs (SELECT, INSERT, UPDATE, DELETE)
112
+ ```
113
+
114
+ ## Development
115
+
116
+ ```bash
117
+ make install # Install all dependencies (requires uv)
118
+ make test # Run tests with coverage
119
+ make lint # Lint with ruff
120
+ make format # Format with ruff
121
+ make check # Lint + test
122
+ ```
@@ -0,0 +1,18 @@
1
+ road24_sdk/__init__.py,sha256=BAqBQRTI5hwt4sewrvnFzpyl9E_FTRiKYbgqZsj3bKg,1444
2
+ road24_sdk/_formatter.py,sha256=OgiVmVidAWzP3VPQuW_WxJynyU7LFBobecXV026CKrA,5495
3
+ road24_sdk/_sanitizer.py,sha256=RxllcSa0QZC4e500hhMDotMZau9MnmAJUr7YbcZlmr0,1957
4
+ road24_sdk/_schemas.py,sha256=HOlBsIZSN2sm3s2MMbp-JoGGju_jjgZPmm8uNc4BCeI,3026
5
+ road24_sdk/_types.py,sha256=OfRdYJhIAeMOtjPPCx-W9vXXAxYvuKjq37NzCubWIg8,1071
6
+ road24_sdk/integrations/__init__.py,sha256=uCtjD-5z2lQYA1j4jN9pFw1azS095L7BVtoxzz-Rl3c,497
7
+ road24_sdk/integrations/_base.py,sha256=IePXuVsvKmDDf3Aq411KzurXkkq5opelz9nqeXeCr2o,489
8
+ road24_sdk/integrations/fastapi.py,sha256=7DN5jwR4SWMNKSDWu4yYecCP8w0pgktKqeuLMliBHB8,8677
9
+ road24_sdk/integrations/httpx.py,sha256=TZySabu04UnUk0Q7LUlszQzTqpj_lZOvqPHN4QOdCT8,4223
10
+ road24_sdk/integrations/redis.py,sha256=KvLilhaZywGSrUJ4hL7T5BqfKGZWxSYQ7BADRVS5Fq0,7401
11
+ road24_sdk/integrations/sqlalchemy.py,sha256=WOhFpUt7RfknhJL253MxNif16OKK8GJlXQ87sDsN9WQ,5055
12
+ road24_sdk/metrics/__init__.py,sha256=vvXfTFdHYnYHkN9REgqvqiGsTkXnszVKXnqHkb5C1FQ,257
13
+ road24_sdk/metrics/db.py,sha256=Lqqy7k1qF8D9sdxEl4qDP5pwqVHXgo1ucDHgUYD4exs,780
14
+ road24_sdk/metrics/http.py,sha256=Ynbf-IvJwkzU6G2eRNWf99CH8ef-iR7CxfEvHR66OvQ,949
15
+ road24_sdk/metrics/redis.py,sha256=qdDuG-2px7_Hp1eiFRc4x8F0MG7KbcuiDiFxw8QsavA,805
16
+ road24_artifacthub-0.1.0.dist-info/METADATA,sha256=DnB13VHVn2yG1vY2WoRwYoP9QJR_SbTledydFRCBa50,4131
17
+ road24_artifacthub-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
18
+ road24_artifacthub-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
road24_sdk/__init__.py ADDED
@@ -0,0 +1,65 @@
1
+ import logging
2
+
3
+ from road24_sdk._formatter import setup_logging
4
+ from road24_sdk._types import (
5
+ ArtifactHubConfig,
6
+ DbOperation,
7
+ ExceptionType,
8
+ HttpDirection,
9
+ LogType,
10
+ RedisOperation,
11
+ )
12
+ from road24_sdk.integrations._base import Integration
13
+
14
+ _config: ArtifactHubConfig | None = None
15
+ _logger = logging.getLogger(__name__)
16
+
17
+
18
+ def init(
19
+ service_name: str = "unknown",
20
+ log_level: str = "INFO",
21
+ debug: bool = False,
22
+ log_format: str = "json",
23
+ integrations: list[Integration] | None = None,
24
+ ) -> None:
25
+ global _config
26
+ resolved = integrations or []
27
+ _config = ArtifactHubConfig(
28
+ service_name=service_name,
29
+ log_level=log_level,
30
+ debug=debug,
31
+ log_format=log_format,
32
+ integrations=resolved,
33
+ )
34
+ setup_logging(
35
+ level=log_level,
36
+ service_name=service_name,
37
+ debug=debug,
38
+ )
39
+ for integration in resolved:
40
+ try:
41
+ integration.setup_once()
42
+ except Exception:
43
+ _logger.warning(
44
+ "Failed to setup integration %s",
45
+ type(integration).__name__,
46
+ exc_info=True,
47
+ )
48
+
49
+
50
+ def configure() -> ArtifactHubConfig:
51
+ if _config is None:
52
+ return ArtifactHubConfig()
53
+ return _config
54
+
55
+
56
+ __all__ = [
57
+ "ArtifactHubConfig",
58
+ "DbOperation",
59
+ "ExceptionType",
60
+ "HttpDirection",
61
+ "LogType",
62
+ "RedisOperation",
63
+ "configure",
64
+ "init",
65
+ ]
@@ -0,0 +1,213 @@
1
+ import json
2
+ import logging
3
+ import sys
4
+ from contextvars import ContextVar, Token
5
+ from datetime import UTC, datetime
6
+ from typing import Any
7
+
8
+ from road24_sdk._schemas import ExceptionAttributes, LogRecord
9
+ from road24_sdk._types import ExceptionType, LogType
10
+
11
+ STANDARD_RECORD_ATTRS = {
12
+ "name",
13
+ "msg",
14
+ "args",
15
+ "created",
16
+ "filename",
17
+ "funcName",
18
+ "levelname",
19
+ "levelno",
20
+ "lineno",
21
+ "module",
22
+ "msecs",
23
+ "pathname",
24
+ "process",
25
+ "processName",
26
+ "relativeCreated",
27
+ "stack_info",
28
+ "exc_info",
29
+ "exc_text",
30
+ "thread",
31
+ "threadName",
32
+ "taskName",
33
+ "message",
34
+ }
35
+
36
+ ZERO_TRACE_ID = "0" * 32
37
+
38
+ _trace_id: ContextVar[str] = ContextVar("trace_id", default="")
39
+
40
+ _service_name: str = "unknown"
41
+
42
+ SILENT_LOGGERS_DEBUG = [
43
+ "uvicorn.access",
44
+ "asyncio",
45
+ ]
46
+
47
+ SILENT_LOGGERS_PROD = [
48
+ "uvicorn",
49
+ "uvicorn.access",
50
+ "uvicorn.error",
51
+ "uvicorn.asgi",
52
+ "sqlalchemy",
53
+ "sqlalchemy.engine",
54
+ "sqlalchemy.pool",
55
+ "httpx",
56
+ "httpcore",
57
+ "asyncio",
58
+ ]
59
+
60
+
61
+ class LogFormatter(logging.Formatter):
62
+ def format(self, record: logging.LogRecord) -> str:
63
+ log_record = LogRecord(
64
+ timestamp=datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z",
65
+ trace_id=_trace_id.get() or ZERO_TRACE_ID,
66
+ log_level=record.levelname,
67
+ type=record.getMessage(),
68
+ service_name=_service_name,
69
+ attributes=self._build_attributes(record),
70
+ )
71
+ return json.dumps(log_record.as_dict(), ensure_ascii=False, default=str)
72
+
73
+ def _build_attributes(self, record: logging.LogRecord) -> dict[str, Any]:
74
+ attrs: dict[str, Any] = {
75
+ "logger_name": record.name,
76
+ "code_module": record.module,
77
+ "code_function": record.funcName,
78
+ "code_lineno": record.lineno,
79
+ }
80
+
81
+ attrs.update(
82
+ {
83
+ k: v
84
+ for k, v in record.__dict__.items()
85
+ if k not in STANDARD_RECORD_ATTRS and not k.startswith("_")
86
+ }
87
+ )
88
+
89
+ if record.exc_info and (exc_type := record.exc_info[0]):
90
+ attrs.update(
91
+ {
92
+ "exception_type": exc_type.__name__,
93
+ "exception_message": str(record.exc_info[1]),
94
+ "exception_stacktrace": self.formatException(record.exc_info),
95
+ }
96
+ )
97
+
98
+ return attrs
99
+
100
+
101
+ def set_trace_context(trace_id: str) -> Token[str]:
102
+ return _trace_id.set(trace_id)
103
+
104
+
105
+ def clear_trace_context(token: Token[str] | None = None) -> None:
106
+ if token is not None:
107
+ _trace_id.reset(token)
108
+ else:
109
+ _trace_id.set("")
110
+
111
+
112
+ def get_trace_id() -> str:
113
+ return _trace_id.get() or ZERO_TRACE_ID
114
+
115
+
116
+ def setup_logging(
117
+ level: str = "INFO",
118
+ service_name: str = "unknown",
119
+ debug: bool = False,
120
+ ) -> None:
121
+ global _service_name
122
+ _service_name = service_name
123
+
124
+ log_level = getattr(logging, level.upper(), logging.INFO)
125
+
126
+ root_logger = logging.getLogger()
127
+ root_logger.setLevel(log_level)
128
+ root_logger.handlers.clear()
129
+
130
+ handler = logging.StreamHandler(sys.stdout)
131
+ handler.setFormatter(LogFormatter())
132
+ handler.setLevel(log_level)
133
+ root_logger.addHandler(handler)
134
+
135
+ _silence_libraries(debug, level)
136
+
137
+
138
+ def _silence_libraries(debug: bool, level: str) -> None:
139
+ is_debug = debug or level.upper() == "DEBUG"
140
+
141
+ if is_debug:
142
+ loggers = SILENT_LOGGERS_DEBUG
143
+ silence_level = logging.WARNING
144
+ else:
145
+ loggers = SILENT_LOGGERS_PROD
146
+ silence_level = logging.CRITICAL
147
+
148
+ for name in loggers:
149
+ lib_logger = logging.getLogger(name)
150
+ lib_logger.setLevel(silence_level)
151
+ lib_logger.propagate = False
152
+
153
+
154
+ class ExceptionLogger:
155
+ def log_http_error(
156
+ self,
157
+ method: str,
158
+ url: str,
159
+ path: str,
160
+ status_code: int,
161
+ detail: str,
162
+ exc: Exception,
163
+ ) -> None:
164
+ attrs = ExceptionAttributes(
165
+ exception_type=ExceptionType.HTTP,
166
+ error_class=type(exc).__name__,
167
+ error_message=detail,
168
+ status_code=status_code,
169
+ method=method,
170
+ url=url,
171
+ path=path,
172
+ )
173
+ logger = logging.getLogger(__name__)
174
+ logger.error(LogType.EXCEPTION, exc_info=exc, extra=attrs.as_dict())
175
+
176
+ def log_validation_error(
177
+ self,
178
+ method: str,
179
+ url: str,
180
+ path: str,
181
+ errors: list[Any],
182
+ ) -> None:
183
+ attrs = ExceptionAttributes(
184
+ exception_type=ExceptionType.VALIDATION,
185
+ error_class="RequestValidationError",
186
+ error_message=f"Validation error: {len(errors)} error(s)",
187
+ status_code=422,
188
+ method=method,
189
+ url=url,
190
+ path=path,
191
+ details=errors,
192
+ )
193
+ logger = logging.getLogger(__name__)
194
+ logger.warning(LogType.EXCEPTION, extra=attrs.as_dict())
195
+
196
+ def log_general_error(
197
+ self,
198
+ method: str,
199
+ url: str,
200
+ path: str,
201
+ exc: Exception,
202
+ ) -> None:
203
+ attrs = ExceptionAttributes(
204
+ exception_type=ExceptionType.GENERAL,
205
+ error_class=type(exc).__name__,
206
+ error_message=str(exc),
207
+ status_code=500,
208
+ method=method,
209
+ url=url,
210
+ path=path,
211
+ )
212
+ logger = logging.getLogger(__name__)
213
+ logger.error(LogType.EXCEPTION, exc_info=exc, extra=attrs.as_dict())
@@ -0,0 +1,61 @@
1
+ import json
2
+ import re
3
+ from typing import Any
4
+
5
+ SENSITIVE_PATTERNS: tuple[re.Pattern[str], ...] = (
6
+ re.compile(r"password", re.IGNORECASE),
7
+ re.compile(r"token", re.IGNORECASE),
8
+ re.compile(r"secret", re.IGNORECASE),
9
+ re.compile(r"api[_-]?key", re.IGNORECASE),
10
+ re.compile(r"auth", re.IGNORECASE),
11
+ re.compile(r"credential", re.IGNORECASE),
12
+ re.compile(r"private[_-]?key", re.IGNORECASE),
13
+ re.compile(r"access[_-]?key", re.IGNORECASE),
14
+ re.compile(r"session[_-]?id", re.IGNORECASE),
15
+ re.compile(r"cookie", re.IGNORECASE),
16
+ re.compile(r"bearer", re.IGNORECASE),
17
+ re.compile(r"pin", re.IGNORECASE),
18
+ re.compile(r"cvv", re.IGNORECASE),
19
+ re.compile(r"card[_-]?number", re.IGNORECASE),
20
+ )
21
+
22
+ MASK = "***REDACTED***"
23
+
24
+
25
+ def is_sensitive_key(key: str) -> bool:
26
+ return any(pattern.search(key) for pattern in SENSITIVE_PATTERNS)
27
+
28
+
29
+ def sanitize_dict(data: dict[str, Any]) -> dict[str, Any]:
30
+ result = {}
31
+ for key, value in data.items():
32
+ if is_sensitive_key(key):
33
+ result[key] = MASK
34
+ elif isinstance(value, dict):
35
+ result[key] = sanitize_dict(value)
36
+ elif isinstance(value, list):
37
+ result[key] = [
38
+ sanitize_dict(item) if isinstance(item, dict) else item for item in value
39
+ ]
40
+ else:
41
+ result[key] = value
42
+ return result
43
+
44
+
45
+ def sanitize_body(body: str) -> str:
46
+ if not body or not body.strip():
47
+ return body
48
+
49
+ try:
50
+ data = json.loads(body)
51
+ if isinstance(data, dict):
52
+ sanitized = sanitize_dict(data)
53
+ return json.dumps(sanitized, ensure_ascii=False)
54
+ if isinstance(data, list):
55
+ sanitized_list = [
56
+ sanitize_dict(item) if isinstance(item, dict) else item for item in data
57
+ ]
58
+ return json.dumps(sanitized_list, ensure_ascii=False)
59
+ return body
60
+ except (json.JSONDecodeError, TypeError):
61
+ return body
road24_sdk/_schemas.py ADDED
@@ -0,0 +1,117 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Any
3
+
4
+ from road24_sdk._types import (
5
+ DbOperation,
6
+ ExceptionType,
7
+ HttpDirection,
8
+ RedisOperation,
9
+ )
10
+
11
+
12
+ @dataclass(slots=True)
13
+ class LogRecord:
14
+ timestamp: str
15
+ trace_id: str
16
+ log_level: str
17
+ type: str
18
+ service_name: str
19
+ attributes: dict[str, Any] = field(default_factory=dict)
20
+
21
+ def as_dict(self) -> dict[str, Any]:
22
+ return {
23
+ "timestamp": self.timestamp,
24
+ "trace_id": self.trace_id,
25
+ "log_level": self.log_level,
26
+ "type": self.type,
27
+ "service_name": self.service_name,
28
+ "attributes": self.attributes,
29
+ }
30
+
31
+
32
+ @dataclass(slots=True)
33
+ class HttpAttributes:
34
+ direction: HttpDirection
35
+ method: str
36
+ url: str
37
+ target: str
38
+ status_code: int
39
+ duration_seconds: float
40
+ request_body: str = ""
41
+ response_body: str = ""
42
+ error_class: str = ""
43
+ error_message: str = ""
44
+
45
+ def as_dict(self) -> dict[str, Any]:
46
+ result: dict[str, Any] = {
47
+ "direction": self.direction.value,
48
+ "method": self.method,
49
+ "url": self.url,
50
+ "target": self.target,
51
+ "status_code": self.status_code,
52
+ "duration_seconds": self.duration_seconds,
53
+ "request_body": self.request_body,
54
+ "response_body": self.response_body,
55
+ }
56
+ if self.error_class:
57
+ result["error_class"] = self.error_class
58
+ result["error_message"] = self.error_message
59
+ return result
60
+
61
+
62
+ @dataclass(slots=True)
63
+ class DbAttributes:
64
+ operation: DbOperation
65
+ table: str
66
+ duration_seconds: float
67
+ statement: str = ""
68
+
69
+ def as_dict(self) -> dict[str, Any]:
70
+ return {
71
+ "operation": self.operation.value,
72
+ "table": self.table,
73
+ "duration_seconds": self.duration_seconds,
74
+ "statement": self.statement,
75
+ }
76
+
77
+
78
+ @dataclass(slots=True)
79
+ class RedisAttributes:
80
+ operation: RedisOperation
81
+ key: str
82
+ duration_seconds: float
83
+ command_args: str = ""
84
+
85
+ def as_dict(self) -> dict[str, Any]:
86
+ return {
87
+ "operation": self.operation.value,
88
+ "key": self.key,
89
+ "duration_seconds": self.duration_seconds,
90
+ "command_args": self.command_args,
91
+ }
92
+
93
+
94
+ @dataclass(slots=True)
95
+ class ExceptionAttributes:
96
+ exception_type: ExceptionType
97
+ error_class: str
98
+ error_message: str
99
+ status_code: int
100
+ method: str
101
+ url: str
102
+ path: str
103
+ details: Any = None
104
+
105
+ def as_dict(self) -> dict[str, Any]:
106
+ result: dict[str, Any] = {
107
+ "exception_type": self.exception_type.value,
108
+ "error_class": self.error_class,
109
+ "error_message": self.error_message,
110
+ "status_code": self.status_code,
111
+ "method": self.method,
112
+ "url": self.url,
113
+ "path": self.path,
114
+ }
115
+ if self.details is not None:
116
+ result["details"] = self.details
117
+ return result
road24_sdk/_types.py ADDED
@@ -0,0 +1,54 @@
1
+ from dataclasses import dataclass, field
2
+ from enum import StrEnum
3
+ from typing import Any
4
+
5
+
6
+ @dataclass(slots=True)
7
+ class ArtifactHubConfig:
8
+ service_name: str = "unknown"
9
+ log_level: str = "INFO"
10
+ debug: bool = False
11
+ log_format: str = "json"
12
+ integrations: list[Any] = field(default_factory=list)
13
+
14
+
15
+ class LogType(StrEnum):
16
+ HTTP_REQUEST = "http_request"
17
+ DB_QUERY = "db_query"
18
+ REDIS_COMMAND = "redis_command"
19
+ EXCEPTION = "exception"
20
+
21
+
22
+ class ExceptionType(StrEnum):
23
+ HTTP = "http"
24
+ VALIDATION = "validation"
25
+ GENERAL = "general"
26
+
27
+
28
+ class HttpDirection(StrEnum):
29
+ INPUT = "input"
30
+ OUTPUT = "output"
31
+
32
+
33
+ class DbOperation(StrEnum):
34
+ SELECT = "select"
35
+ INSERT = "insert"
36
+ UPDATE = "update"
37
+ DELETE = "delete"
38
+ OTHER = "other"
39
+
40
+
41
+ class RedisOperation(StrEnum):
42
+ GET = "get"
43
+ SET = "set"
44
+ DELETE = "delete"
45
+ EXPIRE = "expire"
46
+ HGET = "hget"
47
+ HSET = "hset"
48
+ LPUSH = "lpush"
49
+ RPUSH = "rpush"
50
+ LPOP = "lpop"
51
+ RPOP = "rpop"
52
+ PUBLISH = "publish"
53
+ SUBSCRIBE = "subscribe"
54
+ OTHER = "other"
@@ -0,0 +1,13 @@
1
+ from road24_sdk.integrations._base import Integration
2
+ from road24_sdk.integrations.fastapi import FastApiLoggingIntegration
3
+ from road24_sdk.integrations.httpx import HttpxLoggingIntegration
4
+ from road24_sdk.integrations.redis import RedisLoggingIntegration
5
+ from road24_sdk.integrations.sqlalchemy import SqlalchemyLoggingIntegration
6
+
7
+ __all__ = [
8
+ "FastApiLoggingIntegration",
9
+ "HttpxLoggingIntegration",
10
+ "Integration",
11
+ "RedisLoggingIntegration",
12
+ "SqlalchemyLoggingIntegration",
13
+ ]
@@ -0,0 +1,19 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class Integration(ABC):
5
+ @abstractmethod
6
+ def setup_once(self) -> None: ...
7
+
8
+ @classmethod
9
+ def _mark_setup_once(cls) -> bool:
10
+ flag = f"_setup_once_called_{cls.__name__}"
11
+ if getattr(cls, flag, False):
12
+ return True
13
+ setattr(cls, flag, True)
14
+ return False
15
+
16
+ @classmethod
17
+ def _reset_setup_once(cls) -> None:
18
+ flag = f"_setup_once_called_{cls.__name__}"
19
+ setattr(cls, flag, False)