Metadata-Version: 2.4
Name: grass-metrics
Version: 0.2.0
Summary: Unified metrics library supporting DataDog (DogStatsD) and OpenTelemetry/OpenObserve with automatic backend selection and dual-write mode.
Author: Alex
License-Expression: MIT
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Monitoring
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: datadog
Requires-Dist: datadog>=0.44.0; extra == "datadog"
Provides-Extra: otel
Requires-Dist: opentelemetry-api>=1.20.0; extra == "otel"
Requires-Dist: opentelemetry-sdk>=1.20.0; extra == "otel"
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.20.0; extra == "otel"
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20.0; extra == "otel"
Provides-Extra: all
Requires-Dist: grass-metrics[datadog,otel]; extra == "all"
Provides-Extra: dev
Requires-Dist: grass-metrics[all]; extra == "dev"
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# grass-metrics

Unified metrics library for Grass projects. Supports **DataDog** (DogStatsD/UDP) and **OpenTelemetry/OpenObserve** (OTLP gRPC/HTTP) with automatic backend selection and dual-write mode.

## Installation

```bash
# Core (no backend dependencies -- bring your own)
pip install grass-metrics

# With DataDog support
pip install "grass-metrics[datadog]"

# With OpenTelemetry support
pip install "grass-metrics[otel]"

# Both backends
pip install "grass-metrics[all]"
```

## Quick Start

```python
from grass_metrics import create_metrics_manager

metrics = create_metrics_manager()

metrics.increment("wl.my_service.items.processed")
metrics.gauge("wl.my_service.queue.size", 42)
metrics.histogram("wl.my_service.latency_seconds", 1.23)

# With extra tags
metrics.increment("wl.my_service.uploads.success", extra_tags=["provider:s3"])

# Shutdown (flushes OTel buffers)
metrics.shutdown()
```

## Backend Selection

The factory picks the backend in this priority order:

| Priority | Source | Example |
|----------|--------|---------|
| 1 | `backend` argument to `create_metrics_manager()` | `create_metrics_manager("otel")` |
| 2 | `METRICS_BACKEND` env var | `METRICS_BACKEND=dual` |
| 3 | Local Redis key `metrics_backend` | Set by preloader from cluster config |
| 4 | Default | `"datadog"` |

Supported values: `datadog`, `otel`, `dual`

- **datadog** -- Sends metrics via DogStatsD UDP to the DataDog Agent
- **otel** -- Sends metrics via OTLP (gRPC or HTTP) to an OpenTelemetry Collector / OpenObserve
- **dual** -- Sends to both simultaneously (DataDog primary, OTel secondary). Useful during migration.

## Environment Variables

### Common

| Variable | Description | Default |
|----------|-------------|---------|
| `METRICS_BACKEND` | Backend selection | `datadog` |
| `ENVIRONMENT` | Environment name for tags | `unknown` |
| `CLUSTER_NAME` or `CLUSTERNAME` | Cluster name for tags | `unknown` |
| `HOSTNAME` | Host name for tags | `unknown` |
| `SERVICE_NAME` | Service name for tags | - |
| `POD_NAME` | Pod name for tags (optional) | - |
| `LOCAL_REDIS_DB_URI` | Redis URI for dynamic backend override | - |

### DataDog

| Variable | Description | Default |
|----------|-------------|---------|
| `DD_AGENT_HOST` | DataDog agent host | `172.17.0.1` |
| `DD_DOGSTATSD_PORT` | DogStatsD port | `8125` |

### OpenTelemetry / OpenObserve

| Variable | Description | Default |
|----------|-------------|---------|
| `OTEL_EXPORTER_ENDPOINT` | OTLP endpoint | auto (see below) |
| `OTEL_EXPORTER_AUTH_TOKEN` | Auth header value | - |
| `NODE_IP` | K8s node IP for local agent | - |

OTel endpoint resolution: explicit > `OTEL_EXPORTER_ENDPOINT` > `http://{NODE_IP}:4317` > gateway fallback.

## Kubernetes Pod Spec

```yaml
env:
  - name: METRICS_BACKEND
    valueFrom:
      configMapKeyRef:
        name: scraper-config
        key: metrics_backend
  # DataDog
  - name: DD_AGENT_HOST
    valueFrom:
      fieldRef:
        fieldPath: status.hostIP
  # OTel
  - name: NODE_IP
    valueFrom:
      fieldRef:
        fieldPath: status.hostIP
```

## Docker Compose

```yaml
environment:
  METRICS_BACKEND: "otel"
  DD_AGENT_HOST: "datadog"          # service name
  OTEL_EXPORTER_ENDPOINT: "http://otel-collector:4317"
```

## Domain-Specific Metrics (Consumer Pattern)

For projects with many metrics, wrap `MetricsManager` in a thin domain class:

```python
from grass_metrics import create_metrics_manager

class YtdMetrics:
    DOWNLOADS_SUCCESS = "wl.ytd.downloads.healthy_count"
    DOWNLOADS_FAILED  = "wl.ytd.downloads.failed_count"
    UPLOAD_DURATION   = "wl.ytd.upload.duration_seconds"

    def __init__(self):
        self._m = create_metrics_manager()

    def increment_downloaded_videos_count(self):
        self._m.increment(self.DOWNLOADS_SUCCESS)

    def increment_failed_downloads_count(self):
        self._m.increment(self.DOWNLOADS_FAILED)

    def send_upload_duration(self, seconds: float):
        self._m.histogram(self.UPLOAD_DURATION, seconds)

    def shutdown(self):
        self._m.shutdown()
```

## Direct Publisher Access

For advanced use cases, publishers can be used directly:

```python
from grass_metrics import DatadogPublisher, OtelPublisher

dd = DatadogPublisher(statsd_host="172.17.0.1", statsd_port=8125)
dd.increment_count("my.counter", 1, ["env:prod"])

otel = OtelPublisher(service_name="my-svc", use_grpc=True)
otel.increment_count("my.counter", 1, ["env:prod"])
otel.shutdown()
```

## Architecture

```
┌──────────────────────────────────────────────────┐
│                Consumer Project                   │
│  (e.g. YtdMetrics with domain-specific methods)  │
└──────────────────────┬───────────────────────────┘
                       │
         create_metrics_manager(backend=...)
                       │
                ┌──────▼──────┐
                │   Factory    │  resolves backend from
                │  factory.py  │  env / Redis / default
                └──────┬──────┘
                       │
          ┌────────────┼────────────┐
          │            │            │
    ┌─────▼─────┐ ┌───▼────┐ ┌────▼─────┐
    │ MetricsMgr│ │MetrMgr │ │ DualMgr  │
    │ + DD Pub  │ │+ OTel  │ │ DD + OTel│
    └───────────┘ └────────┘ └──────────┘
```
