Back to Blog

Observability: OpenTelemetry Unified Telemetry Framework

coomia-dip builds a unified observability framework on OpenTelemetry, covering the three pillars: Traces (distributed tracing), Metrics (monitoring), and Logs (structured logging). Through gRPC interceptors for automatic trace context injection, custom metric collectors for business metrics, and structured logs correlated with Trace IDs for end-to-end observability. The backend integrates Jaeger (traces), Prometheus + Grafana (metrics), and Loki (logs). This article covers telemetry architecture, auto-instrumentation, custom metrics, log correlation, and alerting.

CoomiaPublished on October 4, 20254 min read
Share this articleTwitter / X

Series: S6 Platform Engineering · Article 20 | Level: Advanced | Reading Time: 18 min

Observability: OpenTelemetry Unified Telemetry Framework

#TL;DR

coomia-dip builds a unified observability framework on OpenTelemetry, covering the three pillars: Traces (distributed tracing), Metrics (monitoring), and Logs (structured logging). Through gRPC interceptors for automatic trace context injection, custom metric collectors for business metrics, and structured logs correlated with Trace IDs for end-to-end observability. The backend integrates Jaeger (traces), Prometheus + Grafana (metrics), and Loki (logs). This article covers telemetry architecture, auto-instrumentation, custom metrics, log correlation, and alerting.

#1. Three Pillars of Observability

#1.1 Unified Telemetry Model

Code
┌────────────────────────────────────────────────┐
│            OpenTelemetry SDK                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────────┐ │
│  │  Traces   │  │ Metrics  │  │    Logs      │ │
│  └─────┬────┘  └─────┬────┘  └──────┬───────┘ │
│  ┌─────▼─────────────▼──────────────▼───────┐  │
│  │         OTLP Exporter                    │  │
│  └─────────────────┬────────────────────────┘  │
└────────────────────┼───────────────────────────┘
        ┌────────────┼────────────┐
   ┌────▼───┐  ┌────▼────┐  ┌───▼────┐
   │ Jaeger │  │Prometheus│  │  Loki  │
   └────┬───┘  └────┬────┘  └───┬────┘
   ┌────▼────────────▼───────────▼───┐
   │        Grafana Dashboard         │
   └─────────────────────────────────┘

#2. Distributed Tracing

#2.1 gRPC Auto-Tracing

Python
class OTelGrpcInterceptor(grpc.aio.UnaryUnaryClientInterceptor):
    """OpenTelemetry gRPC tracing interceptor"""

    async def intercept_unary_unary(self, continuation, client_call_details, request):
        method = client_call_details.method
        service, method_name = self._parse_method(method)

        with self._tracer.start_as_current_span(
            f"grpc.{service}/{method_name}",
            kind=trace.SpanKind.CLIENT,
            attributes={
                "rpc.system": "grpc",
                "rpc.service": service,
                "rpc.method": method_name,
                "onto.object_type": self._extract_object_type(request),
            },
        ) as span:
            metadata = list(client_call_details.metadata or [])
            inject(metadata, setter=GrpcMetadataSetter())
            new_details = client_call_details._replace(metadata=metadata)

            try:
                response = await continuation(new_details, request)
                span.set_status(StatusCode.OK)
                return response
            except grpc.aio.AioRpcError as e:
                span.set_status(StatusCode.ERROR, str(e))
                raise

#2.2 Cross-Layer Tracing

Code
Client SDK → API Gateway → Ontology Service → Data Service → Iceberg
    │            │               │                │            │
    ├── span ────┤               │                │            │
    │            ├── span ───────┤                │            │
    │            │               ├── span ────────┤            │
    │            │               │                ├── span ────┤
    └────────────┴───────────────┴────────────────┴────────────┘
                        TraceID: abc-123

#3. Metrics Monitoring

#3.1 Platform Metrics

