Skip to content

MRJR0101/Eye-Witness

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

49 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Eye-Witness

Python 3.10+ License: MIT

Python observability library that unifies structured logging, error tracking, and distributed tracing into a single package. One init() call wires up structlog, Sentry SDK, and OpenTelemetry with sensible defaults. Every log line carries trace context. No network dependencies required.


Why Eye-Witness

Most observability setups require you to configure three separate systems that don't talk to each other. Eye-Witness connects them at the source:

  • Structured logging (structlog) produces JSON logs with context variables
  • Error tracking (Sentry SDK) captures exceptions with breadcrumb trails
  • Distributed tracing (OpenTelemetry) tracks timing, spans, and causality

The critical integration is the log correlation bridge: every structlog log line automatically carries trace_id and span_id from the active OpenTelemetry span. This means you can search your logs by trace ID and see exactly what happened during any traced operation.

Design Principles

  • Local-first -- works with zero config, no DSN, no collector, no network
  • Vendor-neutral -- OpenTelemetry exports to any backend (Jaeger, Grafana Tempo, Datadog, New Relic, SigNoz)
  • No-op safe -- empty Sentry DSN disables error tracking with zero side effects; no OTLP endpoint falls back to console export
  • Focused -- observability infrastructure only; never decides what your application does, only records what happened

Quick Start

Install

# With uv (recommended)
uv pip install -e ".[all]"

# With pip
pip install -e ".[all]"

Basic Usage

from eye_witness import init, shutdown
import structlog

# Wire up all three paths with one call
init(service_name="my-app")

log = structlog.get_logger()
log.info("app.started", version="1.0.0")

# Your application code here...
# Every log line now carries trace_id and span_id if a span is active

# Clean shutdown (flushes Sentry events and OpenTelemetry spans)
shutdown()

With OpenTelemetry Tracing

from eye_witness import init, shutdown
from opentelemetry import trace
import structlog

init(
    service_name="my-app",
    otel_exporter="console",  # or "otlp-http", "otlp-grpc"
)

log = structlog.get_logger()
tracer = trace.get_tracer("my-app")

with tracer.start_as_current_span("process_order") as span:
    span.set_attribute("order.id", "ORD-123")
    log.info("order.processing", order_id="ORD-123")
    # This log line automatically includes trace_id and span_id

    with tracer.start_as_current_span("validate"):
        log.info("order.validating")
        # Nested span -- trace_id stays the same, span_id updates

shutdown()

With Sentry Error Tracking

from eye_witness import init, shutdown
import sentry_sdk
import structlog

init(
    service_name="my-app",
    sentry_dsn="https://key@sentry.io/project",  # or empty string to disable
)

log = structlog.get_logger()

# Breadcrumbs record context before a crash
sentry_sdk.add_breadcrumb(
    category="db",
    message="Queried user table",
    level="info",
    data={"query_ms": 45, "rows": 10},
)

try:
    risky_operation()
except Exception as e:
    log.exception("operation.failed")       # Structured log with traceback
    sentry_sdk.capture_exception(e)         # Sends to Sentry with breadcrumbs

CLI Application Pattern

import atexit
from eye_witness import init, shutdown

init(service_name="my-cli-tool")
atexit.register(shutdown)  # Ensures spans and events flush before exit

# ... rest of your CLI application

Architecture

Eye-Witness is built on three paths that integrate through a shared log correlation bridge.

                    eye_witness.init()
                         |
            +------------+------------+
            |            |            |
        Path 1       Path 2       Path 3
       structlog    Sentry SDK   OpenTelemetry
            |            |            |
    JSON logging   breadcrumbs    spans + traces
    context vars   error capture  OTLP export
    6 patterns     5 categories   3 exporters
            |            |            |
            +-----+------+-----+-----+
                  |            |
          log correlation    shutdown()
          bridge injects     flushes both
          trace_id/span_id   Sentry + OTel

Path 1: Structured Logging (structlog)

Six configuration patterns, automatically selected or manually chosen:

Pattern When To Use
Production stdlib JSON Default for production; integrates with stdlib loggers
High-performance orjson Maximum speed; uses orjson + BytesLoggerFactory
Auto-detect TTY Pretty console in dev, JSON in production
Contextvars binding Thread-safe context (request_id, job_id)
ProcessorFormatter bridge Routes stdlib logs through structlog pipeline
Recommended pipeline Opinionated "best of all" configuration

Path 2: Error Tracking (Sentry SDK)

  • Five breadcrumb categories: http, query, ui.click, console, job
  • LoggingIntegration with event_level=None -- breadcrumbs from all log levels, but exceptions sent manually via capture_exception()
  • No-DSN pattern: empty string disables the SDK entirely (no network, no side effects)
  • GlitchTip compatible: change DSN only, keep all code

Path 3: Distributed Tracing (OpenTelemetry)

  • Three exporter options: Console (dev), OTLP HTTP (port 4318), OTLP gRPC (port 4317)
  • BatchSpanProcessor for production, SimpleSpanProcessor for dev
  • Resource attributes: service.name, service.version, deployment.environment
  • Vendor-neutral: works with any OTLP-compatible backend

The Bridge: Log Correlation

