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,175 @@
1
+ import logging
2
+ import re
3
+ import time
4
+ from typing import Any
5
+
6
+ from road24_sdk._schemas import DbAttributes
7
+ from road24_sdk._types import DbOperation, LogType
8
+ from road24_sdk.integrations._base import Integration
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ _START_TIME_KEY = "_query_start_time"
13
+
14
+
15
+ class DbLogger:
16
+ MAX_STATEMENT_LENGTH = 500
17
+
18
+ TABLE_PATTERNS = [
19
+ r"FROM\s+([\"']?[\w\.]+[\"']?)",
20
+ r"INTO\s+([\"']?[\w\.]+[\"']?)",
21
+ r"UPDATE\s+([\"']?[\w\.]+[\"']?)",
22
+ r"DELETE\s+FROM\s+([\"']?[\w\.]+[\"']?)",
23
+ ]
24
+
25
+ def log_query(
26
+ self,
27
+ operation: DbOperation,
28
+ table: str,
29
+ duration_seconds: float,
30
+ statement: str = "",
31
+ record_metrics: bool = True,
32
+ ) -> None:
33
+ if record_metrics:
34
+ from road24_sdk.metrics.db import record_db_query
35
+
36
+ record_db_query(
37
+ operation=operation,
38
+ table=table,
39
+ duration_seconds=duration_seconds,
40
+ )
41
+
42
+ db_attrs = DbAttributes(
43
+ operation=operation,
44
+ table=table,
45
+ duration_seconds=duration_seconds,
46
+ statement=statement[: self.MAX_STATEMENT_LENGTH],
47
+ )
48
+
49
+ logger.info(LogType.DB_QUERY, extra=db_attrs.as_dict())
50
+
51
+ def before_execute(
52
+ self,
53
+ conn: Any,
54
+ cursor: Any,
55
+ statement: str,
56
+ parameters: Any,
57
+ context: Any,
58
+ executemany: bool,
59
+ ) -> None:
60
+ conn.info[_START_TIME_KEY] = time.perf_counter()
61
+
62
+ def after_execute(
63
+ self,
64
+ conn: Any,
65
+ cursor: Any,
66
+ statement: str,
67
+ parameters: Any,
68
+ context: Any,
69
+ executemany: bool,
70
+ record_metrics: bool = True,
71
+ ) -> None:
72
+ start_time = conn.info.pop(_START_TIME_KEY, None)
73
+ duration_seconds = time.perf_counter() - start_time if start_time else 0.0
74
+ operation = self.extract_operation(statement)
75
+ table = self.extract_table(statement)
76
+
77
+ self.log_query(
78
+ operation=operation,
79
+ table=table,
80
+ duration_seconds=duration_seconds,
81
+ statement=statement,
82
+ record_metrics=record_metrics,
83
+ )
84
+
85
+ def extract_operation(self, statement: str) -> DbOperation:
86
+ statement_upper = statement.strip().upper()
87
+
88
+ if statement_upper.startswith("SELECT"):
89
+ return DbOperation.SELECT
90
+ if statement_upper.startswith("INSERT"):
91
+ return DbOperation.INSERT
92
+ if statement_upper.startswith("UPDATE"):
93
+ return DbOperation.UPDATE
94
+ if statement_upper.startswith("DELETE"):
95
+ return DbOperation.DELETE
96
+
97
+ return DbOperation.OTHER
98
+
99
+ def extract_table(self, statement: str) -> str:
100
+ statement_upper = statement.strip().upper()
101
+
102
+ for pattern in self.TABLE_PATTERNS:
103
+ if match := re.search(pattern, statement_upper):
104
+ table = match.group(1).strip("\"'").lower()
105
+ return table.split(".")[-1]
106
+
107
+ return "unknown"
108
+
109
+
110
+ class SqlalchemyLoggingIntegration(Integration):
111
+ def __init__(
112
+ self,
113
+ *,
114
+ enable_logging: bool = True,
115
+ enable_metrics: bool = True,
116
+ ) -> None:
117
+ self._enable_logging = enable_logging
118
+ self._enable_metrics = enable_metrics
119
+ self._db_logger = DbLogger()
120
+
121
+ def setup_once(self) -> None:
122
+ if self._mark_setup_once():
123
+ return
124
+ from sqlalchemy import event
125
+ from sqlalchemy.engine import Engine
126
+
127
+ event.listen(Engine, "before_cursor_execute", self._db_logger.before_execute)
128
+
129
+ if self._enable_logging:
130
+ enable_metrics = self._enable_metrics
131
+ db_logger = self._db_logger
132
+
133
+ event.listen(
134
+ Engine,
135
+ "after_cursor_execute",
136
+ lambda conn, cursor, stmt, params, ctx, many: (
137
+ db_logger.after_execute(
138
+ conn,
139
+ cursor,
140
+ stmt,
141
+ params,
142
+ ctx,
143
+ many,
144
+ record_metrics=enable_metrics,
145
+ )
146
+ ),
147
+ )
148
+
149
+ def setup(self, engine: Any) -> None:
150
+ from sqlalchemy import event
151
+
152
+ sync_engine = getattr(engine, "sync_engine", engine)
153
+
154
+ event.listen(
155
+ sync_engine,
156
+ "before_cursor_execute",
157
+ self._db_logger.before_execute,
158
+ )
159
+
160
+ if self._enable_logging:
161
+ event.listen(
162
+ sync_engine,
163
+ "after_cursor_execute",
164
+ lambda conn, cursor, stmt, params, ctx, many: (
165
+ self._db_logger.after_execute(
166
+ conn,
167
+ cursor,
168
+ stmt,
169
+ params,
170
+ ctx,
171
+ many,
172
+ record_metrics=self._enable_metrics,
173
+ )
174
+ ),
175
+ )
@@ -0,0 +1,9 @@
1
+ from road24_sdk.metrics.db import record_db_query
2
+ from road24_sdk.metrics.http import record_http_request
3
+ from road24_sdk.metrics.redis import record_redis_command
4
+
5
+ __all__ = [
6
+ "record_db_query",
7
+ "record_http_request",
8
+ "record_redis_command",
9
+ ]
@@ -0,0 +1,29 @@
1
+ from prometheus_client import Counter, Histogram
2
+
3
+ from road24_sdk._types import DbOperation
4
+
5
+ DB_QUERY_DURATION = Histogram(
6
+ name="db_query_duration_seconds",
7
+ documentation="Database query duration in seconds",
8
+ labelnames=["operation", "table"],
9
+ buckets=(0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5),
10
+ )
11
+
12
+ DB_QUERY_TOTAL = Counter(
13
+ name="db_queries_total",
14
+ documentation="Total number of database queries",
15
+ labelnames=["operation", "table"],
16
+ )
17
+
18
+
19
+ def record_db_query(
20
+ operation: DbOperation,
21
+ table: str,
22
+ duration_seconds: float,
23
+ ) -> None:
24
+ labels = {
25
+ "operation": operation.value,
26
+ "table": table,
27
+ }
28
+ DB_QUERY_TOTAL.labels(**labels).inc()
29
+ DB_QUERY_DURATION.labels(**labels).observe(duration_seconds)
@@ -0,0 +1,33 @@
1
+ from prometheus_client import Counter, Histogram
2
+
3
+ from road24_sdk._types import HttpDirection
4
+
5
+ HTTP_REQUEST_DURATION = Histogram(
6
+ name="http_request_duration_seconds",
7
+ documentation="HTTP request duration in seconds",
8
+ labelnames=["method", "url", "status_code", "direction"],
9
+ buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
10
+ )
11
+
12
+ HTTP_REQUEST_TOTAL = Counter(
13
+ name="http_requests_total",
14
+ documentation="Total number of HTTP requests",
15
+ labelnames=["method", "url", "status_code", "direction"],
16
+ )
17
+
18
+
19
+ def record_http_request(
20
+ method: str,
21
+ url: str,
22
+ status_code: int,
23
+ duration_seconds: float,
24
+ direction: HttpDirection,
25
+ ) -> None:
26
+ labels = {
27
+ "method": method,
28
+ "url": url,
29
+ "status_code": str(status_code),
30
+ "direction": direction.value,
31
+ }
32
+ HTTP_REQUEST_TOTAL.labels(**labels).inc()
33
+ HTTP_REQUEST_DURATION.labels(**labels).observe(duration_seconds)
@@ -0,0 +1,29 @@
1
+ from prometheus_client import Counter, Histogram
2
+
3
+ from road24_sdk._types import RedisOperation
4
+
5
+ REDIS_COMMAND_DURATION = Histogram(
6
+ name="redis_command_duration_seconds",
7
+ documentation="Redis command duration in seconds",
8
+ labelnames=["operation", "key"],
9
+ buckets=(0.0005, 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5),
10
+ )
11
+
12
+ REDIS_COMMAND_TOTAL = Counter(
13
+ name="redis_commands_total",
14
+ documentation="Total number of Redis commands",
15
+ labelnames=["operation", "key"],
16
+ )
17
+
18
+
19
+ def record_redis_command(
20
+ operation: RedisOperation,
21
+ key: str,
22
+ duration_seconds: float,
23
+ ) -> None:
24
+ labels = {
25
+ "operation": operation.value,
26
+ "key": key,
27
+ }
28
+ REDIS_COMMAND_TOTAL.labels(**labels).inc()
29
+ REDIS_COMMAND_DURATION.labels(**labels).observe(duration_seconds)