Python
class OntoPlatformMetrics:
    def __init__(self):
        self._meter = metrics.get_meter("coomia-dip")

        self.request_counter = self._meter.create_counter(
            "onto.requests.total", description="Total requests", unit="1",
        )
        self.request_duration = self._meter.create_histogram(
            "onto.request.duration", description="Request duration", unit="ms",
        )
        self.object_operations = self._meter.create_counter(
            "onto.objects.operations.total", description="Object operations",
        )
        self.auth_evaluations = self._meter.create_counter(
            "onto.auth.evaluations.total", description="Auth evaluations",
        )
        self.masking_operations = self._meter.create_counter(
            "onto.masking.operations.total", description="Masking operations",
        )

    def record_request(self, method, object_type, status, duration_ms):
        attrs = {"method": method, "object_type": object_type, "status": status}
        self.request_counter.add(1, attrs)
        self.request_duration.record(duration_ms, attrs)

#4. Structured Logging

#4.1 Log-Trace Correlation

Python
class OTelLogHandler(logging.Handler):
    """Auto-inject trace context into logs"""

    def emit(self, record: logging.LogRecord) -> None:
        span = trace.get_current_span()
        if span.is_recording():
            ctx = span.get_span_context()
            record.trace_id = format(ctx.trace_id, "032x")
            record.span_id = format(ctx.span_id, "016x")
        else:
            record.trace_id = "0" * 32
            record.span_id = "0" * 16

        log_entry = {
            "timestamp": record.created,
            "level": record.levelname,
            "message": record.getMessage(),
            "trace_id": record.trace_id,
            "span_id": record.span_id,
            "service": "coomia-dip",
            "attributes": getattr(record, "attributes", {}),
        }
        self._export(log_entry)

#5. Alerting

YAML
groups:
  - name: coomia-dip-alerts
    rules:
      - alert: HighRequestLatency
        expr: histogram_quantile(0.99, onto_request_duration_bucket) > 1000
        for: 5m
        labels: { severity: warning }

      - alert: HighErrorRate
        expr: rate(onto_requests_total{status="error"}[5m]) / rate(onto_requests_total[5m]) > 0.05
        for: 2m
        labels: { severity: critical }

      - alert: HighAuthDenialRate
        expr: rate(onto_auth_evaluations_total{result="denied"}[5m]) > 100
        for: 5m
        labels: { severity: warning }

      - alert: ServiceDown
        expr: up{job=~"onto-.*"} == 0
        for: 1m
        labels: { severity: critical }

#6. Testing

Python
class TestObservability:
    def test_trace_propagation(self):
        with tracer.start_as_current_span("test-root"):
            response = await client.objects.get("Employee", "emp-001")
            spans = exporter.get_finished_spans()
            trace_ids = set(s.context.trace_id for s in spans)
            assert len(trace_ids) == 1

    def test_metrics_recorded(self):
        await client.objects.get("Employee", "emp-001")
        metric_data = reader.get_metrics_data()
        request_metric = find_metric(metric_data, "onto.requests.total")
        assert request_metric.data_points[0].value >= 1

    def test_log_trace_correlation(self):
        with tracer.start_as_current_span("test") as span:
            trace_id = format(span.get_span_context().trace_id, "032x")
            logger.info("test message")
            log_entry = log_exporter.get_last_entry()
            assert log_entry["trace_id"] == trace_id

#7. Production Best Practices

#7.1 Sampling Strategy

ScenarioSample RateRationale
Normal requests1%Reduce storage overhead
Error requests100%Always capture
Slow requests (>1s)100%Always capture
High-classification access100%Compliance requirement

#7.2 Data Retention

Telemetry TypeRetentionStorage
Traces7 daysJaeger + Elasticsearch
Metrics90 daysPrometheus TSDB
Logs30 daysLoki + Object Storage

#8. Summary

The coomia-dip OpenTelemetry observability framework achieves unified collection and correlated analysis across all three telemetry pillars. Key highlights:

  1. Unified framework: OpenTelemetry SDK unifies Traces, Metrics, Logs
  2. Auto-tracing: gRPC interceptors automatically inject trace context
  3. Business metrics: Custom Ontology operation metrics
  4. Log correlation: Structured logs auto-correlated with Trace IDs
  5. Smart alerting: SLA-based multi-tier alert rules

The next article will explore the coomia-dip dashboard with 17 widget types.