The _context.py module provides a structlog processor that reads the current OpenTelemetry span context and injects trace_id and span_id into every log event:

{
  "event": "order.processing",
  "order_id": "ORD-123",
  "trace_id": "0af7651916cd43dd8448eb211c80319c",
  "span_id": "b7ad6b7169203331",
  "level": "info",
  "timestamp": "2026-02-12T10:30:00.000Z"
}

This is the single highest-value integration: you can take any trace ID from your tracing backend and find every log line that happened during that trace.


Package Structure

src/eye_witness/
    __init__.py       Public API: init(), shutdown(), EyeWitnessConfig
    _config.py        EyeWitnessConfig dataclass with from_env() factory
    _init.py          Orchestrator: wires up all three paths in sequence
    _logging.py       Six structlog configuration patterns
    _sentry.py        Sentry SDK setup, LoggingIntegration, breadcrumbs
    _tracing.py       OpenTelemetry TracerProvider, exporters, processors
    _context.py       Log correlation bridge (trace_id/span_id injection)

Seven modules. That's it. No application helpers, no CLI decorators, no framework integrations. Eye-Witness is infrastructure -- it records what your application does, it never decides what your application does.


Configuration

Keyword Arguments

from eye_witness import init

init(
    service_name="my-app",
    service_version="1.0.0",
    environment="production",
    log_format="json",              # "json" | "console" | "auto"
    sentry_dsn="",                  # Empty = disabled
    otel_exporter="otlp-http",      # "console" | "otlp-http" | "otlp-grpc" | "none"
    otel_endpoint="",               # Empty = default for exporter type
)

Environment Variables

Variable Purpose Default
EW_SERVICE_NAME Service name in traces and logs "unknown"
EW_SERVICE_VERSION Service version "0.0.0"
EW_ENVIRONMENT Deployment environment "local"
EW_LOG_FORMAT Log output format "auto"
SENTRY_DSN Sentry project DSN "" (disabled)
SENTRY_ENVIRONMENT Sentry environment name "local"
OTEL_EXPORTER_OTLP_ENDPOINT OTLP collector endpoint (exporter default)
OTEL_EXPORTER_OTLP_HEADERS OTLP headers (e.g., API keys) ""
OTEL_TRACES_SAMPLER Sampling strategy "always_on"

Config Object (Full Control)

from eye_witness import init, EyeWitnessConfig

cfg = EyeWitnessConfig(
    service_name="my-app",
    service_version="2.0.0",
    environment="staging",
    log_format="json",
    sentry_dsn="https://key@sentry.io/1",
    otel_exporter="otlp-grpc",
    otel_endpoint="http://collector:4317",
)
init(config=cfg)

Zero-Config (Env Vars Only)

export EW_SERVICE_NAME=my-app
export SENTRY_DSN=https://key@sentry.io/1
export OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318
from eye_witness import init
init()  # Reads everything from environment

Compatible Backends

Error Tracking

Backend How To Connect
Sentry Set SENTRY_DSN to your Sentry project DSN
GlitchTip Set SENTRY_DSN to your GlitchTip instance DSN
Disabled Leave SENTRY_DSN empty

Tracing

Backend How To Connect
Console (dev) otel_exporter="console"
New Relic OTLP HTTP to https://otlp.nr-data.net, api-key header
Jaeger OTLP HTTP/gRPC to your Jaeger collector
Grafana Tempo OTLP HTTP/gRPC to your Tempo instance
Datadog OTLP to Datadog agent
Honeycomb OTLP HTTP with API key header
SigNoz OTLP HTTP/gRPC to SigNoz collector

No vendor SDK needed for any backend. Pure OpenTelemetry OTLP.


Testing

# Run tests
pytest -q

# With uv
uv run pytest -q

26 tests covering all three paths and the integration bridge.


Integration with Other Projects

Eye-Witness is designed as a shared dependency. Other projects consume it without modification:

# Install as editable dependency in another project
uv pip install -e "<project-root>[all]"
# In your project's startup
from eye_witness import init
init(service_name="codegraphx")

# All your structlog calls now carry trace context
# All your exceptions can go to Sentry
# All your spans export to your chosen backend

What Eye-Witness Does NOT Do

  • No application-specific helpers (CLI decorators, web middleware)
  • No context propagation utilities (inject/extract across services)
  • No metrics collection (traces and logs only)
  • No auto-instrumentation (manual instrumentation by design)
  • No session replay or profiling
  • No framework integrations (FastAPI, Django, Flask)

These belong in the consuming application, not in the shared infrastructure library.


Dependencies

Core

Package Purpose
structlog Structured logging with processor pipelines
sentry-sdk Error tracking and breadcrumb management

Tracing (optional, installed with [all])

Package Purpose
opentelemetry-api Tracing API
opentelemetry-sdk TracerProvider, processors, samplers
opentelemetry-exporter-otlp-proto-http OTLP over HTTP
opentelemetry-exporter-otlp-proto-grpc OTLP over gRPC

Performance (optional)

Package Purpose
orjson Fast JSON serialization for high-performance logging
rich Pretty console output in development

License

MIT

About

Python observability library unifying structlog, Sentry SDK, and OpenTelemetry

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors