jararaca 0.2.37a12__py3-none-any.whl → 0.4.0a5__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.
- README.md +121 -0
- jararaca/__init__.py +267 -15
- jararaca/__main__.py +4 -0
- jararaca/broker_backend/__init__.py +106 -0
- jararaca/broker_backend/mapper.py +25 -0
- jararaca/broker_backend/redis_broker_backend.py +168 -0
- jararaca/cli.py +840 -103
- jararaca/common/__init__.py +3 -0
- jararaca/core/__init__.py +3 -0
- jararaca/core/providers.py +4 -0
- jararaca/core/uow.py +55 -16
- jararaca/di.py +4 -0
- jararaca/files/entity.py.mako +4 -0
- jararaca/lifecycle.py +6 -2
- jararaca/messagebus/__init__.py +5 -1
- jararaca/messagebus/bus_message_controller.py +4 -0
- jararaca/messagebus/consumers/__init__.py +3 -0
- jararaca/messagebus/decorators.py +90 -85
- jararaca/messagebus/implicit_headers.py +49 -0
- jararaca/messagebus/interceptors/__init__.py +3 -0
- jararaca/messagebus/interceptors/aiopika_publisher_interceptor.py +95 -37
- jararaca/messagebus/interceptors/publisher_interceptor.py +42 -0
- jararaca/messagebus/message.py +31 -0
- jararaca/messagebus/publisher.py +47 -4
- jararaca/messagebus/worker.py +1615 -135
- jararaca/microservice.py +248 -36
- jararaca/observability/constants.py +7 -0
- jararaca/observability/decorators.py +177 -16
- jararaca/observability/fastapi_exception_handler.py +37 -0
- jararaca/observability/hooks.py +109 -0
- jararaca/observability/interceptor.py +8 -2
- jararaca/observability/providers/__init__.py +3 -0
- jararaca/observability/providers/otel.py +213 -18
- jararaca/persistence/base.py +40 -3
- jararaca/persistence/exports.py +4 -0
- jararaca/persistence/interceptors/__init__.py +3 -0
- jararaca/persistence/interceptors/aiosqa_interceptor.py +187 -23
- jararaca/persistence/interceptors/constants.py +5 -0
- jararaca/persistence/interceptors/decorators.py +50 -0
- jararaca/persistence/session.py +3 -0
- jararaca/persistence/sort_filter.py +4 -0
- jararaca/persistence/utilities.py +74 -32
- jararaca/presentation/__init__.py +3 -0
- jararaca/presentation/decorators.py +170 -82
- jararaca/presentation/exceptions.py +23 -0
- jararaca/presentation/hooks.py +4 -0
- jararaca/presentation/http_microservice.py +4 -0
- jararaca/presentation/server.py +120 -41
- jararaca/presentation/websocket/__init__.py +3 -0
- jararaca/presentation/websocket/base_types.py +4 -0
- jararaca/presentation/websocket/context.py +34 -4
- jararaca/presentation/websocket/decorators.py +8 -41
- jararaca/presentation/websocket/redis.py +280 -53
- jararaca/presentation/websocket/types.py +6 -2
- jararaca/presentation/websocket/websocket_interceptor.py +74 -23
- jararaca/reflect/__init__.py +3 -0
- jararaca/reflect/controller_inspect.py +81 -0
- jararaca/reflect/decorators.py +238 -0
- jararaca/reflect/metadata.py +76 -0
- jararaca/rpc/__init__.py +3 -0
- jararaca/rpc/http/__init__.py +101 -0
- jararaca/rpc/http/backends/__init__.py +14 -0
- jararaca/rpc/http/backends/httpx.py +43 -9
- jararaca/rpc/http/backends/otel.py +4 -0
- jararaca/rpc/http/decorators.py +378 -113
- jararaca/rpc/http/httpx.py +3 -0
- jararaca/scheduler/__init__.py +3 -0
- jararaca/scheduler/beat_worker.py +758 -0
- jararaca/scheduler/decorators.py +89 -28
- jararaca/scheduler/types.py +11 -0
- jararaca/tools/app_config/__init__.py +3 -0
- jararaca/tools/app_config/decorators.py +7 -19
- jararaca/tools/app_config/interceptor.py +10 -4
- jararaca/tools/typescript/__init__.py +3 -0
- jararaca/tools/typescript/decorators.py +120 -0
- jararaca/tools/typescript/interface_parser.py +1126 -189
- jararaca/utils/__init__.py +3 -0
- jararaca/utils/rabbitmq_utils.py +372 -0
- jararaca/utils/retry.py +148 -0
- jararaca-0.4.0a5.dist-info/LICENSE +674 -0
- jararaca-0.4.0a5.dist-info/LICENSES/GPL-3.0-or-later.txt +232 -0
- {jararaca-0.2.37a12.dist-info → jararaca-0.4.0a5.dist-info}/METADATA +14 -7
- jararaca-0.4.0a5.dist-info/RECORD +88 -0
- {jararaca-0.2.37a12.dist-info → jararaca-0.4.0a5.dist-info}/WHEEL +1 -1
- pyproject.toml +131 -0
- jararaca/messagebus/types.py +0 -30
- jararaca/scheduler/scheduler.py +0 -154
- jararaca/tools/metadata.py +0 -47
- jararaca-0.2.37a12.dist-info/RECORD +0 -63
- /jararaca-0.2.37a12.dist-info/LICENSE → /LICENSE +0 -0
- {jararaca-0.2.37a12.dist-info → jararaca-0.4.0a5.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import typing
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from typing import Any, Generator, Literal
|
|
9
|
+
|
|
10
|
+
from jararaca.observability.decorators import (
|
|
11
|
+
AttributeMap,
|
|
12
|
+
AttributeValue,
|
|
13
|
+
TracingContextProvider,
|
|
14
|
+
TracingSpan,
|
|
15
|
+
TracingSpanContext,
|
|
16
|
+
get_tracing_ctx_provider,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@contextmanager
|
|
21
|
+
def start_span(
|
|
22
|
+
name: str,
|
|
23
|
+
attributes: AttributeMap | None = None,
|
|
24
|
+
) -> Generator[None, Any, None]:
|
|
25
|
+
if trace_context_provider := get_tracing_ctx_provider():
|
|
26
|
+
with trace_context_provider.start_span_context(
|
|
27
|
+
trace_name=name, context_attributes=attributes
|
|
28
|
+
):
|
|
29
|
+
yield
|
|
30
|
+
else:
|
|
31
|
+
yield
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def spawn_trace(
|
|
35
|
+
name: str,
|
|
36
|
+
attributes: AttributeMap | None = None,
|
|
37
|
+
) -> typing.ContextManager[None]:
|
|
38
|
+
logging.warning(
|
|
39
|
+
"spawn_trace is deprecated, use start_span as context manager instead."
|
|
40
|
+
)
|
|
41
|
+
return start_span(name=name, attributes=attributes)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def add_event(
|
|
45
|
+
name: str,
|
|
46
|
+
attributes: AttributeMap | None = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
|
|
49
|
+
if trace_context_provider := get_tracing_ctx_provider():
|
|
50
|
+
trace_context_provider.add_event(
|
|
51
|
+
event_name=name,
|
|
52
|
+
event_attributes=attributes,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def set_span_status(status_code: Literal["OK", "ERROR", "UNSET"]) -> None:
|
|
57
|
+
|
|
58
|
+
if trace_context_provider := get_tracing_ctx_provider():
|
|
59
|
+
trace_context_provider.set_span_status(status_code=status_code)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def record_exception(
|
|
63
|
+
exception: Exception,
|
|
64
|
+
attributes: AttributeMap | None = None,
|
|
65
|
+
escaped: bool = False,
|
|
66
|
+
) -> None:
|
|
67
|
+
|
|
68
|
+
if trace_context_provider := get_tracing_ctx_provider():
|
|
69
|
+
trace_context_provider.record_exception(
|
|
70
|
+
exception=exception,
|
|
71
|
+
attributes=attributes,
|
|
72
|
+
escaped=escaped,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def set_span_attribute(
|
|
77
|
+
key: str,
|
|
78
|
+
value: AttributeValue,
|
|
79
|
+
) -> None:
|
|
80
|
+
|
|
81
|
+
if trace_context_provider := get_tracing_ctx_provider():
|
|
82
|
+
trace_context_provider.set_span_attribute(
|
|
83
|
+
key=key,
|
|
84
|
+
value=value,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_tracing_provider() -> TracingContextProvider | None:
|
|
89
|
+
return get_tracing_ctx_provider()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_current_span_context() -> TracingSpanContext | None:
|
|
93
|
+
|
|
94
|
+
if trace_context_provider := get_tracing_ctx_provider():
|
|
95
|
+
return trace_context_provider.get_current_span_context()
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_current_span() -> TracingSpan | None:
|
|
100
|
+
|
|
101
|
+
if trace_context_provider := get_tracing_ctx_provider():
|
|
102
|
+
return trace_context_provider.get_current_span()
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def add_span_link(span_context: TracingSpanContext) -> None:
|
|
107
|
+
|
|
108
|
+
if trace_context_provider := get_tracing_ctx_provider():
|
|
109
|
+
trace_context_provider.add_link(span_context=span_context)
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
1
5
|
from contextlib import asynccontextmanager
|
|
2
6
|
from typing import AsyncContextManager, AsyncGenerator, Protocol
|
|
3
7
|
|
|
4
8
|
from jararaca.microservice import (
|
|
5
|
-
AppContext,
|
|
6
9
|
AppInterceptor,
|
|
7
10
|
AppInterceptorWithLifecycle,
|
|
11
|
+
AppTransactionContext,
|
|
8
12
|
Container,
|
|
9
13
|
Microservice,
|
|
10
14
|
)
|
|
@@ -32,7 +36,9 @@ class ObservabilityInterceptor(AppInterceptor, AppInterceptorWithLifecycle):
|
|
|
32
36
|
self.observability_provider = observability_provider
|
|
33
37
|
|
|
34
38
|
@asynccontextmanager
|
|
35
|
-
async def intercept(
|
|
39
|
+
async def intercept(
|
|
40
|
+
self, app_context: AppTransactionContext
|
|
41
|
+
) -> AsyncGenerator[None, None]:
|
|
36
42
|
|
|
37
43
|
async with self.observability_provider.tracing_provider.root_setup(app_context):
|
|
38
44
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
1
5
|
import logging
|
|
2
6
|
from contextlib import asynccontextmanager, contextmanager
|
|
3
|
-
from typing import AsyncGenerator, Generator, Protocol
|
|
7
|
+
from typing import Any, AsyncGenerator, Generator, Literal, Protocol
|
|
4
8
|
|
|
5
9
|
from opentelemetry import metrics, trace
|
|
6
10
|
from opentelemetry._logs import set_logger_provider
|
|
@@ -23,51 +27,194 @@ from opentelemetry.sdk.trace import TracerProvider
|
|
|
23
27
|
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
24
28
|
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
|
|
25
29
|
|
|
26
|
-
from jararaca.
|
|
30
|
+
from jararaca.messagebus.implicit_headers import (
|
|
31
|
+
ImplicitHeaders,
|
|
32
|
+
provide_implicit_headers,
|
|
33
|
+
use_implicit_headers,
|
|
34
|
+
)
|
|
35
|
+
from jararaca.microservice import (
|
|
36
|
+
AppTransactionContext,
|
|
37
|
+
Container,
|
|
38
|
+
Microservice,
|
|
39
|
+
use_app_transaction_context,
|
|
40
|
+
)
|
|
41
|
+
from jararaca.observability.constants import TRACEPARENT_KEY
|
|
27
42
|
from jararaca.observability.decorators import (
|
|
43
|
+
AttributeMap,
|
|
44
|
+
AttributeValue,
|
|
28
45
|
TracingContextProvider,
|
|
29
46
|
TracingContextProviderFactory,
|
|
30
|
-
|
|
47
|
+
TracingSpan,
|
|
48
|
+
TracingSpanContext,
|
|
31
49
|
)
|
|
32
50
|
from jararaca.observability.interceptor import ObservabilityProvider
|
|
33
51
|
|
|
34
52
|
tracer: trace.Tracer = trace.get_tracer(__name__)
|
|
35
53
|
|
|
36
54
|
|
|
55
|
+
def extract_context_attributes(ctx: AppTransactionContext) -> dict[str, Any]:
|
|
56
|
+
tx_data = ctx.transaction_data
|
|
57
|
+
extra_attributes: dict[str, Any] = {}
|
|
58
|
+
|
|
59
|
+
if tx_data.context_type == "http":
|
|
60
|
+
extra_attributes = {
|
|
61
|
+
"http.method": tx_data.request.method,
|
|
62
|
+
"http.url": str(tx_data.request.url),
|
|
63
|
+
"http.path": tx_data.request.url.path,
|
|
64
|
+
"http.route.path": tx_data.request.scope["route"].path,
|
|
65
|
+
"http.route.endpoint.name": tx_data.request["route"].endpoint.__qualname__,
|
|
66
|
+
"http.query": tx_data.request.url.query,
|
|
67
|
+
**{
|
|
68
|
+
f"http.request.path_param.{k}": v
|
|
69
|
+
for k, v in tx_data.request.path_params.items()
|
|
70
|
+
},
|
|
71
|
+
**{
|
|
72
|
+
f"http.request.query_param.{k}": v
|
|
73
|
+
for k, v in tx_data.request.query_params.items()
|
|
74
|
+
},
|
|
75
|
+
**{
|
|
76
|
+
f"http.request.header.{k}": v
|
|
77
|
+
for k, v in tx_data.request.headers.items()
|
|
78
|
+
},
|
|
79
|
+
"http.request.client.host": (
|
|
80
|
+
tx_data.request.client.host if tx_data.request.client else ""
|
|
81
|
+
),
|
|
82
|
+
}
|
|
83
|
+
elif tx_data.context_type == "message_bus":
|
|
84
|
+
extra_attributes = {
|
|
85
|
+
"bus.message.name": tx_data.message_type.__qualname__,
|
|
86
|
+
"bus.message.module": tx_data.message_type.__module__,
|
|
87
|
+
"bus.message.category": tx_data.message_type.MESSAGE_CATEGORY,
|
|
88
|
+
"bus.message.type": tx_data.message_type.MESSAGE_TYPE,
|
|
89
|
+
"bus.message.topic": tx_data.message_type.MESSAGE_TOPIC,
|
|
90
|
+
}
|
|
91
|
+
elif tx_data.context_type == "websocket":
|
|
92
|
+
extra_attributes = {
|
|
93
|
+
"ws.url": str(tx_data.websocket.url),
|
|
94
|
+
}
|
|
95
|
+
elif tx_data.context_type == "scheduler":
|
|
96
|
+
extra_attributes = {
|
|
97
|
+
"sched.task_name": tx_data.task_name,
|
|
98
|
+
"sched.scheduled_to": tx_data.scheduled_to.isoformat(),
|
|
99
|
+
"sched.cron_expression": tx_data.cron_expression,
|
|
100
|
+
"sched.triggered_at": tx_data.triggered_at.isoformat(),
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
"app.context_type": tx_data.context_type,
|
|
104
|
+
"controller_member_reflect.rest_controller.class_name": ctx.controller_member_reflect.controller_reflect.controller_class.__qualname__,
|
|
105
|
+
"controller_member_reflect.rest_controller.module": ctx.controller_member_reflect.controller_reflect.controller_class.__module__,
|
|
106
|
+
"controller_member_reflect.member_function.name": ctx.controller_member_reflect.member_function.__qualname__,
|
|
107
|
+
"controller_member_reflect.member_function.module": ctx.controller_member_reflect.member_function.__module__,
|
|
108
|
+
**extra_attributes,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class OtelTracingSpan(TracingSpan):
|
|
113
|
+
|
|
114
|
+
def __init__(self, span: trace.Span) -> None:
|
|
115
|
+
self.span = span
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class OtelTracingSpanContext(TracingSpanContext):
|
|
119
|
+
|
|
120
|
+
def __init__(self, span_context: trace.SpanContext) -> None:
|
|
121
|
+
self.span_context = span_context
|
|
122
|
+
|
|
123
|
+
|
|
37
124
|
class OtelTracingContextProvider(TracingContextProvider):
|
|
38
125
|
|
|
39
|
-
def __init__(self, app_context:
|
|
126
|
+
def __init__(self, app_context: AppTransactionContext) -> None:
|
|
40
127
|
self.app_context = app_context
|
|
41
128
|
|
|
42
129
|
@contextmanager
|
|
43
|
-
def
|
|
130
|
+
def start_span_context(
|
|
44
131
|
self,
|
|
45
132
|
trace_name: str,
|
|
46
|
-
context_attributes:
|
|
133
|
+
context_attributes: AttributeMap | None,
|
|
47
134
|
) -> Generator[None, None, None]:
|
|
48
135
|
|
|
49
136
|
with tracer.start_as_current_span(trace_name, attributes=context_attributes):
|
|
50
137
|
yield
|
|
51
138
|
|
|
139
|
+
def add_event(
|
|
140
|
+
self, event_name: str, event_attributes: AttributeMap | None = None
|
|
141
|
+
) -> None:
|
|
142
|
+
trace.get_current_span().add_event(name=event_name, attributes=event_attributes)
|
|
143
|
+
|
|
144
|
+
def set_span_status(self, status_code: Literal["OK", "ERROR", "UNSET"]) -> None:
|
|
145
|
+
span = trace.get_current_span()
|
|
146
|
+
if status_code == "OK":
|
|
147
|
+
span.set_status(trace.Status(trace.StatusCode.OK))
|
|
148
|
+
elif status_code == "ERROR":
|
|
149
|
+
span.set_status(trace.Status(trace.StatusCode.ERROR))
|
|
150
|
+
else:
|
|
151
|
+
span.set_status(trace.Status(trace.StatusCode.UNSET))
|
|
152
|
+
|
|
153
|
+
def record_exception(
|
|
154
|
+
self,
|
|
155
|
+
exception: Exception,
|
|
156
|
+
attributes: AttributeMap | None = None,
|
|
157
|
+
escaped: bool = False,
|
|
158
|
+
) -> None:
|
|
159
|
+
span = trace.get_current_span()
|
|
160
|
+
span.record_exception(exception, attributes=attributes, escaped=escaped)
|
|
161
|
+
|
|
162
|
+
def set_span_attribute(self, key: str, value: AttributeValue) -> None:
|
|
163
|
+
span = trace.get_current_span()
|
|
164
|
+
span.set_attribute(key, value)
|
|
165
|
+
|
|
166
|
+
def update_span_name(self, new_name: str) -> None:
|
|
167
|
+
span = trace.get_current_span()
|
|
168
|
+
|
|
169
|
+
span.update_name(new_name)
|
|
170
|
+
|
|
171
|
+
def add_link(self, span_context: TracingSpanContext) -> None:
|
|
172
|
+
if not isinstance(span_context, OtelTracingSpanContext):
|
|
173
|
+
return
|
|
174
|
+
span = trace.get_current_span()
|
|
175
|
+
span.add_link(span_context.span_context)
|
|
176
|
+
|
|
177
|
+
def get_current_span(self) -> TracingSpan | None:
|
|
178
|
+
return OtelTracingSpan(trace.get_current_span())
|
|
179
|
+
|
|
180
|
+
def get_current_span_context(self) -> TracingSpanContext | None:
|
|
181
|
+
return OtelTracingSpanContext(trace.get_current_span().get_span_context())
|
|
182
|
+
|
|
52
183
|
|
|
53
184
|
class OtelTracingContextProviderFactory(TracingContextProviderFactory):
|
|
54
185
|
|
|
55
|
-
def provide_provider(
|
|
186
|
+
def provide_provider(
|
|
187
|
+
self, app_context: AppTransactionContext
|
|
188
|
+
) -> TracingContextProvider:
|
|
56
189
|
return OtelTracingContextProvider(app_context)
|
|
57
190
|
|
|
58
191
|
@asynccontextmanager
|
|
59
|
-
async def root_setup(
|
|
192
|
+
async def root_setup(
|
|
193
|
+
self, app_tx_ctx: AppTransactionContext
|
|
194
|
+
) -> AsyncGenerator[None, None]:
|
|
60
195
|
|
|
61
196
|
title: str = "Unmapped App Context Execution"
|
|
62
|
-
headers = {}
|
|
197
|
+
headers: dict[str, Any] = {}
|
|
198
|
+
tx_data = app_tx_ctx.transaction_data
|
|
199
|
+
extra_attributes = extract_context_attributes(app_tx_ctx)
|
|
63
200
|
|
|
64
|
-
if
|
|
201
|
+
if tx_data.context_type == "http":
|
|
202
|
+
headers = dict(tx_data.request.headers)
|
|
203
|
+
title = f"HTTP {tx_data.request.method} {tx_data.request.url}"
|
|
204
|
+
extra_attributes["http.request.body"] = (await tx_data.request.body())[
|
|
205
|
+
:5000
|
|
206
|
+
].decode(errors="ignore")
|
|
65
207
|
|
|
66
|
-
|
|
67
|
-
title = f"
|
|
208
|
+
elif tx_data.context_type == "message_bus":
|
|
209
|
+
title = f"Message Bus {tx_data.topic}"
|
|
210
|
+
headers = use_implicit_headers() or {}
|
|
68
211
|
|
|
69
|
-
elif
|
|
70
|
-
|
|
212
|
+
elif tx_data.context_type == "websocket":
|
|
213
|
+
headers = dict(tx_data.websocket.headers)
|
|
214
|
+
title = f"WebSocket {tx_data.websocket.url}"
|
|
215
|
+
|
|
216
|
+
elif tx_data.context_type == "scheduler":
|
|
217
|
+
title = f"Scheduler Task {tx_data.task_name}"
|
|
71
218
|
|
|
72
219
|
carrier = {
|
|
73
220
|
key: value
|
|
@@ -86,8 +233,28 @@ class OtelTracingContextProviderFactory(TracingContextProviderFactory):
|
|
|
86
233
|
|
|
87
234
|
ctx2 = W3CBaggagePropagator().extract(b2, context=ctx)
|
|
88
235
|
|
|
89
|
-
with tracer.start_as_current_span(
|
|
90
|
-
|
|
236
|
+
with tracer.start_as_current_span(
|
|
237
|
+
name=title,
|
|
238
|
+
context=ctx2,
|
|
239
|
+
attributes={
|
|
240
|
+
**extra_attributes,
|
|
241
|
+
},
|
|
242
|
+
) as root_span:
|
|
243
|
+
cx = root_span.get_span_context()
|
|
244
|
+
span_traceparent_id = hex(cx.trace_id)[2:].rjust(32, "0")
|
|
245
|
+
if app_tx_ctx.transaction_data.context_type == "http":
|
|
246
|
+
app_tx_ctx.transaction_data.request.scope[TRACEPARENT_KEY] = (
|
|
247
|
+
span_traceparent_id
|
|
248
|
+
)
|
|
249
|
+
elif app_tx_ctx.transaction_data.context_type == "websocket":
|
|
250
|
+
app_tx_ctx.transaction_data.websocket.scope[TRACEPARENT_KEY] = (
|
|
251
|
+
span_traceparent_id
|
|
252
|
+
)
|
|
253
|
+
tracing_headers: ImplicitHeaders = {}
|
|
254
|
+
TraceContextTextMapPropagator().inject(tracing_headers)
|
|
255
|
+
W3CBaggagePropagator().inject(tracing_headers)
|
|
256
|
+
with provide_implicit_headers(tracing_headers):
|
|
257
|
+
yield
|
|
91
258
|
|
|
92
259
|
|
|
93
260
|
class LoggerHandlerCallback(Protocol):
|
|
@@ -95,6 +262,34 @@ class LoggerHandlerCallback(Protocol):
|
|
|
95
262
|
def __call__(self, logger_handler: logging.Handler) -> None: ...
|
|
96
263
|
|
|
97
264
|
|
|
265
|
+
class CustomLoggingHandler(LoggingHandler):
|
|
266
|
+
|
|
267
|
+
def _translate(self, record: logging.LogRecord) -> dict[str, Any]:
|
|
268
|
+
try:
|
|
269
|
+
ctx = use_app_transaction_context()
|
|
270
|
+
data = super()._translate(record)
|
|
271
|
+
extra_attributes = extract_context_attributes(ctx)
|
|
272
|
+
|
|
273
|
+
current_span = trace.get_current_span()
|
|
274
|
+
|
|
275
|
+
data["attributes"] = {
|
|
276
|
+
**data.get("attributes", {}),
|
|
277
|
+
**extra_attributes,
|
|
278
|
+
**(
|
|
279
|
+
{
|
|
280
|
+
"span_name": current_span.name,
|
|
281
|
+
}
|
|
282
|
+
if hasattr(current_span, "name")
|
|
283
|
+
and current_span.is_recording() is False
|
|
284
|
+
else {}
|
|
285
|
+
),
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return data
|
|
289
|
+
except LookupError:
|
|
290
|
+
return super()._translate(record)
|
|
291
|
+
|
|
292
|
+
|
|
98
293
|
class OtelObservabilityProvider(ObservabilityProvider):
|
|
99
294
|
|
|
100
295
|
def __init__(
|
|
@@ -139,11 +334,11 @@ class OtelObservabilityProvider(ObservabilityProvider):
|
|
|
139
334
|
BatchLogRecordProcessor(self.logs_exporter)
|
|
140
335
|
)
|
|
141
336
|
|
|
142
|
-
logging_handler =
|
|
337
|
+
logging_handler = CustomLoggingHandler(
|
|
143
338
|
level=logging.DEBUG, logger_provider=logger_provider
|
|
144
339
|
)
|
|
145
340
|
|
|
146
|
-
logging_handler.addFilter(lambda _: get_tracing_ctx_provider() is not None)
|
|
341
|
+
# logging_handler.addFilter(lambda _: get_tracing_ctx_provider() is not None)
|
|
147
342
|
|
|
148
343
|
self.logging_handler_callback(logging_handler)
|
|
149
344
|
|
jararaca/persistence/base.py
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Lucas S
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable, Protocol, Self, Type, TypeVar
|
|
2
6
|
|
|
3
7
|
from pydantic import BaseModel
|
|
8
|
+
from sqlalchemy.ext.asyncio import AsyncAttrs
|
|
4
9
|
from sqlalchemy.orm import DeclarativeBase
|
|
5
10
|
|
|
6
11
|
IDENTIFIABLE_SCHEMA_T = TypeVar("IDENTIFIABLE_SCHEMA_T")
|
|
@@ -20,14 +25,46 @@ def recursive_get_dict(obj: Any) -> Any:
|
|
|
20
25
|
return obj
|
|
21
26
|
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
RESULT_T = TypeVar("RESULT_T", covariant=True)
|
|
29
|
+
ENTITY_T_CONTRA = TypeVar("ENTITY_T_CONTRA", bound="BaseEntity", contravariant=True)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class EntityParserType(Protocol[ENTITY_T_CONTRA, RESULT_T]):
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def parse_entity(cls, model: ENTITY_T_CONTRA) -> "RESULT_T": ...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
EntityParserFunc = Callable[[ENTITY_T_CONTRA], RESULT_T]
|
|
39
|
+
|
|
40
|
+
BASED_BASE_ENTITY_T = TypeVar("BASED_BASE_ENTITY_T", bound="BaseEntity")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BaseEntity(AsyncAttrs, DeclarativeBase):
|
|
24
44
|
|
|
25
45
|
@classmethod
|
|
26
46
|
def from_basemodel(cls, mutation: T_BASEMODEL) -> "Self":
|
|
27
47
|
intersection = set(cls.__annotations__.keys()) & set(
|
|
28
|
-
mutation.model_fields.keys()
|
|
48
|
+
mutation.__class__.model_fields.keys()
|
|
29
49
|
)
|
|
30
50
|
return cls(**{k: getattr(mutation, k) for k in intersection})
|
|
31
51
|
|
|
32
52
|
def to_basemodel(self, model: Type[T_BASEMODEL]) -> T_BASEMODEL:
|
|
33
53
|
return model.model_validate(recursive_get_dict(self))
|
|
54
|
+
|
|
55
|
+
def parse_entity_with_func(
|
|
56
|
+
self, model_cls: EntityParserFunc["Self", RESULT_T]
|
|
57
|
+
) -> RESULT_T:
|
|
58
|
+
return model_cls(self)
|
|
59
|
+
|
|
60
|
+
def parse_entity_with_type(
|
|
61
|
+
self: BASED_BASE_ENTITY_T,
|
|
62
|
+
model_cls: Type[EntityParserType[BASED_BASE_ENTITY_T, RESULT_T]],
|
|
63
|
+
) -> RESULT_T:
|
|
64
|
+
return model_cls.parse_entity(self)
|
|
65
|
+
|
|
66
|
+
def __rshift__(self, model: EntityParserFunc["Self", RESULT_T]) -> RESULT_T:
|
|
67
|
+
return self.parse_entity_with_func(model)
|
|
68
|
+
|
|
69
|
+
def __and__(self, model: Type[EntityParserType["Self", RESULT_T]]) -> RESULT_T:
|
|
70
|
+
return self.parse_entity_with_type(model)
|
jararaca/persistence/exports.py
CHANGED