Instrumenting edge applications shouldn't require complex or heavy local setups. Deploying a standard OpenTelemetry (OTel) agent with Grafana Alloy and running a local LGTM (Loki, Grafana, Tempo, Mimir/Prometheus) stack in Docker Compose gives you instant, enterprise-grade diagnostics.

1. The LGTM Local Stack

To visualize your traces, logs, and metrics, you need a local aggregation layer. The Docker Compose setup below spins up the core components of the Grafana observability stack:

  • Grafana: The visualization dashboard.
  • Prometheus: Stores metrics exported by the clients.
  • Loki: Aggregates application and transport logs.
  • Tempo: Traces application logic flow across network boundaries.
# docker-compose.lgtm.yml
version: "3.8"

services:
  # 1. Grafana for visualization
  grafana:
    image: grafana/grafana:10.4.1
    ports:
      - "3000:3000"
    environment:
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
    volumes:
      - grafana-storage:/var/lib/grafana

  # 2. Prometheus for metrics storage
  prometheus:
    image: prom/prometheus:v2.51.0
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--enable-feature=otlp-write-receiver"
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

  # 3. Loki for log aggregation
  loki:
    image: grafana/loki:2.9.8
    ports:
      - "3100:3100"
    command: -config.file=/etc/loki/local-config.yaml

  # 4. Tempo for trace storage
  tempo:
    image: grafana/tempo:2.4.1
    command: [ "-config.file=/etc/tempo.yaml" ]
    ports:
      - "3200:3200" # tempo HTTP
      - "4317:4317" # otlp grpc
      - "4318:4318" # otlp http
    volumes:
      - ./tempo.yaml:/etc/tempo.yaml

volumes:
  grafana-storage:
      

2. Configuring the Alloy Collector

Grafana Alloy acts as a lightweight telemetry pipeline receiver. It accepts standard OTLP (OpenTelemetry Protocol) payloads on your local network, filters them, batches them, and routes them to Loki, Tempo, and Prometheus:

# config.alloy
// 1. Listen for OTLP gRPC and HTTP payloads
otelcol.receiver.otlp "default" {
  grpc {
    endpoint = "0.0.0.0:4317"
  }
  http {
    endpoint = "0.0.0.0:4318"
  }

  output {
    metrics = [otelcol.processor.batch.default.input]
    traces  = [otelcol.processor.batch.default.input]
    logs    = [otelcol.processor.batch.default.input]
  }
}

// 2. Batch telemetry events to optimize flow
otelcol.processor.batch "default" {
  output {
    metrics = [otelcol.exporter.prometheus.local.input]
    traces  = [otelcol.exporter.otlp.tempo.input]
    logs    = [otelcol.exporter.loki.local.input]
  }
}

// 3. Export Metrics to Prometheus
otelcol.exporter.prometheus "local" {
  forward_to = [prometheus.remote_write.local.receiver]
}

prometheus.remote_write "local" {
  endpoint {
    url = "http://prometheus:9090/api/v1/write"
  }
}

// 4. Export Traces to Tempo
otelcol.exporter.otlp "tempo" {
  client {
    endpoint = "tempo:4317"
    tls {
      insecure = true
    }
  }
}

// 5. Export Logs to Loki
otelcol.exporter.loki "local" {
  forward_to = [loki.write.local.receiver]
}

loki.write "local" {
  endpoint {
    url = "http://loki:3100/loki/api/v1/push"
  }
}
      

3. Instrumenting Edge Clients

Once the LGTM collector and Alloy pipeline are active, instrumenting your application involves pointing the standard OpenTelemetry SDK exporters to the Alloy endpoint:

  • Traces Endpoint: http://localhost:4318/v1/traces
  • Logs Endpoint: http://localhost:4318/v1/logs

In modern mobile client SDKs, these are typically initialized during launch by specifying the resource details and attaching the batch span processors. You can now use the Grafana explorer to trace client-side interactions down to the millisecond, isolating network bottlenecks instantly.