mobius-logging-py 1.0.0__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.
Files changed (24) hide show
  1. mobius_logging_py-1.0.0/PKG-INFO +211 -0
  2. mobius_logging_py-1.0.0/README.md +192 -0
  3. mobius_logging_py-1.0.0/pyproject.toml +38 -0
  4. mobius_logging_py-1.0.0/setup.cfg +4 -0
  5. mobius_logging_py-1.0.0/src/mobius_logging/__init__.py +80 -0
  6. mobius_logging_py-1.0.0/src/mobius_logging/bootstrap.py +120 -0
  7. mobius_logging_py-1.0.0/src/mobius_logging/constants.py +32 -0
  8. mobius_logging_py-1.0.0/src/mobius_logging/context.py +140 -0
  9. mobius_logging_py-1.0.0/src/mobius_logging/fields.py +83 -0
  10. mobius_logging_py-1.0.0/src/mobius_logging/kafka_context.py +57 -0
  11. mobius_logging_py-1.0.0/src/mobius_logging/kafka_producer.py +127 -0
  12. mobius_logging_py-1.0.0/src/mobius_logging/log_type.py +20 -0
  13. mobius_logging_py-1.0.0/src/mobius_logging/logging_config.py +122 -0
  14. mobius_logging_py-1.0.0/src/mobius_logging/masking.py +78 -0
  15. mobius_logging_py-1.0.0/src/mobius_logging/middleware.py +65 -0
  16. mobius_logging_py-1.0.0/src/mobius_logging/observed.py +115 -0
  17. mobius_logging_py-1.0.0/src/mobius_logging/py.typed +0 -0
  18. mobius_logging_py-1.0.0/src/mobius_logging_py.egg-info/PKG-INFO +211 -0
  19. mobius_logging_py-1.0.0/src/mobius_logging_py.egg-info/SOURCES.txt +22 -0
  20. mobius_logging_py-1.0.0/src/mobius_logging_py.egg-info/dependency_links.txt +1 -0
  21. mobius_logging_py-1.0.0/src/mobius_logging_py.egg-info/requires.txt +13 -0
  22. mobius_logging_py-1.0.0/src/mobius_logging_py.egg-info/top_level.txt +1 -0
  23. mobius_logging_py-1.0.0/tests/test_core.py +139 -0
  24. mobius_logging_py-1.0.0/tests/test_middleware.py +35 -0
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.4
2
+ Name: mobius-logging-py
3
+ Version: 1.0.0
4
+ Summary: Standardized logging context, observation, masking and Kafka schema events for Mobius Python (FastAPI) services.
5
+ Author: Mobius Platform
6
+ Keywords: logging,observability,fastapi,mobius,kafka
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: py-kafka-producer-client>=0.1.7
10
+ Provides-Extra: otel
11
+ Requires-Dist: opentelemetry-api>=1.20; extra == "otel"
12
+ Provides-Extra: fastapi
13
+ Requires-Dist: starlette>=0.27; extra == "fastapi"
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest>=7.4; extra == "dev"
16
+ Requires-Dist: starlette>=0.27; extra == "dev"
17
+ Requires-Dist: httpx>=0.24; extra == "dev"
18
+ Requires-Dist: opentelemetry-api>=1.20; extra == "dev"
19
+
20
+ # Mobius Logging for Python
21
+
22
+ Python port of the Java Mobius logging library. It standardizes logging context
23
+ for Mobius **FastAPI / Starlette** services: a contextvars-based context store
24
+ (the analog of SLF4J MDC), a request middleware, an `@observed` method
25
+ decorator, sensitive-data masking, OpenTelemetry trace sync, JSON logging, and
26
+ a Kafka schema-event producer.
27
+
28
+ Schema constants (Kafka topic `construct-data-1`, schema id, tenant id, and
29
+ `schema_version=mobius.log.v1`) are **identical to the Java library**, so events
30
+ from Python services land in the same pipeline.
31
+
32
+ Requires Python 3.10+.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install mobius-logging-py
38
+
39
+ # or directly from git
40
+ pip install "mobius-logging-py @ git+https://<your-git-host>/mobius-logging-py.git@v1.0.0"
41
+ ```
42
+
43
+ The import package is `mobius_logging` (e.g. `from mobius_logging import setup_logging`).
44
+
45
+ Optional extras: `mobius-logging-py[otel]` (OpenTelemetry trace sync),
46
+ `mobius-logging-py[fastapi]` (Starlette middleware).
47
+
48
+ ## Quick start
49
+
50
+ ```python
51
+ from fastapi import FastAPI
52
+ from mobius_logging import setup_logging, observed, LogType
53
+ from py_kafka_producer_client import Producer # your internal client
54
+
55
+ app = FastAPI()
56
+
57
+ setup_logging(
58
+ app,
59
+ service_name="orders-service",
60
+ profile="prod", # json logs for staging/prod/production
61
+ sensitive_keys=["session_id", "refresh_token"],
62
+ kafka_producer=Producer(...), # wrapped in PyKafkaProducerClientSender
63
+ )
64
+
65
+ @app.post("/orders")
66
+ @observed(event_type="order.create", log_type=LogType.AUDIT, resource_type="order")
67
+ async def create_order(order_id: str):
68
+ ...
69
+ ```
70
+
71
+ `setup_logging` is the one-call equivalent of Java's Spring auto-configuration
72
+ (Python has no auto-config, so wiring is explicit). It configures logging,
73
+ registers custom sensitive keys, builds the schema producer, and adds the
74
+ request middleware.
75
+
76
+ ## Java → Python mapping
77
+
78
+ | Java Mobius logging library | Python (`mobius_logging`) |
79
+ | --- | --- |
80
+ | SLF4J `MDC` | `contextvars.ContextVar` (`context.py`) |
81
+ | `PlatformLogContext` | `mobius_logging.context` functions |
82
+ | `PlatformLogFields` | `mobius_logging.fields` |
83
+ | `LogType` enum | `LogType` enum |
84
+ | `PlatformLoggingFilter` (servlet) | `PlatformLoggingMiddleware` (ASGI) |
85
+ | `@PlatformObserved` + AOP aspect | `@observed` decorator |
86
+ | `KafkaLogProducer` (`KafkaTemplate`) | `KafkaLogProducer` + `LogEventSender` |
87
+ | `PlatformKafka*Context` | `producer_headers` / `apply_consumer_record` |
88
+ | `SensitiveDataMasker` | `mobius_logging.masking` |
89
+ | `LogbackConfigurator` | `configure_platform_logging` |
90
+ | Spring auto-config classes | `setup_logging` (explicit) |
91
+
92
+ Out of scope for v1 (present in Java): JDBC/JPA, MongoDB, and Camunda logging.
93
+
94
+ ## Context API
95
+
96
+ ```python
97
+ from mobius_logging import context, LogType
98
+
99
+ context.init() # sets schema_version
100
+ context.tenant_id("tenant-123")
101
+ context.log_type(LogType.AUDIT)
102
+ context.resource_type("order")
103
+ context.resource_id("order-789")
104
+ context.field("custom_key", "value")
105
+ context.ensure_correlation_id() # generates one if missing
106
+ context.sync_trace_from_opentelemetry()
107
+
108
+ snap = context.snapshot()
109
+ try:
110
+ context.put("event_type", "order.approved")
111
+ finally:
112
+ context.restore(snap)
113
+ ```
114
+
115
+ > Note: Java's `ensureCorrelationId()` has an inverted condition and only
116
+ > regenerates when one already exists. This port fixes that — it generates a
117
+ > correlation id when none is present.
118
+
119
+ ## `@observed`
120
+
121
+ Works on sync and async functions. Adds `log_type`, `event_type`,
122
+ `resource_type`, `class_name`, `method_name`, `outcome`, `method_duration_ms`,
123
+ and `exception_type`/`exception_message` on failure. After completion it ships
124
+ the context to the schema producer (if one is registered), then restores the
125
+ pre-call context snapshot (Java clears MDC instead).
126
+
127
+ ```python
128
+ @observed(event_type="order.create", resource_type="order")
129
+ def create_order(order_id: str): ...
130
+ ```
131
+
132
+ ## Kafka
133
+
134
+ ### Schema event producer
135
+
136
+ The producer depends only on a small protocol, so the library has no hard Kafka
137
+ dependency. It matches the `py-kafka-producer-client` (>=0.1.7) signature — the
138
+ event is a dict and `topic` is keyword-only:
139
+
140
+ ```python
141
+ class LogEventSender(Protocol):
142
+ def send(self, value: dict, *, topic: str) -> Any: ...
143
+ ```
144
+
145
+ For the internal `py-kafka-producer-client`, pass the client to
146
+ `setup_logging(kafka_producer=...)` or wrap it directly:
147
+
148
+ ```python
149
+ from mobius_logging import KafkaLogProducer, PyKafkaProducerClientSender, set_log_producer
150
+
151
+ producer = KafkaLogProducer(PyKafkaProducerClientSender(client), "orders-service")
152
+ set_log_producer(producer)
153
+ ```
154
+
155
+ `PyKafkaProducerClientSender` calls `client.send(value, topic=...)`, passing the
156
+ `DataIngestionOperation` envelope as a dict (the client serializes it). To use a
157
+ different client, pass any object exposing `send(value, *, topic)` as
158
+ `kafka_sender=`.
159
+
160
+ Sending runs on a background thread pool (best-effort, never raises, never
161
+ blocks the request) — the analog of the Java `@Async` method.
162
+
163
+ ### Context propagation
164
+
165
+ ```python
166
+ from mobius_logging import producer_headers, apply_consumer_record
167
+
168
+ # producer side
169
+ client.send(topic, value, headers=producer_headers())
170
+
171
+ # consumer side
172
+ apply_consumer_record(record.headers(), topic=record.topic(),
173
+ partition=record.partition(), offset=record.offset())
174
+ ```
175
+
176
+ Propagated header fields: `trace_id`, `span_id`, `correlation_id`,
177
+ `causation_id`, `tenant_id`. Consumer also sets `kafka_topic`,
178
+ `kafka_partition`, `kafka_offset`.
179
+
180
+ ## Logging output
181
+
182
+ `configure_platform_logging(service_name, profile)` installs a root handler that
183
+ injects the current context onto every record. Profiles `staging`/`prod`/
184
+ `production` emit JSON; others use a readable text pattern. A daily-rotating
185
+ file handler writes to `logs/<service>.log` (30 days retained).
186
+
187
+ In JSON mode every context field is included automatically. For the text
188
+ pattern, `txn_id`, `tenant_id`, and `trace_id` are surfaced inline.
189
+
190
+ ## Sensitive data masking
191
+
192
+ ```python
193
+ from mobius_logging import mask, mask_map, init_custom_keys
194
+
195
+ init_custom_keys(["session_id", "refresh_token"])
196
+ mask("authorization", token) # masked
197
+ mask_map(context.copy()) # masks all sensitive keys in a dict
198
+ ```
199
+
200
+ Default sensitive keys: `password`, `token`, `secret`, `authorization`,
201
+ `cookie`, `apikey`, `privatekey`, `kubeconfig`. Matching is case-insensitive.
202
+ Masking keeps the last 4 characters of generic values and partially masks
203
+ emails — identical behaviour to the Java masker.
204
+
205
+ ## Development
206
+
207
+ ```bash
208
+ python -m venv .venv && source .venv/bin/activate
209
+ pip install -e ".[dev]"
210
+ pytest
211
+ ```
@@ -0,0 +1,192 @@
1
+ # Mobius Logging for Python
2
+
3
+ Python port of the Java Mobius logging library. It standardizes logging context
4
+ for Mobius **FastAPI / Starlette** services: a contextvars-based context store
5
+ (the analog of SLF4J MDC), a request middleware, an `@observed` method
6
+ decorator, sensitive-data masking, OpenTelemetry trace sync, JSON logging, and
7
+ a Kafka schema-event producer.
8
+
9
+ Schema constants (Kafka topic `construct-data-1`, schema id, tenant id, and
10
+ `schema_version=mobius.log.v1`) are **identical to the Java library**, so events
11
+ from Python services land in the same pipeline.
12
+
13
+ Requires Python 3.10+.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ pip install mobius-logging-py
19
+
20
+ # or directly from git
21
+ pip install "mobius-logging-py @ git+https://<your-git-host>/mobius-logging-py.git@v1.0.0"
22
+ ```
23
+
24
+ The import package is `mobius_logging` (e.g. `from mobius_logging import setup_logging`).
25
+
26
+ Optional extras: `mobius-logging-py[otel]` (OpenTelemetry trace sync),
27
+ `mobius-logging-py[fastapi]` (Starlette middleware).
28
+
29
+ ## Quick start
30
+
31
+ ```python
32
+ from fastapi import FastAPI
33
+ from mobius_logging import setup_logging, observed, LogType
34
+ from py_kafka_producer_client import Producer # your internal client
35
+
36
+ app = FastAPI()
37
+
38
+ setup_logging(
39
+ app,
40
+ service_name="orders-service",
41
+ profile="prod", # json logs for staging/prod/production
42
+ sensitive_keys=["session_id", "refresh_token"],
43
+ kafka_producer=Producer(...), # wrapped in PyKafkaProducerClientSender
44
+ )
45
+
46
+ @app.post("/orders")
47
+ @observed(event_type="order.create", log_type=LogType.AUDIT, resource_type="order")
48
+ async def create_order(order_id: str):
49
+ ...
50
+ ```
51
+
52
+ `setup_logging` is the one-call equivalent of Java's Spring auto-configuration
53
+ (Python has no auto-config, so wiring is explicit). It configures logging,
54
+ registers custom sensitive keys, builds the schema producer, and adds the
55
+ request middleware.
56
+
57
+ ## Java → Python mapping
58
+
59
+ | Java Mobius logging library | Python (`mobius_logging`) |
60
+ | --- | --- |
61
+ | SLF4J `MDC` | `contextvars.ContextVar` (`context.py`) |
62
+ | `PlatformLogContext` | `mobius_logging.context` functions |
63
+ | `PlatformLogFields` | `mobius_logging.fields` |
64
+ | `LogType` enum | `LogType` enum |
65
+ | `PlatformLoggingFilter` (servlet) | `PlatformLoggingMiddleware` (ASGI) |
66
+ | `@PlatformObserved` + AOP aspect | `@observed` decorator |
67
+ | `KafkaLogProducer` (`KafkaTemplate`) | `KafkaLogProducer` + `LogEventSender` |
68
+ | `PlatformKafka*Context` | `producer_headers` / `apply_consumer_record` |
69
+ | `SensitiveDataMasker` | `mobius_logging.masking` |
70
+ | `LogbackConfigurator` | `configure_platform_logging` |
71
+ | Spring auto-config classes | `setup_logging` (explicit) |
72
+
73
+ Out of scope for v1 (present in Java): JDBC/JPA, MongoDB, and Camunda logging.
74
+
75
+ ## Context API
76
+
77
+ ```python
78
+ from mobius_logging import context, LogType
79
+
80
+ context.init() # sets schema_version
81
+ context.tenant_id("tenant-123")
82
+ context.log_type(LogType.AUDIT)
83
+ context.resource_type("order")
84
+ context.resource_id("order-789")
85
+ context.field("custom_key", "value")
86
+ context.ensure_correlation_id() # generates one if missing
87
+ context.sync_trace_from_opentelemetry()
88
+
89
+ snap = context.snapshot()
90
+ try:
91
+ context.put("event_type", "order.approved")
92
+ finally:
93
+ context.restore(snap)
94
+ ```
95
+
96
+ > Note: Java's `ensureCorrelationId()` has an inverted condition and only
97
+ > regenerates when one already exists. This port fixes that — it generates a
98
+ > correlation id when none is present.
99
+
100
+ ## `@observed`
101
+
102
+ Works on sync and async functions. Adds `log_type`, `event_type`,
103
+ `resource_type`, `class_name`, `method_name`, `outcome`, `method_duration_ms`,
104
+ and `exception_type`/`exception_message` on failure. After completion it ships
105
+ the context to the schema producer (if one is registered), then restores the
106
+ pre-call context snapshot (Java clears MDC instead).
107
+
108
+ ```python
109
+ @observed(event_type="order.create", resource_type="order")
110
+ def create_order(order_id: str): ...
111
+ ```
112
+
113
+ ## Kafka
114
+
115
+ ### Schema event producer
116
+
117
+ The producer depends only on a small protocol, so the library has no hard Kafka
118
+ dependency. It matches the `py-kafka-producer-client` (>=0.1.7) signature — the
119
+ event is a dict and `topic` is keyword-only:
120
+
121
+ ```python
122
+ class LogEventSender(Protocol):
123
+ def send(self, value: dict, *, topic: str) -> Any: ...
124
+ ```
125
+
126
+ For the internal `py-kafka-producer-client`, pass the client to
127
+ `setup_logging(kafka_producer=...)` or wrap it directly:
128
+
129
+ ```python
130
+ from mobius_logging import KafkaLogProducer, PyKafkaProducerClientSender, set_log_producer
131
+
132
+ producer = KafkaLogProducer(PyKafkaProducerClientSender(client), "orders-service")
133
+ set_log_producer(producer)
134
+ ```
135
+
136
+ `PyKafkaProducerClientSender` calls `client.send(value, topic=...)`, passing the
137
+ `DataIngestionOperation` envelope as a dict (the client serializes it). To use a
138
+ different client, pass any object exposing `send(value, *, topic)` as
139
+ `kafka_sender=`.
140
+
141
+ Sending runs on a background thread pool (best-effort, never raises, never
142
+ blocks the request) — the analog of the Java `@Async` method.
143
+
144
+ ### Context propagation
145
+
146
+ ```python
147
+ from mobius_logging import producer_headers, apply_consumer_record
148
+
149
+ # producer side
150
+ client.send(topic, value, headers=producer_headers())
151
+
152
+ # consumer side
153
+ apply_consumer_record(record.headers(), topic=record.topic(),
154
+ partition=record.partition(), offset=record.offset())
155
+ ```
156
+
157
+ Propagated header fields: `trace_id`, `span_id`, `correlation_id`,
158
+ `causation_id`, `tenant_id`. Consumer also sets `kafka_topic`,
159
+ `kafka_partition`, `kafka_offset`.
160
+
161
+ ## Logging output
162
+
163
+ `configure_platform_logging(service_name, profile)` installs a root handler that
164
+ injects the current context onto every record. Profiles `staging`/`prod`/
165
+ `production` emit JSON; others use a readable text pattern. A daily-rotating
166
+ file handler writes to `logs/<service>.log` (30 days retained).
167
+
168
+ In JSON mode every context field is included automatically. For the text
169
+ pattern, `txn_id`, `tenant_id`, and `trace_id` are surfaced inline.
170
+
171
+ ## Sensitive data masking
172
+
173
+ ```python
174
+ from mobius_logging import mask, mask_map, init_custom_keys
175
+
176
+ init_custom_keys(["session_id", "refresh_token"])
177
+ mask("authorization", token) # masked
178
+ mask_map(context.copy()) # masks all sensitive keys in a dict
179
+ ```
180
+
181
+ Default sensitive keys: `password`, `token`, `secret`, `authorization`,
182
+ `cookie`, `apikey`, `privatekey`, `kubeconfig`. Matching is case-insensitive.
183
+ Masking keeps the last 4 characters of generic values and partially masks
184
+ emails — identical behaviour to the Java masker.
185
+
186
+ ## Development
187
+
188
+ ```bash
189
+ python -m venv .venv && source .venv/bin/activate
190
+ pip install -e ".[dev]"
191
+ pytest
192
+ ```
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mobius-logging-py"
7
+ version = "1.0.0"
8
+ description = "Standardized logging context, observation, masking and Kafka schema events for Mobius Python (FastAPI) services."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [{ name = "Mobius Platform" }]
12
+ keywords = ["logging", "observability", "fastapi", "mobius", "kafka"]
13
+ dependencies = [
14
+ # Schema-event producer used by @observed. Resolved from the internal index.
15
+ "py-kafka-producer-client>=0.1.7",
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ # OpenTelemetry trace-id sync (sync_trace_from_opentelemetry is a no-op without it)
20
+ otel = ["opentelemetry-api>=1.20"]
21
+ # Web middleware target
22
+ fastapi = ["starlette>=0.27"]
23
+ # Dev / test tooling
24
+ dev = [
25
+ "pytest>=7.4",
26
+ "starlette>=0.27",
27
+ "httpx>=0.24",
28
+ "opentelemetry-api>=1.20",
29
+ ]
30
+
31
+ [tool.setuptools.packages.find]
32
+ where = ["src"]
33
+
34
+ [tool.setuptools.package-data]
35
+ mobius_logging = ["py.typed"]
36
+
37
+ [tool.pytest.ini_options]
38
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,80 @@
1
+ """Mobius Logging for Python.
2
+
3
+ Python port of the Java Mobius logging library: standardized logging context,
4
+ method observation, sensitive-data masking, OpenTelemetry trace sync, JSON
5
+ logging and a Kafka schema-event producer for FastAPI / Starlette services.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from . import context, fields
10
+ from .context import (
11
+ clear,
12
+ correlation_id,
13
+ ensure_correlation_id,
14
+ field,
15
+ get,
16
+ init,
17
+ log_type,
18
+ put,
19
+ requester_type,
20
+ resource_id,
21
+ resource_type,
22
+ restore,
23
+ snapshot,
24
+ sync_trace_from_opentelemetry,
25
+ tenant_id,
26
+ transaction_id,
27
+ )
28
+ from .kafka_context import apply_consumer_record, producer_headers
29
+ from .kafka_producer import (
30
+ KafkaLogProducer,
31
+ LogEventSender,
32
+ PyKafkaProducerClientSender,
33
+ get_log_producer,
34
+ set_log_producer,
35
+ )
36
+ from .log_type import LogType
37
+ from .logging_config import configure_platform_logging
38
+ from .masking import init_custom_keys, mask, mask_map
39
+ from .middleware import PlatformLoggingMiddleware
40
+ from .observed import observed
41
+ from .bootstrap import setup_logging
42
+
43
+ __version__ = "1.0.0"
44
+
45
+ __all__ = [
46
+ "context",
47
+ "fields",
48
+ "LogType",
49
+ "PlatformLoggingMiddleware",
50
+ "observed",
51
+ "setup_logging",
52
+ "configure_platform_logging",
53
+ "KafkaLogProducer",
54
+ "LogEventSender",
55
+ "PyKafkaProducerClientSender",
56
+ "set_log_producer",
57
+ "get_log_producer",
58
+ "producer_headers",
59
+ "apply_consumer_record",
60
+ "init_custom_keys",
61
+ "mask",
62
+ "mask_map",
63
+ # context functions
64
+ "put",
65
+ "get",
66
+ "field",
67
+ "init",
68
+ "clear",
69
+ "snapshot",
70
+ "restore",
71
+ "log_type",
72
+ "tenant_id",
73
+ "resource_id",
74
+ "resource_type",
75
+ "correlation_id",
76
+ "transaction_id",
77
+ "requester_type",
78
+ "ensure_correlation_id",
79
+ "sync_trace_from_opentelemetry",
80
+ ]
@@ -0,0 +1,120 @@
1
+ """One-call bootstrap for FastAPI services.
2
+
3
+ Wires logging configuration, sensitive-key registration, the schema producer
4
+ and the request middleware in a single call - the rough equivalent of the
5
+ Java auto-configuration classes (which Spring applies automatically; Python
6
+ has no auto-config, so this is explicit).
7
+
8
+ Example::
9
+
10
+ from kafka_producer_client import KafkaProducerConfig
11
+ from mobius_logging import setup_logging
12
+
13
+ setup_logging(
14
+ app,
15
+ service_name="orders-service",
16
+ profile="prod",
17
+ sensitive_keys=["session_id", "refresh_token"],
18
+ kafka_config=KafkaProducerConfig(
19
+ bootstrap_servers="broker:9092",
20
+ default_topic="construct-data-1",
21
+ ),
22
+ )
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ from typing import Any, Iterable, Optional
28
+
29
+ from . import masking
30
+ from .kafka_producer import (
31
+ KafkaLogProducer,
32
+ LogEventSender,
33
+ PyKafkaProducerClientSender,
34
+ set_log_producer,
35
+ )
36
+ from .logging_config import configure_platform_logging
37
+ from .middleware import PlatformLoggingMiddleware
38
+
39
+ log = logging.getLogger(__name__)
40
+
41
+
42
+ def setup_logging(
43
+ app: Optional[Any] = None,
44
+ *,
45
+ service_name: str = "unknown-service",
46
+ profile: Optional[str] = None,
47
+ sensitive_keys: Optional[Iterable[str]] = None,
48
+ kafka_config: Optional[Any] = None,
49
+ kafka_producer: Optional[Any] = None,
50
+ kafka_sender: Optional[LogEventSender] = None,
51
+ enable_file: bool = True,
52
+ ) -> Optional[KafkaLogProducer]:
53
+ """Configure the library and (optionally) register FastAPI middleware.
54
+
55
+ The schema producer is resolved in this order:
56
+
57
+ 1. ``kafka_sender`` - any object with ``send(value, *, topic)``;
58
+ 2. ``kafka_producer`` - a ``py-kafka-producer-client`` ``KafkaProducerClient``
59
+ instance, wrapped automatically;
60
+ 3. ``kafka_config`` - a ``KafkaProducerConfig``; the client's singleton is
61
+ configured and used (the default integration);
62
+ 4. the already-configured ``py-kafka-producer-client`` singleton, if the app
63
+ called ``configure_kafka_producer(...)`` itself.
64
+
65
+ If none resolve, ``@observed`` simply skips schema publishing.
66
+
67
+ Returns the registered :class:`KafkaLogProducer`, if any.
68
+ """
69
+ configure_platform_logging(service_name, profile, enable_file=enable_file)
70
+ masking.init_custom_keys(sensitive_keys)
71
+
72
+ sender = _resolve_sender(kafka_sender, kafka_producer, kafka_config)
73
+
74
+ producer: Optional[KafkaLogProducer] = None
75
+ if sender is not None:
76
+ producer = KafkaLogProducer(sender, service_name)
77
+ set_log_producer(producer)
78
+ else:
79
+ log.warning(
80
+ "mobius-logging: no Kafka producer resolved; @observed will not "
81
+ "publish schema events. Pass kafka_config/kafka_producer/kafka_sender."
82
+ )
83
+
84
+ if app is not None:
85
+ app.add_middleware(PlatformLoggingMiddleware, service_name=service_name)
86
+
87
+ return producer
88
+
89
+
90
+ def _resolve_sender(
91
+ kafka_sender: Optional[LogEventSender],
92
+ kafka_producer: Optional[Any],
93
+ kafka_config: Optional[Any],
94
+ ) -> Optional[LogEventSender]:
95
+ if kafka_sender is not None:
96
+ return kafka_sender
97
+ if kafka_producer is not None:
98
+ return PyKafkaProducerClientSender(kafka_producer)
99
+
100
+ # Default: integrate with the internal py-kafka-producer-client singleton.
101
+ try:
102
+ from kafka_producer_client.action_logger import (
103
+ configure_kafka_producer,
104
+ get_kafka_producer,
105
+ )
106
+ except ImportError:
107
+ log.warning("mobius-logging: py-kafka-producer-client not installed.")
108
+ return None
109
+
110
+ try:
111
+ if kafka_config is not None:
112
+ configure_kafka_producer(kafka_config)
113
+ return PyKafkaProducerClientSender(get_kafka_producer())
114
+ except Exception: # noqa: BLE001 - producer is optional, never crash setup
115
+ log.warning(
116
+ "mobius-logging: could not initialize py-kafka-producer-client "
117
+ "(was configure_kafka_producer/kafka_config provided?).",
118
+ exc_info=True,
119
+ )
120
+ return None
@@ -0,0 +1,32 @@
1
+ """Platform constants. Mirrors the Java ``PlatformConstant``.
2
+
3
+ The Kafka topic, schema id and tenant id are kept identical to the Java
4
+ library on purpose: Python services emit into the same schema pipeline.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import re
9
+
10
+ SCHEMA_VERSION = "mobius.log.v1"
11
+
12
+ # Masking
13
+ EMAIL_RE = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$")
14
+ # Mask every word char that is followed by at least 4 more word chars,
15
+ # i.e. keep the last 4 characters visible. Matches the Java MASKING_REGEX.
16
+ MASKING_RE = re.compile(r"\w(?=\w{4})")
17
+ MASKING_PATTERN = "*****"
18
+ AT_SYMBOL = "@"
19
+ STAR = "*"
20
+
21
+ # Kafka schema pipeline (must match the Java library)
22
+ TOPIC = "construct-data-1"
23
+ TENANT_ID = "2cf76e5f-26ad-4f2c-bccc-f4bc1e7bfb64"
24
+ SCHEMA_ID = "6a2f9b55f7827435c6ff87c4"
25
+
26
+ # Legacy -> canonical key remapping applied before shipping to the schema.
27
+ SANITIZE_KEYS = {
28
+ "agentId": "agent_id",
29
+ "userId": "user_id",
30
+ "tenantId": "tenant_id",
31
+ "txnId": "txn_id",
32
+ }