Skip to main content

tgedr-observability

Coverage PyPI

overview

This repository is an early-stage observability toolkit built around a small Python package and a companion local monitoring stack. The Docker side is the main operational surface: an OpenTelemetry collector receives telemetry and routes metrics, logs, and traces into dedicated backends for storage and analysis. In practice, the project acts as a foundation for collecting, storing, and exploring observability data rather than as a finished application.

development

  • main requirements:

    • uv
    • bash
  • Clone the repository like this:

    git clone git@github.com:jtviegas/observability
    
  • cd into the folder: cd observability

  • install requirements: ./helper.sh reqs

source code guide

The Python package lives under src/tgedr_observability and currently exposes three modules:

  • commons.py: shared constants and common types.
  • metrics.py: singleton wrapper for OpenTelemetry metrics setup and recording.
  • logs.py: singleton wrapper for OpenTelemetry logs setup and recording.

commons.py

commons.py contains:

  • LOCAL_METRICS_URL: default local OTLP HTTP endpoint for metrics (http://localhost:4318/v1/metrics).
  • OTLP environment variable names:
    • TGEDR_OBSERVABILITY_SERVICE
    • TGEDR_OBSERVABILITY_EXPORTER_ENDPOINT
    • TGEDR_OBSERVABILITY_EXPORTER_HEADERS
    • TGEDR_OBSERVABILITY_EXPORTER_FILE
  • OtlpConfig: frozen dataclass with service, optional http_exporter_endpoint, optional http_exporter_headers, and optional file_exporter_url.
  • OtlpConfig.resolve_from_env(): helper that builds an OtlpConfig from env vars.
    • Returns a config only when both service and endpoint are present.
    • Parses headers from JSON when provided.
    • Includes file_exporter_url from TGEDR_OBSERVABILITY_EXPORTER_FILE when set.
    • Returns None when required env vars are missing.
  • ObservabilityError: package-specific exception used for observability validation errors.

metrics.py

Metrics is a singleton manager that lazily initializes a global OpenTelemetry MeterProvider.

Bootstrap behavior:

  1. Metrics.instance(otlp_config=None) returns the singleton object when bootstrap succeeds.
  2. If the provider is not initialized yet, instance() resolves configuration in this order:
  • explicit otlp_config argument, then
  • OtlpConfig.resolve_from_env().
  1. When a config is available, private method __bootstrap(otlp_config) runs and configures:
  • Resource with service.name from otlp_config.service.
  • ConsoleMetricExporter through a PeriodicExportingMetricReader (always enabled).
  • Optional file exporter (ConsoleMetricExporter writing to a file) when otlp_config.file_exporter_url is set. The parent directory is created automatically.
  • Optional OTLP HTTP exporter (OTLPMetricExporter) when otlp_config.http_exporter_endpoint is set.
  1. The configured provider is installed globally with metrics.set_meter_provider(provider).
  2. If no config can be resolved, instance() returns None.

Metric naming convention:

  • Metric names must have at least two dot-separated parts, for example orders.api.requests.
  • Everything before the last segment is used as meter scope name.
  • Last segment is used as instrument name.
  • Invalid names raise ObservabilityError.

Instruments managed by the class:

  • Counter path: internally uses create_up_down_counter.
  • Gauge path: uses create_gauge with unit inferred from suffix (_s -> s, otherwise 1).
  • Histogram path: uses create_histogram with the same unit inference.

Public methods:

  • add_to_counter(name, value, attributes=None)
  • add_to_gauge(name, value, attributes=None)
  • add_to_histogram(name, value, attributes=None)
  • force_flush(timeout_millis=10000)
  • shutdown()
  • app_shutdown() (flush + shutdown convenience hook; no-op if instance() resolves to None)

logs.py

Logs is a singleton manager that lazily initializes a global OpenTelemetry LoggerProvider.

Bootstrap behavior:

  1. Logs.instance(otlp_config=None) returns the singleton object when bootstrap succeeds.
  2. If the provider is not initialized yet, instance() resolves configuration in this order:
  • explicit otlp_config argument, then
  • OtlpConfig.resolve_from_env().
  1. When a config is available, private method __bootstrap(otlp_config) runs and configures:
  • Resource with service.name from otlp_config.service.
  • BatchLogRecordProcessor(ConsoleLogRecordExporter()) (always enabled).
  • Optional file exporter (ConsoleLogRecordExporter writing to a file) when otlp_config.file_exporter_url is set. The parent directory is created automatically.
  • Optional BatchLogRecordProcessor(OTLPLogExporter(...)) when otlp_config.http_exporter_endpoint is set.
  1. The provider is installed globally with set_logger_provider(provider).
  2. LoggingHandler is attached through logging.basicConfig(..., force=True).
  3. If no config can be resolved, instance() returns None.

Public methods:

  • force_flush(timeout_millis=10000)
  • shutdown() (also detaches and closes the installed handler)
  • app_shutdown() (flush + shutdown convenience hook; no-op if instance() resolves to None)

usage examples

You can bootstrap in two ways:

  • Pass an explicit OtlpConfig to Metrics.instance(...) / Logs.instance(...).
  • Set environment variables and let instance() load config automatically.

Environment-only bootstrap requires:

  • TGEDR_OBSERVABILITY_SERVICE
  • TGEDR_OBSERVABILITY_EXPORTER_ENDPOINT
  • Optional TGEDR_OBSERVABILITY_EXPORTER_HEADERS as a JSON object string.
  • Optional TGEDR_OBSERVABILITY_EXPORTER_FILE — path to a file where telemetry is also written (parent directories are created automatically).

Example:

export TGEDR_OBSERVABILITY_SERVICE="orders-service"
export TGEDR_OBSERVABILITY_EXPORTER_ENDPOINT="http://localhost:4318/v1/metrics"
export TGEDR_OBSERVABILITY_EXPORTER_HEADERS='{"Authorization":"Bearer your-token"}'
export TGEDR_OBSERVABILITY_EXPORTER_FILE="/var/log/myapp/telemetry.log"

For logs, use the logs endpoint in TGEDR_OBSERVABILITY_EXPORTER_ENDPOINT:

export TGEDR_OBSERVABILITY_EXPORTER_ENDPOINT="http://localhost:4318/v1/logs"

metrics

from tgedr_observability.commons import LOCAL_METRICS_URL, OtlpConfig
from tgedr_observability.metrics import Metrics

metrics_manager = Metrics.instance(
  otlp_config=OtlpConfig(service="orders-service", http_exporter_endpoint=LOCAL_METRICS_URL),
)
if metrics_manager is None:
  raise RuntimeError("Metrics bootstrap failed: missing config")

metrics_manager.add_to_counter("orders.api.requests", 1, {"route": "/orders"})
metrics_manager.add_to_histogram("orders.api.latency_s", 0.123, {"route": "/orders"})
metrics_manager.force_flush()

Metrics with file exporter:

from tgedr_observability.commons import OtlpConfig
from tgedr_observability.metrics import Metrics

metrics_manager = Metrics.instance(
  otlp_config=OtlpConfig(
    service="orders-service",
    http_exporter_endpoint="http://localhost:4318/v1/metrics",
    file_exporter_url="/var/log/myapp/metrics.log",
  ),
)
if metrics_manager is None:
  raise RuntimeError("Metrics bootstrap failed: missing config")

metrics_manager.add_to_counter("orders.api.requests", 1, {"route": "/orders"})
metrics_manager.force_flush()

Metrics with environment-only bootstrap:

from tgedr_observability.metrics import Metrics

metrics_manager = Metrics.instance()
if metrics_manager is None:
  raise RuntimeError("Metrics bootstrap failed: set TGEDR_OBSERVABILITY_* env vars")

metrics_manager.add_to_counter("orders.api.requests", 1, {"route": "/orders"})
metrics_manager.force_flush()

logs

import logging
from tgedr_observability.commons import OtlpConfig
from tgedr_observability.logs import Logs

logs_manager = Logs.instance(
  otlp_config=OtlpConfig(service="orders-service", http_exporter_endpoint="http://localhost:4318/v1/logs"),
)
if logs_manager is None:
  raise RuntimeError("Logs bootstrap failed: missing config")

logger = logging.getLogger(__name__)
logger.info("orders api started")
logs_manager.force_flush()

Logs with file exporter:

import logging
from tgedr_observability.commons import OtlpConfig
from tgedr_observability.logs import Logs

logs_manager = Logs.instance(
  otlp_config=OtlpConfig(
    service="orders-service",
    http_exporter_endpoint="http://localhost:4318/v1/logs",
    file_exporter_url="/var/log/myapp/logs.log",
  ),
)
if logs_manager is None:
  raise RuntimeError("Logs bootstrap failed: missing config")

logger = logging.getLogger(__name__)
logger.info("orders api started")
logs_manager.force_flush()

Logs with environment-only bootstrap:

import logging
from tgedr_observability.logs import Logs

logs_manager = Logs.instance()
if logs_manager is None:
  raise RuntimeError("Logs bootstrap failed: set TGEDR_OBSERVABILITY_* env vars")

logger = logging.getLogger(__name__)
logger.info("orders api started")
logs_manager.force_flush()

graceful shutdown

Register app-level shutdown hooks in your entrypoint:

import atexit
from tgedr_observability.logs import Logs
from tgedr_observability.metrics import Metrics

atexit.register(Logs.app_shutdown)
atexit.register(Metrics.app_shutdown)

tests and coverage

Run tests:

./helper.sh test

Run and print coverage:

./helper.sh test_coverage

Current unit tests cover all Python code under src/.

observability stack

VictoriaMetrics

victoriametrics/victoria-metrics is the metrics database in this stack. It stores time-series data such as request counts, latency measurements, resource usage, and any custom application metrics sent through OpenTelemetry. In this project, the collector forwards metrics to VictoriaMetrics through its OpenTelemetry ingestion endpoint, and Grafana can then query that stored data for dashboards and operational analysis.

Loki

grafana/loki is the log storage backend. Loki is designed to ingest and organize logs efficiently, making it a good fit for centralizing application and infrastructure logs without the overhead of a heavier full-text indexing platform. Here, the OpenTelemetry collector sends log data to Loki so logs can be explored alongside metrics and traces, which makes it easier to correlate failures, warnings, and service behavior across the same time window.

Jaeger

jaegertracing/all-in-one is the distributed tracing backend. It collects and visualizes traces, which show how a request flows across services and how long each span takes, making it useful for debugging latency and request-path failures. The all-in-one image bundles Jaeger's main components into a single container, which keeps this setup simple for local or small-scale environments while still providing a usable trace exploration surface.

securing the collector

⚠️ Important: The collector requires a valid Bearer token on every request. The OTLP HTTP receiver is protected with Bearer token authentication using the OpenTelemetry Collector's bearertokenauth extension. Any request to the collector endpoint (localhost:4318 or the remote endpoint) without an Authorization: Bearer <token> header will be rejected with HTTP 401 Unauthorized. This applies to all telemetry data—traces, metrics, and logs.

configuration

  1. Set the token. Add a strong random secret to your .secrets file (which is git-ignored and loaded by helper.sh):

    export COLLECTOR_TOKEN="$(openssl rand -hex 32)"
    
  2. Rebuild and push the collector image so the updated config is baked in:

    ./helper.sh build_push_collector
    
  3. Start the stack. The docker-compose.yml passes COLLECTOR_TOKEN through as an environment variable so the collector reads it at runtime:

    cd docker/observability && docker compose up -d
    

sending telemetry

All telemetry ingestion requires the token header. Any client sending OTLP over HTTP must include the Authorization: Bearer <token> header on every call, or the request will be rejected:

Authorization: Bearer <your-token>

Examples:

With telemetrygen (as used in helper.sh):

telemetrygen traces --otlp-http \
  --otlp-header "Authorization=\"Bearer $COLLECTOR_TOKEN\"" \
  --traces 1

With the OpenTelemetry SDK, configure the exporter headers via the OTEL_EXPORTER_OTLP_HEADERS environment variable (applied to all outbound requests):

export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer $COLLECTOR_TOKEN"

If the token is missing or invalid, the collector will return 401 Unauthorized. Verify the token is set correctly in your environment before troubleshooting other issues.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tgedr_observability-0.0.3.tar.gz (8.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tgedr_observability-0.0.3-py3-none-any.whl (9.9 kB view details)

Uploaded Python 3

File details

Details for the file tgedr_observability-0.0.3.tar.gz.

File metadata

  • Download URL: tgedr_observability-0.0.3.tar.gz
  • Upload date:
  • Size: 8.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for tgedr_observability-0.0.3.tar.gz
Algorithm Hash digest
SHA256 d54401316446f470c94e4e2b0cc95702f4958da82357491082278ead79b63b67
MD5 aefaf4b6b2a5b3a070e8d972beeff2e0
BLAKE2b-256 e6bde8ed0f875e400fbe49c8b2a2f922e725047de74ce46b98609d5128654fb3

See more details on using hashes here.

File details

Details for the file tgedr_observability-0.0.3-py3-none-any.whl.

File metadata

  • Download URL: tgedr_observability-0.0.3-py3-none-any.whl
  • Upload date:
  • Size: 9.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for tgedr_observability-0.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 1481e19d001f08e06965fc80fa32a4f7debc776543666387b3e4f644c9ac1ac2
MD5 73dac6cd17ed16c87871f3e718e9adb0
BLAKE2b-256 69b73abadd3fbf9df2e91cd0224b9fb760cf14c67ce412b4de2e42eb88a648e0

See more details on using hashes here.

Release history Release notifications | RSS feed

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

This release

0.0.3 This release

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page