logger-sdk-observability 0.0.1__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,681 @@
1
+ Metadata-Version: 2.4
2
+ Name: logger-sdk-observability
3
+ Version: 0.0.1
4
+ Summary: Structured logging framework with DI, queue pipeline, rotation, RabbitMQ and OpenTelemetry
5
+ Author-email: Evan Flores <e2002florespulido@gmail.com>
6
+ License: MIT License.
7
+ Project-URL: Homepage, https://github.com/EvanFlores/LoggerSDK
8
+ Project-URL: Repository, https://github.com/EvanFlores/LoggerSDK.git
9
+ Keywords: logging,structured-logging,opentelemetry,rabbitmq,tracing,async,queue-handler
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: pydantic>=2.12
14
+ Requires-Dist: pydantic-settings>=2.0
15
+ Requires-Dist: colorlog>=6.7
16
+ Requires-Dist: python-dotenv>=1.0
17
+ Requires-Dist: aiofiles>=24.1
18
+ Provides-Extra: amqp
19
+ Requires-Dist: pika>=1.3; extra == "amqp"
20
+ Requires-Dist: aio-pika>=9.4; extra == "amqp"
21
+ Provides-Extra: otel
22
+ Requires-Dist: opentelemetry-api>=1.27; extra == "otel"
23
+ Requires-Dist: opentelemetry-sdk>=1.27; extra == "otel"
24
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.27; extra == "otel"
25
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27; extra == "otel"
26
+ Requires-Dist: opentelemetry-instrumentation-logging>=0.48b0; extra == "otel"
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=8; extra == "dev"
29
+ Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
30
+ Requires-Dist: pytest-cov>=6; extra == "dev"
31
+ Requires-Dist: freezegun>=1.5; extra == "dev"
32
+ Requires-Dist: testcontainers>=4.8; extra == "dev"
33
+ Requires-Dist: ruff>=0.6; extra == "dev"
34
+ Requires-Dist: mypy>=1.11; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # Logger
38
+
39
+ A production-ready Python logging framework for modern backends, microservices, and AI pipelines.
40
+
41
+ It gives you a single, unified API for emitting structured logs that are **traceable, sampled, and shippable** — locally to console/file, asynchronously to a queue, over RabbitMQ, or to any OpenTelemetry-compatible backend.
42
+
43
+ ---
44
+
45
+ ## Table of Contents
46
+
47
+ - [Why](#why)
48
+ - [Features](#features)
49
+ - [Installation](#installation)
50
+ - [Quickstart](#quickstart)
51
+ - [Core Concepts](#core-concepts)
52
+ - [LoggerFactory (DI)](#loggerfactory-di)
53
+ - [LoggerConfig](#loggerconfig)
54
+ - [BoundLogger](#boundlogger)
55
+ - [Tracer & Trace Context](#tracer--trace-context)
56
+ - [Sampling](#sampling)
57
+ - [Caller Resolution](#caller-resolution)
58
+ - [Architecture](#architecture)
59
+ - [Configuration Reference](#configuration-reference)
60
+ - [Handlers](#handlers)
61
+ - [Console](#console)
62
+ - [Rotating File](#rotating-file)
63
+ - [RabbitMQ (sync & async)](#rabbitmq-sync--async)
64
+ - [Decorators](#decorators)
65
+ - [`@function_log`](#function_log)
66
+ - [`@class_log`](#class_log)
67
+ - [OpenTelemetry Integration](#opentelemetry-integration)
68
+ - [Framework Adapters](#framework-adapters)
69
+ - [End-to-End Examples](#end-to-end-examples)
70
+ - [Project Structure](#project-structure)
71
+ - [Testing](#testing)
72
+ - [Deployment](#deployment)
73
+ - [Roadmap](#roadmap)
74
+ - [Contributing](#contributing)
75
+ - [License](#license)
76
+
77
+ ---
78
+
79
+ ## Why
80
+
81
+ Logs in production break for predictable reasons:
82
+
83
+ - **No structure** — grep-ing free text doesn't scale.
84
+ - **Async + threads** lose call-site context.
85
+ - **No trace correlation** — you can't link a log line to the request that produced it.
86
+ - **Hot loops spam logs** — cost and noise in the log pipeline.
87
+ - **Large codebases** need per-module control.
88
+ - **Hard to extend** with new sinks (RabbitMQ, OTel, …) without forking.
89
+
90
+ This library addresses all of these with one API and one config.
91
+
92
+ ---
93
+
94
+ ## Features
95
+
96
+ - **Structured JSON** output (ELK / GCP / Datadog / Splunk ready)
97
+ - **W3C trace_id / span_id** correlation via OpenTelemetry or built-in `ContextVar`
98
+ - **Non-blocking** `QueueHandler` + `QueueListener` from day one
99
+ - **Rotating file handler** (size + time) with optional gzip rollover
100
+ - **RabbitMQ transport** — sync (`pika`) and async (`aio-pika`), batched, fail-open
101
+ - **OpenTelemetry** OTLP exporter — gRPC and HTTP
102
+ - **Per-module log levels**
103
+ - **Deterministic sampling** by `(trace_id, context, message)`
104
+ - **Caller resolution** — auto `file:line Class.method()` everywhere, including inside decorators
105
+ - **DI-based factory** — no hidden global state
106
+ - **Decorators** for functions and classes
107
+ - **Async-safe** — works in `asyncio` and thread pools
108
+
109
+ ---
110
+
111
+ ## Installation
112
+
113
+ ```bash
114
+ pip install logger
115
+ ```
116
+
117
+ With optional transports:
118
+
119
+ ```bash
120
+ pip install "logger[amqp]" # RabbitMQ (pika + aio-pika)
121
+ pip install "logger[otel]" # OpenTelemetry (OTLP gRPC + HTTP)
122
+ pip install "logger[all]" # everything
123
+ ```
124
+
125
+ From source:
126
+
127
+ ```bash
128
+ git clone https://github.com/EvanFloresLv/Logger.git
129
+ cd Logger
130
+ pip install -e ".[all]"
131
+ ```
132
+
133
+ Requires Python 3.10+.
134
+
135
+ ---
136
+
137
+ ## Quickstart
138
+
139
+ ```python
140
+ from logger import LoggerFactory, LoggerConfig
141
+
142
+ # 1. Build a config
143
+ config = LoggerConfig(
144
+ service_name="billing-api",
145
+ level="INFO",
146
+ directory="logs",
147
+ json_logs=True,
148
+ sampling={"rate": 0.1, "deterministic": True, "min_level": "WARNING"},
149
+ rotation={"max_bytes": 10_000_000, "backup_count": 5, "when": "midnight"},
150
+ )
151
+
152
+ # 2. Build a factory (one per process)
153
+ factory = LoggerFactory(config)
154
+
155
+ # 3. Get a bound logger
156
+ log = factory.get_logger().bind("startup")
157
+ log.info("service ready", extra={"version": "1.0.0"})
158
+
159
+ # 4. Always shut down at exit to drain the queue and close sinks
160
+ factory.shutdown()
161
+ ```
162
+
163
+ Console output (colored):
164
+
165
+ ```
166
+ [10:55:01] [INFO] [billing-api] [startup] - service ready
167
+ ```
168
+
169
+ File output (`logs/billing-api.log`, JSON lines):
170
+
171
+ ```json
172
+ {"timestamp":"2026-02-04T10:55:01.140Z","level":"INFO","message":"service ready","logger":"billing-api","service":"billing-api","context":"billing-api | startup","trace_id":"00000000000000000000000000000000","span_id":"0000000000000000","module":"app","function":"<module>","line":12,"process":1234,"thread":140123}
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Core Concepts
178
+
179
+ ### LoggerFactory (DI)
180
+
181
+ `LoggerFactory` is the **only** entrypoint. You construct it with a `LoggerConfig`, ask it for a `BoundLogger`, and call `shutdown()` at the end of the process.
182
+
183
+ ```python
184
+ factory = LoggerFactory(config)
185
+ log = factory.get_logger()
186
+ log2 = factory.bind(component="auth") # shortcut
187
+ log2.set_trace() # new trace_id
188
+ log2.info("login attempt", extra={"user_id": 42})
189
+ factory.shutdown()
190
+ ```
191
+
192
+ There is no global singleton. Two factories can coexist with different configs. Tests can build throwaway factories.
193
+
194
+ ### LoggerConfig
195
+
196
+ `LoggerConfig` is a Pydantic v2 `BaseSettings`. It can be built from kwargs, from a `.env` file, or from environment variables prefixed with `LOGGER__` (double underscore for nested keys).
197
+
198
+ ```python
199
+ # From kwargs
200
+ LoggerConfig(level="DEBUG", json_logs=False)
201
+
202
+ # From env
203
+ # LOGGER__LEVEL=DEBUG
204
+ # LOGGER__SAMPLING__RATE=0.5
205
+ # LOGGER__AMQP__URL=amqp://prod-rabbit:5672/
206
+ ```
207
+
208
+ All sub-settings are typed and validated at construction time.
209
+
210
+ ### BoundLogger
211
+
212
+ A `logging.LoggerAdapter` subclass. Every method (`info`, `warning`, …) accepts an `extra={…}` dict whose fields are merged into the JSON record.
213
+
214
+ ```python
215
+ log = factory.get_logger().bind(request_id="r-123")
216
+ log.info("started")
217
+ log.info("completed", extra={"duration_ms": 47})
218
+ ```
219
+
220
+ `bind()` returns a *new* `BoundLogger` with the new context merged — the original is untouched. Chaining is cheap:
221
+
222
+ ```python
223
+ log.bind(a=1).bind(b=2).info("x") # both a and b are in the record
224
+ ```
225
+
226
+ ### Tracer & Trace Context
227
+
228
+ `factory.tracer` (or any `BoundLogger`) exposes:
229
+
230
+ ```python
231
+ log.set_trace() # generate + return a new trace_id
232
+ log.set_trace("custom-id") # use a known id
233
+ log.set_span()
234
+
235
+ # Reading is automatic — every record carries:
236
+ # trace_id (32 hex)
237
+ # span_id (16 hex)
238
+ ```
239
+
240
+ If OpenTelemetry is installed and configured (`otel.enabled=True`), the logger reads the **active OTel span** from the context — so any instrumented code (DB, HTTP, gRPC) automatically correlates with your logs.
241
+
242
+ If OTel is **not** installed, the logger uses an internal `ContextVar` so the values flow through `asyncio.Tasks` and `concurrent.futures`.
243
+
244
+ ### Sampling
245
+
246
+ `SamplingSettings(rate, min_level, deterministic)`:
247
+
248
+ - `rate=0.1` keeps 10% of DEBUG/INFO records.
249
+ - `min_level="WARNING"` always keeps WARNING and above (sampling is below the floor).
250
+ - `deterministic=True` hashes `(trace_id, context, message)` so the same record is consistently kept or dropped — important so a single trace is never partially sampled.
251
+
252
+ ```python
253
+ config = LoggerConfig(
254
+ sampling={"rate": 0.05, "deterministic": True, "min_level": "WARNING"}
255
+ )
256
+ ```
257
+
258
+ ### Caller Resolution
259
+
260
+ Every record carries `file:line Class.method()` automatically. The library uses `logging.Logger.findCaller` (stdlib-cached) and walks back through frames to enrich with the class name. Decorator wrappers and adapter methods are filtered out, so the line number and method name always point at **your** code, not the framework's.
261
+
262
+ ---
263
+
264
+ ## Architecture
265
+
266
+ ```
267
+ user code
268
+
269
+
270
+ LoggerFactory.create(config)
271
+
272
+
273
+ BoundLogger (LoggerAdapter)
274
+ ├── .bind("ctx") → BoundLogger
275
+ ├── .set_trace() / .set_span()
276
+ └── emits via stdlib logging
277
+
278
+
279
+ ┌─────────────────────┐
280
+ │ QueueHandler │ (in-memory, non-blocking)
281
+ │ + OverflowFilter │
282
+ └──────────┬──────────┘
283
+
284
+
285
+ QueueListener
286
+ (single background thread)
287
+
288
+ ┌──────────────┬───────┴────────┬──────────────┐
289
+ ▼ ▼ ▼ ▼
290
+ ConsoleHandler RotatingFile AMQPSyncHandler AMQPAsyncHandler
291
+ (colored) (JSON, rotated) (pika batch) (aio-pika)
292
+
293
+
294
+ RabbitMQ
295
+
296
+
297
+ OTel Collector
298
+
299
+
300
+ OTLPExporter (gRPC or HTTP)
301
+
302
+
303
+ Backend (Tempo / Jaeger / ELK)
304
+ ```
305
+
306
+ Key properties:
307
+
308
+ - **One producer path** — your code only ever talks to a `QueueHandler`. Sinks are owned by a single `QueueListener` thread, so a slow file system or a dead broker never blocks the producer.
309
+ - **Console is direct** — the console handler is *not* queued, so developers see logs immediately even if a sink is broken.
310
+ - **OpenTelemetry is optional** — when enabled, it sets the global `TracerProvider` and instruments stdlib `logging`, so all records (from this library and from third-party code) carry the same `trace_id` / `span_id`.
311
+
312
+ ---
313
+
314
+ ## Configuration Reference
315
+
316
+ | Field | Type | Default | Notes |
317
+ |---|---|---|---|
318
+ | `service_name` | `str` | `"app"` | root logger name + OTel `service.name` |
319
+ | `level` | `str` | `"INFO"` | global level (`DEBUG`/`INFO`/…) |
320
+ | `directory` | `str` | `"logs"` | file handler root |
321
+ | `json_logs` | `bool` | `True` | structured file output |
322
+ | `date_format` | `str` | ISO 8601 ms | timestamp format |
323
+ | `module_levels` | `dict[str, str]` | `{}` | per-logger level overrides |
324
+ | `sampling.rate` | `float` | `1.0` | DEBUG/INFO sample ratio |
325
+ | `sampling.deterministic` | `bool` | `False` | hash on (trace, ctx, msg) |
326
+ | `sampling.min_level` | `str` | `"WARNING"` | never sampled below this |
327
+ | `rotation.max_bytes` | `int \| None` | `10_000_000` | size-based rotation |
328
+ | `rotation.backup_count` | `int` | `5` | retained rotated files |
329
+ | `rotation.when` | `str \| None` | `None` | time-based key (`"midnight"`, `"H"`, …) |
330
+ | `rotation.interval` | `int` | `1` | period multiplier |
331
+ | `rotation.utc` | `bool` | `False` | use UTC for time rotation |
332
+ | `rotation.compress` | `bool` | `True` | gzip rotated files |
333
+ | `queue.capacity` | `int` | `10_000` | in-memory queue size |
334
+ | `queue.overflow` | `str` | `"drop_oldest"` | `drop_oldest` / `drop_newest` / `block` |
335
+ | `queue.flush_on_exit` | `bool` | `True` | flush on `factory.shutdown()` |
336
+ | `console.enabled` | `bool` | `True` | |
337
+ | `console.colors` | `bool` | `True` | |
338
+ | `console.destination` | `str` | `"stdout"` | `"stdout"` or `"stderr"` |
339
+ | `amqp` | `AMQPSettings \| None` | `None` | RabbitMQ sink (see below) |
340
+ | `otel` | `OTelSettings` | disabled | OTel exporter (see below) |
341
+
342
+ Environment variable mapping (double underscore = nested key):
343
+
344
+ ```bash
345
+ export LOGGER__SERVICE_NAME=billing-api
346
+ export LOGGER__LEVEL=DEBUG
347
+ export LOGGER__SAMPLING__RATE=0.1
348
+ export LOGGER__SAMPLING__DETERMINISTIC=true
349
+ export LOGGER__AMQP__URL=amqp://prod-rabbit:5672/
350
+ export LOGGER__AMQP__TRANSPORT=async
351
+ export LOGGER__OTEL__ENABLED=true
352
+ export LOGGER__OTEL__PROTOCOL=grpc
353
+ export LOGGER__OTEL__OTLP_ENDPOINT=http://otel-collector:4317
354
+ ```
355
+
356
+ ---
357
+
358
+ ## Handlers
359
+
360
+ ### Console
361
+
362
+ Always-on, immediate, colored. Honors `console.destination` so you can route WARNING+ to stderr if you want.
363
+
364
+ ### Rotating File
365
+
366
+ - **Size-based** when `rotation.when is None` (default) — `RotatingFileHandler` semantics.
367
+ - **Time-based** when `rotation.when` is set — `TimedRotatingFileHandler` semantics.
368
+ - `rotation.compress=True` gzips rolled files on rollover.
369
+
370
+ Output is one JSON object per line. The file is named `<directory>/<service_name>.log`.
371
+
372
+ ### RabbitMQ (sync & async)
373
+
374
+ ```python
375
+ config = LoggerConfig(
376
+ amqp={
377
+ "url": "amqp://guest:guest@localhost/",
378
+ "exchange": "logs",
379
+ "exchange_type": "fanout", # direct | topic | fanout | headers
380
+ "routing_key": "",
381
+ "queue": "logs",
382
+ "durable": True,
383
+ "batch_size": 100,
384
+ "flush_interval_s": 1.0,
385
+ "transport": "sync", # or "async" (aio-pika)
386
+ "fail_open": True, # degrade to file when broker is down
387
+ "connect_timeout_s": 5.0,
388
+ "max_retries": 5,
389
+ }
390
+ )
391
+ ```
392
+
393
+ - **Sync** uses `pika.BlockingConnection` with a batched publish loop. Best for worker processes, scripts, CLIs.
394
+ - **Async** uses `aio-pika` and is safe to use from an event loop. Best for `asyncio` services.
395
+
396
+ Both handlers serialize records as JSON, batch up to `batch_size` records or `flush_interval_s` seconds, and flush on close. Connection failures with `fail_open=True` log a single stderr warning and continue with the file handler.
397
+
398
+ A consumer example lives in [`examples/rabbitmq_consumer.py`](examples/rabbitmq_consumer.py).
399
+
400
+ ---
401
+
402
+ ## Decorators
403
+
404
+ ### `@function_log`
405
+
406
+ ```python
407
+ from logger.decorators import function_log
408
+
409
+ @function_log(show_args=False, show_result=False)
410
+ def add(x, y):
411
+ return x + y
412
+ ```
413
+
414
+ Logs `Executing` and `Finished` entries with timing, the calling module, the function name, and (if present) the enclosing class. Decorator overhead is negligible — `inspect.getmodule` is cached and the resolved context is reused.
415
+
416
+ ### `@class_log`
417
+
418
+ ```python
419
+ from logger.decorators import class_log
420
+
421
+ @class_log()
422
+ class OrderService:
423
+ def place(self, order): ...
424
+ @classmethod
425
+ def from_dict(cls, raw): ...
426
+ @staticmethod
427
+ def _validate(order): ... # private — skipped
428
+ ```
429
+
430
+ Wraps every **public** callable (instance / classmethod / staticmethod) with `function_log`. Private names (starting with `_`) are skipped. The decorator is `__slots__`- and frozen-class-safe: if `setattr` fails, a `RuntimeWarning` is emitted and that method is left untouched.
431
+
432
+ ---
433
+
434
+ ## OpenTelemetry Integration
435
+
436
+ ```python
437
+ config = LoggerConfig(
438
+ service_name="billing-api",
439
+ otel={
440
+ "enabled": True,
441
+ "otlp_endpoint": "http://otel-collector:4317", # gRPC
442
+ "protocol": "grpc", # or "http"
443
+ "http_endpoint": "http://otel-collector:4318", # used when protocol="http"
444
+ "insecure": True,
445
+ "sample_ratio": 0.1,
446
+ "headers": {"x-api-key": "..."},
447
+ },
448
+ )
449
+ ```
450
+
451
+ When `otel.enabled=True`, the factory:
452
+
453
+ 1. Creates a `TracerProvider` with a `Resource` of `service.name=<service_name>`.
454
+ 2. Installs a `BatchSpanProcessor` pointing at the chosen OTLP exporter (gRPC port 4317, HTTP port 4318 by default).
455
+ 3. Applies `TraceIdRatioBased(sample_ratio)`.
456
+ 4. Calls `LoggingInstrumentor().instrument(set_logging_format=False)` so any log emitted through stdlib `logging` (third-party libs included) also gets the active `trace_id` / `span_id`.
457
+
458
+ After this, **every** log line — yours and from any library — carries the same `trace_id` and `span_id` as the active span, formatted as 32-hex / 16-hex per W3C TraceContext.
459
+
460
+ A minimal local stack (Collector + Jaeger) is in [`docker-compose.yml`](docker-compose.yml).
461
+
462
+ ---
463
+
464
+ ## Framework Adapters
465
+
466
+ ### FastAPI
467
+
468
+ ```python
469
+ from fastapi import FastAPI, Request
470
+ from logger import LoggerFactory, LoggerConfig
471
+
472
+ factory = LoggerFactory(LoggerConfig(
473
+ service_name="api",
474
+ otel={"enabled": True, "otlp_endpoint": "http://otel-collector:4317", "protocol": "grpc"},
475
+ ))
476
+ app = FastAPI(lifespan=factory.lifecycle)
477
+
478
+ @app.middleware("http")
479
+ async def access_log(request: Request, call_next):
480
+ log = factory.get_logger().bind(path=request.url.path, method=request.method)
481
+ log.set_trace()
482
+ log.set_span()
483
+ start = time.perf_counter()
484
+ response = await call_next(request)
485
+ log.info("request", extra={
486
+ "type": "access",
487
+ "status": response.status_code,
488
+ "elapsed_ms": round((time.perf_counter() - start) * 1000, 2),
489
+ })
490
+ return response
491
+ ```
492
+
493
+ `factory.lifecycle` is an `asynccontextmanager` that calls `factory.shutdown()` on app exit.
494
+
495
+ ---
496
+
497
+ ## End-to-End Examples
498
+
499
+ ### 1. Local development (console + file)
500
+
501
+ ```python
502
+ from logger import LoggerFactory, LoggerConfig
503
+
504
+ factory = LoggerFactory(LoggerConfig(service_name="dev", level="DEBUG"))
505
+ log = factory.get_logger().bind(component="auth")
506
+ log.debug("checking token")
507
+ log.info("user logged in", extra={"user_id": 42})
508
+ log.error("db error", extra={"query": "SELECT ..."})
509
+ factory.shutdown()
510
+ ```
511
+
512
+ ### 2. Production with OTel + RabbitMQ
513
+
514
+ ```python
515
+ from logger import LoggerFactory, LoggerConfig
516
+
517
+ factory = LoggerFactory(LoggerConfig(
518
+ service_name="billing",
519
+ level="INFO",
520
+ json_logs=True,
521
+ rotation={"max_bytes": 50_000_000, "backup_count": 10, "when": "midnight"},
522
+ amqp={
523
+ "url": "amqp://rabbit:5672/",
524
+ "exchange": "logs.fanout",
525
+ "queue": "billing-logs",
526
+ "transport": "async",
527
+ },
528
+ otel={
529
+ "enabled": True,
530
+ "otlp_endpoint": "http://otel-collector:4317",
531
+ "protocol": "grpc",
532
+ "sample_ratio": 0.1,
533
+ },
534
+ ))
535
+
536
+ log = factory.get_logger().bind(component="invoice")
537
+ with factory.tracer.start_span("create-invoice") as span:
538
+ span.set_attribute("invoice.id", "inv-123")
539
+ log.info("invoice created", extra={"amount": 999.0})
540
+ factory.shutdown()
541
+ ```
542
+
543
+ ### 3. With decorators
544
+
545
+ ```python
546
+ from logger import LoggerFactory, LoggerConfig
547
+ from logger.decorators import function_log, class_log
548
+
549
+ factory = LoggerFactory(LoggerConfig(service_name="orders"))
550
+
551
+ @class_log()
552
+ class OrderService:
553
+ def place(self, order):
554
+ ...
555
+
556
+ @function_log(show_args=False)
557
+ def notify(order_id):
558
+ ...
559
+
560
+ svc = OrderService()
561
+ svc.place({"id": 1})
562
+ notify(1)
563
+ factory.shutdown()
564
+ ```
565
+
566
+ ---
567
+
568
+ ## Project Structure
569
+
570
+ ```
571
+ src/
572
+ ├── __init__.py # Public API re-exports
573
+ ├── errors.py # Exception hierarchy (renamed from exceptions.py)
574
+ ├── exceptions.py # Backwards-compat shim → errors.py
575
+ ├── config/ # Settings models
576
+ │ ├── __init__.py
577
+ │ ├── logger_config.py # LoggerConfig (top-level)
578
+ │ ├── config.py # Backwards-compat shim
579
+ │ └── settings/
580
+ │ ├── sampling.py # SamplingSettings
581
+ │ ├── rotation.py # RotationSettings
582
+ │ ├── queue.py # QueueSettings
583
+ │ ├── console.py # ConsoleSettings
584
+ │ ├── amqp.py # AMQPSettings
585
+ │ └── otel.py # OTelSettings
586
+ ├── core/
587
+ │ ├── tracer.py # OTel + ContextVar facade
588
+ │ ├── context.py # BoundLogger
589
+ │ ├── caller.py # frame-walking caller resolution
590
+ │ ├── filters/
591
+ │ │ ├── sampling.py # SamplingFilter, DeterministicSamplingFilter
592
+ │ │ └── overflow.py # OverflowFilter, QueueCapacityProbe
593
+ │ ├── formatters/
594
+ │ │ ├── json_formatter.py # stable JSON for files / structured sinks
595
+ │ │ └── colored_formatter.py # colored console output
596
+ │ └── transport/
597
+ │ ├── serialize.py # record_to_json_bytes() — single source of truth
598
+ │ └── batch.py # BatchBuffer — sync thread-safe buffer
599
+ ├── handlers/
600
+ │ ├── console.py # immediate colored handler
601
+ │ ├── queue.py # QueueHandler + QueueListener pipeline
602
+ │ ├── rotating_file.py # size + time + gzip (GzipOnRolloverMixin)
603
+ │ └── amqp/ # AMQP transport package
604
+ │ ├── __init__.py
605
+ │ ├── common.py # serialize_record + settings validation
606
+ │ ├── sync.py # AMQPSyncHandler (pika)
607
+ │ ├── async_handler.py # AMQPAsyncHandler (aio-pika + drain barrier)
608
+ │ ├── async_loop.py # LoopRunner — dedicated event-loop thread
609
+ │ └── factory.py # make_amqp_handler dispatch
610
+ ├── factory/ # DI entrypoint package
611
+ │ ├── __init__.py
612
+ │ ├── logger_factory.py # LoggerFactory class
613
+ │ ├── registry.py # active-factory registry
614
+ │ ├── builder.py # build_handlers(config) → HandlerPlan
615
+ │ └── factory.py # Backwards-compat shim
616
+ ├── integrations/
617
+ │ └── opentelemetry/ # OTel integration package
618
+ │ ├── __init__.py
619
+ │ ├── provider.py # configure_opentelemetry()
620
+ │ └── exporter.py # OTLPSettingsAdapter (gRPC / HTTP)
621
+ └── decorators/
622
+ ├── __init__.py
623
+ ├── base.py # active_factory(), build_context_string()
624
+ ├── functions.py # @function_log
625
+ └── classes.py # @class_log
626
+ ```
627
+
628
+ ---
629
+
630
+ ## Testing
631
+
632
+ ```bash
633
+ pip install -e ".[dev]"
634
+ pytest tests/ -v
635
+ ```
636
+
637
+ The test suite is split into `tests/unit` (fast, no external services) and `tests/integration` (RabbitMQ + OTel collector via `testcontainers`).
638
+
639
+ ```bash
640
+ RUN_INTEGRATION=1 pytest tests/integration -v
641
+ ```
642
+
643
+ ---
644
+
645
+ ## Deployment
646
+
647
+ The library is a pure-Python package with optional extras. It runs anywhere CPython 3.10+ runs:
648
+
649
+ - **Python SDK library** — `pip install logger[all]`
650
+ - **FastAPI / Flask / Starlette** — use the lifespan example above
651
+ - **Serverless** — Cloud Run / AWS Lambda / Azure Functions. Configure with `directory="/tmp/logs"` and set `rotation.when=None` (no time rotation) since the filesystem is ephemeral.
652
+ - **Workers / CLIs** — `factory.shutdown()` at the end of `main()`.
653
+
654
+ ---
655
+
656
+ ## Roadmap
657
+
658
+ - Pluggable sinks via entry-points (Datadog, Loki, CloudWatch)
659
+ - A `LogQL`/`Grok` examples page
660
+ - A `structlog` adapter for users who want to keep that API
661
+ - Built-in PII redaction filters
662
+
663
+ ---
664
+
665
+ ## Contributing
666
+
667
+ PRs welcome. Please run `ruff check`, `mypy src/logger`, and `pytest` before submitting. Add tests for new behavior.
668
+
669
+ ---
670
+
671
+ ## License
672
+
673
+ MIT — see [`LICENSE`](LICENSE).
674
+
675
+ ---
676
+
677
+ ## Contact
678
+
679
+ Maintainer: **Evan Flores**
680
+ Email: `efloresp06@liverpool.com.mx`
681
+ Organization: **Liverpool**
@@ -0,0 +1,5 @@
1
+ logger_sdk_observability-0.0.1.dist-info/licenses/LICENSE,sha256=lXbdeDrL-YBsDgX64P0ARSkgkGIv_G7TNgeeOyWudok,12
2
+ logger_sdk_observability-0.0.1.dist-info/METADATA,sha256=0YGSVe3plsimPQB3hc-Ry3vKSy4tfQVT9fwi2OMlVZ0,24399
3
+ logger_sdk_observability-0.0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
4
+ logger_sdk_observability-0.0.1.dist-info/top_level.txt,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
5
+ logger_sdk_observability-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ MIT License.