Skip to main content
Technology & EngineeringObservability Services255 lines

Elastic Apm

Instrument applications with Elastic APM and the ELK Stack for traces, logs, and metrics.

Quick Summary29 lines
You are an expert in Elastic APM and the ELK Stack (Elasticsearch, Logstash, Kibana). You help developers instrument applications with Elastic APM agents, correlate traces with logs, build Kibana visualizations, and write Elasticsearch queries for deep observability.

## Key Points

- **Capturing request bodies in production** - Setting `captureBody: 'all'` leaks sensitive data; use `'errors'` or `'off'` in production.
- **Missing log correlation** - Without injecting trace IDs into logs, you cannot jump from a trace to its log lines in Kibana.
- **Single-node Elasticsearch in production** - Always run a cluster with replicas; a single node loses data on failure.
- **Not filtering health checks** - Synthetic health checks pollute APM transaction data and skew latency metrics.
- You already use Elasticsearch and Kibana for logging and want APM in the same stack.
- You need service maps and dependency visualization for microservice architectures.
- You want trace-log correlation without maintaining separate systems.
- You need self-hosted observability with full control over data retention.
- You are running the Elastic Stack on Kubernetes with ECK (Elastic Cloud on Kubernetes).

## Quick Example

```bash
npm install elastic-apm-node
```

```typescript
// WRONG - you lose all trace data
apm.start({
  transactionSampleRate: 0, // No traces collected at all
});
// Use a reasonable rate like 0.1 (10%) for high-traffic services
```
skilldb get observability-services-skills/Elastic ApmFull skill: 255 lines
Paste into your CLAUDE.md or agent config

Elastic APM Integration

You are an expert in Elastic APM and the ELK Stack (Elasticsearch, Logstash, Kibana). You help developers instrument applications with Elastic APM agents, correlate traces with logs, build Kibana visualizations, and write Elasticsearch queries for deep observability.

Core Philosophy

Unified Observability in Elastic

Elastic APM stores traces, metrics, and logs in Elasticsearch. A single data store means you can correlate a slow transaction trace with the exact log lines and host metrics without switching tools.

Agent Auto-Instrumentation

Elastic APM agents automatically instrument popular frameworks, databases, and HTTP clients. Add custom spans only for business-specific operations.

Kibana as the Analysis Layer

Kibana provides APM-specific views (service maps, trace waterfall, error grouping) plus full Elasticsearch query power through Discover and Lens.

Setup

Install the Node.js APM agent:

npm install elastic-apm-node

Initialize before any other require:

// apm.ts - import FIRST
import apm from 'elastic-apm-node';

apm.start({
  serviceName: process.env.ELASTIC_APM_SERVICE_NAME || 'my-service',
  serverUrl: process.env.ELASTIC_APM_SERVER_URL || 'http://localhost:8200',
  secretToken: process.env.ELASTIC_APM_SECRET_TOKEN,
  environment: process.env.NODE_ENV || 'development',
  captureBody: 'errors',
  transactionSampleRate: 1.0,
  logLevel: 'info',
});

export default apm;

Docker Compose for the Elastic Stack:

version: '3.8'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms1g -Xmx1g
    ports:
      - "9200:9200"

  kibana:
    image: docker.elastic.co/kibana/kibana:8.13.0
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    ports:
      - "5601:5601"

  apm-server:
    image: docker.elastic.co/apm/apm-server:8.13.0
    environment:
      - output.elasticsearch.hosts=["http://elasticsearch:9200"]
      - apm-server.host=0.0.0.0:8200
    ports:
      - "8200:8200"

Key Patterns

Do: Add custom spans for business operations

import apm from './apm';

async function processPayment(orderId: string, amount: number) {
  const span = apm.startSpan('payment.process', 'external', 'stripe');
  if (span) {
    span.addLabels({ 'order.id': orderId, 'payment.amount': amount });
  }
  try {
    const result = await stripe.charges.create({ amount, currency: 'usd' });
    return result;
  } catch (error) {
    apm.captureError(error as Error);
    throw error;
  } finally {
    span?.end();
  }
}

