Back to Code & Architecture

Document not found, overwrite this content with #not-found slot in <ContentDoc>.

Production Observability with Signoz

This covers how I set up Signoz as the primary observability platform for production workloads, complemented by CloudWatch for AWS-native metrics. On the side, I explore the LGTM stack (Loki, Grafana, Tempo, Mimir) for advanced patterns.

Why Signoz?

  • All-in-one: Traces, metrics, and logs in a single platform
  • OpenTelemetry native: No proprietary agents or lock-in
  • Cost-effective: Self-hosted, no per-GB ingestion fees
  • ClickHouse backend: Blazing fast queries on large datasets

Architecture

OpenTelemetry Collector Configuration

The collector acts as the central pipeline — receive from apps, process, and export to Signoz:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 5s
    send_batch_size: 1000

  memory_limiter:
    check_interval: 1s
    limit_mib: 512
    spike_limit_mib: 128

  resource:
    attributes:
      - key: environment
        value: production
        action: upsert
      - key: service.namespace
        value: indomarco
        action: upsert

  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors-policy
        type: status_code
        status_code:
          status_codes:
            - ERROR
      - name: slow-traces
        type: latency
        latency:
          threshold_ms: 1000
      - name: probabilistic
        type: probabilistic
        probabilistic:
          sampling_percentage: 10

exporters:
  otlp/signoz:
    endpoint: signoz-otel-collector:4317
    tls:
      insecure: true

  awscloudwatch:
    region: ap-southeast-1
    namespace: CustomMetrics
    metric_declarations:
      - dimensions: [[service.name]]
        metric_name_selectors:
          - "http.server.duration"
          - "http.server.request.size"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch, resource, tail_sampling]
      exporters: [otlp/signoz]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch, resource]
      exporters: [otlp/signoz, awscloudwatch]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch, resource]
      exporters: [otlp/signoz]

Application Instrumentation (Node.js Example)

const { NodeSDK } = require('@opentelemetry/sdk-node')
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc')
const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-grpc')
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics')

const sdk = new NodeSDK({
  serviceName: process.env.SERVICE_NAME,
  traceExporter: new OTLPTraceExporter({
    url: process.env.OTEL_COLLECTOR_URL || 'grpc://otel-collector:4317',
  }),
  metricReader: new PeriodicExportingMetricReader({
    exporter: new OTLPMetricExporter({
      url: process.env.OTEL_COLLECTOR_URL || 'grpc://otel-collector:4317',
    }),
    exportIntervalMillis: 30000,
  }),
  instrumentations: [
    getNodeAutoInstrumentations({
      '@opentelemetry/instrumentation-http': { enabled: true },
      '@opentelemetry/instrumentation-express': { enabled: true },
      '@opentelemetry/instrumentation-mysql2': { enabled: true },
      '@opentelemetry/instrumentation-redis': { enabled: true },
    }),
  ],
})

sdk.start()

Alerting Rules

groups:
  - name: service-health
    rules:
      - alert: HighErrorRate
        expr: |
          sum(rate(signoz_calls_total{status_code="STATUS_CODE_ERROR"}[5m])) 
          / sum(rate(signoz_calls_total[5m])) > 0.05
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "High error rate detected (> 5%)"

      - alert: HighLatency
        expr: |
          histogram_quantile(0.95, sum(rate(signoz_latency_bucket[5m])) by (le, service_name)) > 2000
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "P95 latency above 2 seconds"

      - alert: ServiceDown
        expr: |
          up{job=~".*"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Service instance is down"

LGTM Stack (Hobby / Exploration)

For personal projects and exploratory work, I run the Grafana LGTM stack:

ComponentPurpose
LokiLog aggregation (like CloudWatch Logs but self-hosted)
GrafanaVisualization and dashboards
TempoDistributed tracing
MimirLong-term metrics storage

This gives me hands-on experience with the full Grafana ecosystem, which I can leverage when teams prefer Grafana-based tooling.

Results

  • 70% faster incident resolution with correlated traces, metrics, and logs
  • Tail sampling reduces storage costs by 90% while keeping all error traces
  • < 5 minute detection for anomalies via alerting rules
  • Full request lifecycle visibility across 100+ microservices