Not: Setting sample rate to 0 in production

// WRONG - you lose all trace data
apm.start({
  transactionSampleRate: 0, // No traces collected at all
});
// Use a reasonable rate like 0.1 (10%) for high-traffic services

Do: Correlate logs with trace IDs

import apm from './apm';
import pino from 'pino';

const logger = pino({
  mixin() {
    const traceIds = apm.currentTraceIds;
    return {
      'trace.id': traceIds['trace.id'],
      'transaction.id': traceIds['transaction.id'],
      'span.id': traceIds['span.id'],
    };
  },
});

// Logs automatically include trace context for correlation in Kibana
logger.info('Payment processed successfully');

Common Patterns

Elasticsearch Query for Error Analysis

POST /apm-*-error-*/_search
{
  "size": 0,
  "query": {
    "bool": {
      "filter": [
        { "term": { "service.name": "checkout-api" } },
        { "range": { "@timestamp": { "gte": "now-1h" } } }
      ]
    }
  },
  "aggs": {
    "error_groups": {
      "terms": {
        "field": "error.grouping_key",
        "size": 10,
        "order": { "_count": "desc" }
      },
      "aggs": {
        "sample": {
          "top_hits": {
            "size": 1,
            "_source": ["error.exception.message", "error.exception.type"]
          }
        }
      }
    }
  }
}

Custom Transaction Filtering

// Filter health checks from APM data
apm.addFilter((payload) => {
  if (payload.context?.request?.url?.pathname === '/health') {
    return false; // Drop this transaction
  }
  return payload;
});

Kibana Saved Search via API

curl -X POST "http://localhost:5601/api/saved_objects/search" \
  -H "kbn-xsrf: true" \
  -H "Content-Type: application/json" \
  -d '{
    "attributes": {
      "title": "Slow Transactions",
      "kibanaSavedObjectMeta": {
        "searchSourceJSON": "{\"query\":{\"bool\":{\"filter\":[{\"range\":{\"transaction.duration.us\":{\"gte\":1000000}}}]}},\"index\":\"apm-*\"}"
      }
    }
  }'

Alerting with Elasticsearch Watcher

{
  "trigger": {
    "schedule": { "interval": "1m" }
  },
  "input": {
    "search": {
      "request": {
        "indices": ["apm-*-error-*"],
        "body": {
          "query": {
            "bool": {
              "filter": [
                { "term": { "service.name": "api-gateway" } },
                { "range": { "@timestamp": { "gte": "now-5m" } } }
              ]
            }
          }
        }
      }
    }
  },
  "condition": {
    "compare": { "ctx.payload.hits.total.value": { "gt": 50 } }
  },
  "actions": {
    "notify_slack": {
      "webhook": {
        "scheme": "https",
        "host": "hooks.slack.com",
        "port": 443,
        "method": "POST",
        "path": "/services/xxx",
        "body": "{\"text\": \"High error count: {{ctx.payload.hits.total.value}} errors in 5m\"}"
      }
    }
  }
}

Anti-Patterns

  • Capturing request bodies in production - Setting captureBody: 'all' leaks sensitive data; use 'errors' or 'off' in production.
  • Missing log correlation - Without injecting trace IDs into logs, you cannot jump from a trace to its log lines in Kibana.
  • Single-node Elasticsearch in production - Always run a cluster with replicas; a single node loses data on failure.
  • Not filtering health checks - Synthetic health checks pollute APM transaction data and skew latency metrics.

When to Use

  • You already use Elasticsearch and Kibana for logging and want APM in the same stack.
  • You need service maps and dependency visualization for microservice architectures.
  • You want trace-log correlation without maintaining separate systems.
  • You need self-hosted observability with full control over data retention.
  • You are running the Elastic Stack on Kubernetes with ECK (Elastic Cloud on Kubernetes).

Install this skill directly: skilldb add observability-services-skills

Get CLI access →