# API Keys
Source: https://docs.logclaw.ai/api-keys
Create and manage API keys for authenticating log ingestion on LogClaw Cloud
# API Keys
API keys authenticate your application's log ingestion requests to LogClaw Cloud. Each key is scoped to a project and automatically associates incoming logs with the correct tenant.
***
## Getting Your API Key
Go to [console.logclaw.ai](https://console.logclaw.ai) and sign in with your account. If you don't have an account yet, click **Sign Up** to create one.
From the dashboard, create a new project or select an existing one. Each project has its own isolated log pipeline and API keys.
In your project, go to **Settings → API Keys**.
Click **"Create API Key"**, give it a descriptive name (e.g. `production-backend`), and copy the key immediately. Keys start with `lc_proj_` and are only shown once.
```
lc_proj_5a33915526cd1af8238c6e690eeff9cec2611750d9914d2dd1cc7a87cf08c717
```
API keys are shown only once at creation time. Store your key securely before closing the dialog.
***
## Using Your API Key
Add the `x-logclaw-api-key` header to all ingestion requests:
```bash theme={null}
curl -X POST https://otel.logclaw.ai/v1/logs \
-H "Content-Type: application/json" \
-H "x-logclaw-api-key: $LOGCLAW_API_KEY" \
-d '{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "my-api"}}
]
},
"scopeLogs": [{
"logRecords": [{
"timeUnixNano": "'$(date +%s)000000000'",
"severityText": "INFO",
"body": {"stringValue": "Hello from LogClaw!"}
}]
}]
}]
}'
```
### Environment Variables
We recommend storing your API key in an environment variable:
```bash theme={null}
export LOGCLAW_API_KEY=lc_proj_your_key_here
```
For OpenTelemetry SDKs, use the standard OTLP environment variables:
```bash theme={null}
export OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.logclaw.ai
export OTEL_EXPORTER_OTLP_HEADERS=x-logclaw-api-key=lc_proj_your_key_here
export OTEL_EXPORTER_OTLP_PROTOCOL=http/json
```
These environment variables work with any OpenTelemetry SDK — Python, Node.js, Go, Java, .NET, Ruby, and Rust — with zero code changes.
***
## Tenant ID Auto-Injection
You do **not** need to add a `tenant_id` to your logs. LogClaw automatically injects the correct tenant identifier based on your API key's project. Any `tenant_id` already present in the payload is stripped and replaced to prevent spoofing.
This means your application code stays clean — just send standard OTLP logs and LogClaw handles multi-tenant isolation for you.
***
## Security Best Practices
Never hardcode API keys in source code. Use environment variables or a secret manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault).
Create new keys and revoke old ones periodically. You can have multiple active keys per project to enable zero-downtime rotation.
Create separate API keys for each environment (staging, production) and each service to limit blast radius.
If a key is exposed, revoke it immediately from Settings → API Keys. A new key can be created in seconds.
***
## Revoking API Keys
1. Go to your project in [console.logclaw.ai](https://console.logclaw.ai)
2. Navigate to **Settings → API Keys**
3. Find the key you want to revoke and click **Revoke**
4. The key stops working immediately — any requests using it will receive a `401 Unauthorized` response
Revoking a key is irreversible. Create a new key before revoking the old one if you need uninterrupted log ingestion.
***
## Self-Hosted Deployments
If you're running LogClaw self-hosted, API keys are not required. Logs are sent directly to the OTel Collector endpoint and access is controlled via Kubernetes NetworkPolicy. See the [Quick Start](/quickstart-send-logs) for self-hosted endpoint details.
# Infrastructure Agent API
Source: https://docs.logclaw.ai/api-reference/agent
Cluster health monitoring API from the Go-based infrastructure agent.
# Infrastructure Agent API
The Infrastructure Agent is a Go-based service that collects health metrics from Kubernetes infrastructure components and exposes them via HTTP endpoints.
**Base URL:** `http://logclaw-agent:8080`
**Dashboard proxy:** `/api/agent/*`
***
## Health Check
```
GET /health
```
Liveness probe endpoint.
### Response
```json theme={null}
{
"status": "ok"
}
```
***
## Readiness Check
```
GET /ready
```
Readiness probe endpoint. Returns `200` only when all collectors are initialized.
### Response (Ready)
```json theme={null}
{
"status": "ready",
"collectors": {
"kafka": "initialized",
"flink": "initialized",
"opensearch": "initialized",
"eso": "initialized"
}
}
```
### Response (Not Ready)
```json theme={null}
{
"status": "not_ready",
"collectors": {
"kafka": "initialized",
"flink": "initializing",
"opensearch": "initialized",
"eso": "error"
}
}
```
***
## Infrastructure Metrics
```
GET /metrics
```
Returns aggregated infrastructure metrics from all collectors. Used by the Dashboard's pipeline monitoring view.
### Response
```json theme={null}
{
"kafka": {
"status": "healthy",
"brokers": 3,
"topics": {
"raw-logs": {
"partitions": 6,
"consumerLag": 150
},
"enriched-logs": {
"partitions": 6,
"consumerLag": 0
}
}
},
"flink": {
"status": "healthy",
"jobs": [
{
"name": "logclaw-anomaly-detection",
"state": "RUNNING",
"taskManagers": 2,
"uptime": "48h30m"
}
]
},
"opensearch": {
"status": "green",
"nodes": 6,
"indices": 15,
"totalDocs": 1542000,
"totalSizeBytes": 2147483648,
"activePrimaryShards": 15,
"activeShards": 30
},
"eso": {
"status": "healthy",
"externalSecrets": [
{
"name": "logclaw-ticketing-credentials",
"status": "SecretSynced",
"lastSyncTime": "2024-03-01T15:00:00Z"
}
]
}
}
```
### Metric Fields
#### Kafka
| Field | Type | Description |
| ---------------------- | ------- | --------------------------------------------------- |
| `status` | string | Overall Kafka health: `healthy`, `degraded`, `down` |
| `brokers` | integer | Number of active Kafka brokers |
| `topics[].partitions` | integer | Number of partitions per topic |
| `topics[].consumerLag` | integer | Consumer lag (unconsumed messages) |
#### Flink
| Field | Type | Description |
| --------------------- | ------- | ------------------------------------------- |
| `status` | string | Flink health: `healthy`, `degraded`, `down` |
| `jobs[].name` | string | Job deployment name |
| `jobs[].state` | string | Job state: `RUNNING`, `FAILED`, `SUSPENDED` |
| `jobs[].taskManagers` | integer | Number of active task managers |
#### OpenSearch
| Field | Type | Description |
| ---------------- | ------- | ---------------------------------------- |
| `status` | string | Cluster health: `green`, `yellow`, `red` |
| `nodes` | integer | Total cluster nodes |
| `totalDocs` | integer | Total indexed documents |
| `totalSizeBytes` | integer | Total index storage in bytes |
#### External Secrets Operator
| Field | Type | Description |
| -------------------------------- | ------ | ---------------------------------------------- |
| `status` | string | ESO health: `healthy`, `degraded`, `down` |
| `externalSecrets[].status` | string | Sync status: `SecretSynced`, `SecretSyncError` |
| `externalSecrets[].lastSyncTime` | string | ISO-8601 timestamp of last sync |
***
## Configuration
### Environment Variables
| Variable | Required | Default | Description |
| ------------------- | -------- | ----------------- | ------------------------------------ |
| `LOGCLAW_TENANT_ID` | Yes | — | Tenant identifier for filtering CRDs |
| `LOGCLAW_NAMESPACE` | No | Current namespace | Kubernetes namespace to watch |
| `PORT` | No | `8080` | HTTP server port |
# Bridge API
Source: https://docs.logclaw.ai/api-reference/bridge
Bridge service API for health checks, metrics, and runtime configuration.
# Bridge API
The Bridge exposes HTTP endpoints for health monitoring, Prometheus metrics, and runtime configuration management.
**Base URL:** `http://logclaw-bridge:8080`
**Dashboard proxy:** `/api/bridge/*`
## Health Check
```
GET /health
```
Returns the health status of all Bridge threads and connections.
### Response
```json theme={null}
{
"status": "ok",
"threads": {
"etl": "running",
"anomaly": "running",
"indexer": "running",
"lifecycle": "running"
},
"kafka": "connected",
"opensearch": "connected"
}
```
| Field | Type | Description |
| ------------ | ------ | -------------------------------- |
| `status` | string | Overall status: `ok` or `error` |
| `threads` | object | Status of each processing thread |
| `kafka` | string | Kafka connection status |
| `opensearch` | string | OpenSearch connection status |
***
## Prometheus Metrics
```
GET /metrics
```
Returns Prometheus-format metrics for monitoring and alerting.
### Metrics
| Metric | Type | Labels | Description |
| ------------------------------------------- | ------- | ------ | -------------------------------------------------- |
| `logclaw_bridge_etl_consumed_total` | counter | — | Kafka messages (batches) consumed from `raw-logs` |
| `logclaw_bridge_etl_records_received_total` | counter | — | Individual OTLP log records unpacked and flattened |
| `logclaw_bridge_etl_produced_total` | counter | — | Enriched documents written to `enriched-logs` |
| `logclaw_bridge_anomalies_detected_total` | counter | — | Anomalies detected by the Z-score engine |
| `logclaw_bridge_opensearch_indexed_total` | counter | — | Documents successfully indexed into OpenSearch |
| `logclaw_bridge_opensearch_errors_total` | counter | — | OpenSearch indexing errors |
### Example Response
```
# HELP logclaw_bridge_etl_consumed_total Kafka messages consumed
# TYPE logclaw_bridge_etl_consumed_total counter
logclaw_bridge_etl_consumed_total 47
# HELP logclaw_bridge_etl_records_received_total OTLP log records received
# TYPE logclaw_bridge_etl_records_received_total counter
logclaw_bridge_etl_records_received_total 12450
# HELP logclaw_bridge_etl_produced_total Enriched documents produced
# TYPE logclaw_bridge_etl_produced_total counter
logclaw_bridge_etl_produced_total 12450
# HELP logclaw_bridge_anomalies_detected_total Anomalies detected
# TYPE logclaw_bridge_anomalies_detected_total counter
logclaw_bridge_anomalies_detected_total 3
```
`etl_consumed_total` counts **Kafka messages** (batches), while `etl_records_received_total` counts **individual log records**. A single Kafka message can contain hundreds of OTLP log records. Use `etl_records_received_total` for accurate log volume tracking.
***
## Get Configuration
```
GET /config
```
Returns the current runtime configuration.
### Response
```json theme={null}
{
"anomalyThreshold": 2.5,
"windowSize": 50,
"kafkaBrokers": "logclaw-kafka-kafka-bootstrap:9093",
"rawTopic": "raw-logs",
"enrichedTopic": "enriched-logs",
"opensearchEndpoint": "https://logclaw-opensearch:9200"
}
```
***
## Update Configuration
```
PATCH /config
```
Update runtime configuration dynamically. Changes take effect immediately without restart.
### Request Body
```json theme={null}
{
"anomalyThreshold": 3.0,
"windowSize": 100
}
```
| Field | Type | Description |
| ------------------ | ------- | --------------------------------------------------------- |
| `anomalyThreshold` | number | Z-score threshold for anomaly detection (default: 2.5) |
| `windowSize` | integer | Sliding window size for anomaly calculation (default: 50) |
### Response
```json theme={null}
{
"status": "ok",
"config": {
"anomalyThreshold": 3.0,
"windowSize": 100
}
}
```
Runtime config changes are not persisted across pod restarts. For permanent changes, update the Helm values and redeploy.
# Agent Health
Source: https://docs.logclaw.ai/api-reference/endpoints/agent-health
GET /api/agent/health
Liveness probe for the Infrastructure Agent.
# Infrastructure Metrics
Source: https://docs.logclaw.ai/api-reference/endpoints/agent-metrics
GET /api/agent/metrics
Returns aggregated infrastructure health metrics from all cluster
collectors: Kafka broker and topic status, Flink job states, OpenSearch
cluster health, and External Secrets sync status.
# Bridge Configuration
Source: https://docs.logclaw.ai/api-reference/endpoints/bridge-config
GET /api/bridge/config
Returns the current Bridge runtime configuration including anomaly detection
thresholds, Kafka topics, and OpenSearch endpoint.
# Bridge Health
Source: https://docs.logclaw.ai/api-reference/endpoints/bridge-health
GET /api/bridge/health
Returns the health status of all Bridge processing threads and backend
connections (Kafka, OpenSearch).
# Create API Key
Source: https://docs.logclaw.ai/api-reference/endpoints/create-api-key
POST /api/admin/api-keys
Create a new API key for the project. The full key value is returned **only
once** in the response — store it securely.
Keys start with `lc_proj_` and are 64-character hex strings.
# List API Keys
Source: https://docs.logclaw.ai/api-reference/endpoints/list-api-keys
GET /api/admin/api-keys
Returns all API keys for the authenticated project. Key values are masked —
only the prefix is returned.
# List Incidents
Source: https://docs.logclaw.ai/api-reference/endpoints/list-incidents
GET /api/ticketing/api/incidents
Returns all tracked incidents for your project with their current state,
severity, affected services, and ticketing platform status.
Results are automatically scoped to your project's tenant.
# List Indices
Source: https://docs.logclaw.ai/api-reference/endpoints/list-indices
GET /api/opensearch/_cat/indices
Returns all OpenSearch indices with their health status, document count,
and storage size. LogClaw uses daily indices with the pattern
`logclaw-logs-YYYY.MM.dd`.
# Revoke API Key
Source: https://docs.logclaw.ai/api-reference/endpoints/revoke-api-key
DELETE /api/admin/api-keys
Permanently revoke an API key. The key stops working immediately — any
requests using it will receive `401 Unauthorized`. This action is irreversible.
# Runtime Configuration
Source: https://docs.logclaw.ai/api-reference/endpoints/runtime-config
GET /api/ticketing/api/v1/config
Returns the complete Ticketing Agent runtime configuration including
platform settings, severity routing rules, anomaly thresholds, and LLM config.
Sensitive fields (API keys, tokens) are masked with `****`.
# Send Logs
Source: https://docs.logclaw.ai/api-reference/endpoints/send-logs
POST /v1/logs
Ingest log records in OTLP HTTP/JSON format. This is the primary ingestion
endpoint for LogClaw Cloud. Logs are processed through the pipeline:
OTel Collector → Kafka → Bridge → OpenSearch.
The auth proxy automatically injects your project's `tenant_id` into each
log record based on your API key.
# Ingestion API
Source: https://docs.logclaw.ai/api-reference/ingestion
OTLP log ingestion endpoints via the OpenTelemetry Collector.
# Ingestion API
Logs are ingested into LogClaw exclusively via **OTLP (OpenTelemetry Protocol)**. The OTel Collector accepts both gRPC and HTTP transports.
## Send Logs (HTTP/JSON)
HTTP method
```
POST /v1/logs
```
Send log records in OTLP HTTP/JSON format.
**LogClaw Cloud (managed):** `POST https://otel.logclaw.ai/v1/logs`
**Via Dashboard proxy (self-hosted):** `POST /api/otel/v1/logs`
**Direct (self-hosted):** `POST http://logclaw-otel-collector:4318/v1/logs`
### Authentication
**LogClaw Cloud** requires an API key via the `x-logclaw-api-key` header. Get your key from [console.logclaw.ai](https://console.logclaw.ai) under **Settings → API Keys**. See [API Keys](/api-keys) for details.
**Self-hosted** deployments do not require authentication — access is controlled at the Kubernetes NetworkPolicy level.
### Request Headers
| Header | Value |
| ------------------- | ---------------------------------------------------------------------- |
| `Content-Type` | `application/json` |
| `x-logclaw-api-key` | `lc_proj_...` (required for LogClaw Cloud, not needed for self-hosted) |
### Request Body
```json theme={null}
{
"resourceLogs": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": { "stringValue": "payment-api" }
},
{
"key": "host.name",
"value": { "stringValue": "pod-abc123" }
}
]
},
"scopeLogs": [
{
"logRecords": [
{
"timeUnixNano": "1709312400000000000",
"severityText": "ERROR",
"severityNumber": 17,
"body": {
"stringValue": "Connection refused to database"
},
"traceId": "abcdef1234567890abcdef1234567890",
"spanId": "abcdef1234567890",
"attributes": [
{
"key": "environment",
"value": { "stringValue": "production" }
},
{
"key": "region",
"value": { "stringValue": "us-east-1" }
}
]
}
]
}
]
}
]
}
```
### Response
**200 OK**
```json theme={null}
{
"partialSuccess": {}
}
```
An empty `partialSuccess` object means all records were accepted.
### OTLP Field Reference
| Field | Type | Required | Description |
| --------------------- | ------- | -------- | --------------------------------------------------------- |
| `resourceLogs` | array | Yes | Array of resource log groups |
| `resource.attributes` | array | No | Resource-level attributes (service.name, host.name, etc.) |
| `scopeLogs` | array | Yes | Array of scope log groups |
| `logRecords` | array | Yes | Array of individual log records |
| `timeUnixNano` | string | No | Timestamp in nanoseconds since epoch |
| `severityText` | string | No | Severity level: TRACE, DEBUG, INFO, WARN, ERROR, FATAL |
| `severityNumber` | integer | No | Numeric severity (1-24) |
| `body.stringValue` | string | Yes | The log message |
| `traceId` | string | No | 32-character hex trace ID |
| `spanId` | string | No | 16-character hex span ID |
| `attributes` | array | No | Additional key-value attributes |
### Attribute Value Types
OTLP attributes support multiple value types:
```json theme={null}
{"key": "name", "value": {"stringValue": "hello"}}
{"key": "count", "value": {"intValue": "42"}}
{"key": "ratio", "value": {"doubleValue": 3.14}}
{"key": "enabled", "value": {"boolValue": true}}
```
## Send Logs (gRPC)
**LogClaw Cloud (managed):** `grpc://otel.logclaw.ai:443`
**Direct (self-hosted):** `grpc://logclaw-otel-collector:4317`
Use the OTLP gRPC exporter from any OpenTelemetry SDK. This is the **recommended** transport for production workloads — binary Protobuf is more compact and efficient than JSON.
LogClaw Cloud gRPC uses TLS on port 443. Set your SDK's `insecure` option to `false` (the default). Authentication via `x-logclaw-api-key` header is required — the same as HTTP.
See the [OTLP Integration Guide](/otlp-integration) for SDK examples in Python, Java, Node.js, and Go.
## Health Check
```
GET http://logclaw-otel-collector:13133/
```
**Response:**
```json theme={null}
{
"status": "Server available",
"upSince": "2024-01-15T10:30:00Z",
"uptime": "48h30m"
}
```
## Examples
### curl — Single Log (LogClaw Cloud)
```bash theme={null}
curl -X POST https://otel.logclaw.ai/v1/logs \
-H "Content-Type: application/json" \
-H "x-logclaw-api-key: $LOGCLAW_API_KEY" \
-d '{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "my-app"}}
]
},
"scopeLogs": [{
"logRecords": [{
"timeUnixNano": "'$(date +%s)000000000'",
"severityText": "ERROR",
"body": {"stringValue": "Connection timeout"}
}]
}]
}]
}'
```
### curl — Single Log (Self-Hosted)
```bash theme={null}
curl -X POST http://localhost:4318/v1/logs \
-H "Content-Type: application/json" \
-d '{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "my-app"}}
]
},
"scopeLogs": [{
"logRecords": [{
"timeUnixNano": "'$(date +%s)000000000'",
"severityText": "ERROR",
"body": {"stringValue": "Connection timeout"}
}]
}]
}]
}'
```
### curl — Batch (Multiple Records, LogClaw Cloud)
```bash theme={null}
curl -X POST https://otel.logclaw.ai/v1/logs \
-H "Content-Type: application/json" \
-H "x-logclaw-api-key: $LOGCLAW_API_KEY" \
-d '{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "batch-test"}}
]
},
"scopeLogs": [{
"logRecords": [
{
"timeUnixNano": "'$(date +%s)000000000'",
"severityText": "INFO",
"body": {"stringValue": "Request started"}
},
{
"timeUnixNano": "'$(date +%s)000000000'",
"severityText": "ERROR",
"body": {"stringValue": "Database connection failed"}
},
{
"timeUnixNano": "'$(date +%s)000000000'",
"severityText": "WARN",
"body": {"stringValue": "Retrying in 5 seconds"}
}
]
}]
}]
}'
```
# OpenSearch API
Source: https://docs.logclaw.ai/api-reference/opensearch
OpenSearch query and index management endpoints proxied through the Dashboard.
# OpenSearch API
LogClaw stores processed logs in OpenSearch. The Dashboard proxies OpenSearch API requests with automatic Basic Auth injection.
**Base URL:** `https://logclaw-opensearch:9200`
**Dashboard proxy:** `/api/opensearch/*`
## Index Pattern
LogClaw writes to daily indices: `logclaw-logs-YYYY.MM.dd`
Wildcard for all logs: `logclaw-logs-*`
***
## Cluster Health
```
GET /_cluster/health
```
### Response
```json theme={null}
{
"cluster_name": "logclaw-opensearch",
"status": "green",
"number_of_nodes": 6,
"number_of_data_nodes": 3,
"active_primary_shards": 15,
"active_shards": 30
}
```
| Status | Meaning |
| -------- | -------------------------------------------------------- |
| `green` | All primary and replica shards assigned |
| `yellow` | All primary shards assigned, some replicas unassigned |
| `red` | Some primary shards unassigned — data may be unavailable |
***
## List Indices
```
GET /_cat/indices?format=json
```
Returns all indices with size, document count, and health status.
### Response
```json theme={null}
[
{
"health": "green",
"status": "open",
"index": "logclaw-logs-2024.03.01",
"docs.count": "15420",
"store.size": "12.3mb"
}
]
```
***
## Search Logs
```
POST /logclaw-logs-*/_search
```
Query logs using the OpenSearch Query DSL.
### Request Body
```json theme={null}
{
"size": 50,
"sort": [{ "timestamp": "desc" }],
"query": {
"bool": {
"must": [
{ "match": { "level": "ERROR" } },
{ "match": { "service": "payment-api" } }
],
"filter": [
{
"range": {
"timestamp": {
"gte": "now-1h",
"lte": "now"
}
}
}
]
}
}
}
```
### Response
```json theme={null}
{
"hits": {
"total": { "value": 42, "relation": "eq" },
"hits": [
{
"_index": "logclaw-logs-2024.03.01",
"_id": "abc123",
"_source": {
"timestamp": "2024-03-01T15:30:00Z",
"service": "payment-api",
"level": "ERROR",
"message": "Connection refused to database",
"trace_id": "abcdef1234567890",
"span_id": "1234567890ab",
"host": "pod-xyz",
"tenant_id": "acme-corp",
"anomaly_score": 3.2,
"is_anomaly": true
}
}
]
}
}
```
### Common Query Patterns
**All errors in the last hour:**
```json theme={null}
{
"query": {
"bool": {
"must": [{ "match": { "level": "ERROR" } }],
"filter": [{ "range": { "timestamp": { "gte": "now-1h" } } }]
}
}
}
```
**Logs by trace ID:**
```json theme={null}
{
"query": {
"term": { "trace_id": "abcdef1234567890abcdef1234567890" }
},
"sort": [{ "timestamp": "asc" }]
}
```
**Anomalies above threshold:**
```json theme={null}
{
"query": {
"bool": {
"must": [
{ "term": { "is_anomaly": true } },
{ "range": { "anomaly_score": { "gte": 2.5 } } }
]
}
}
}
```
**Full-text search:**
```json theme={null}
{
"query": {
"match": { "message": "connection timeout database" }
}
}
```
***
## Document Count
```
GET /logclaw-logs-*/_count
```
### Response
```json theme={null}
{
"count": 154200,
"_shards": {
"total": 15,
"successful": 15,
"failed": 0
}
}
```
***
## Document Schema
Each indexed log document has these fields:
| Field | Type | Description |
| --------------- | ------- | ----------------------------------------------------- |
| `timestamp` | date | ISO-8601 timestamp |
| `service` | keyword | Service name (from `service.name` resource attribute) |
| `level` | keyword | Log level: INFO, WARN, ERROR, FATAL |
| `message` | text | Log message body |
| `trace_id` | keyword | Distributed trace ID |
| `span_id` | keyword | Span ID |
| `host` | keyword | Hostname or pod name |
| `tenant_id` | keyword | Tenant identifier |
| `anomaly_score` | float | Z-score from anomaly detection (0 if normal) |
| `is_anomaly` | boolean | Whether the document triggered an anomaly alert |
| `batch_id` | keyword | Upload batch identifier (for file uploads) |
| `environment` | keyword | Environment tag (production, staging, dev) |
| `region` | keyword | Cloud region |
Custom OTLP attributes are flattened as top-level fields. For example, `{"key": "user.id", "value": {"stringValue": "12345"}}` becomes `"user.id": "12345"` in the indexed document.
# API Reference
Source: https://docs.logclaw.ai/api-reference/overview
Complete API documentation for all LogClaw services with interactive playground.
# API Reference
LogClaw exposes APIs through three access patterns:
1. **LogClaw Cloud (otel.logclaw\.ai)** — all endpoints are authenticated via `x-logclaw-api-key` header and routed through the Auth Proxy. Use this for LogClaw Cloud deployments. Every endpoint in this reference includes an interactive playground — paste your API key and send requests directly from the docs.
2. **Dashboard Proxy** — the Next.js Dashboard proxies requests to backend services under `/api//`. Use this for browser-based access in self-hosted deployments.
3. **Direct Service Access** — each backend service exposes its own API on its Kubernetes ClusterIP service. Use this for programmatic access from within the cluster.
## Authentication
**LogClaw Cloud** requires an API key for all API requests. Include the `x-logclaw-api-key` header with every request:
```bash theme={null}
curl -H "x-logclaw-api-key: lc_proj_your_key_here" \
https://otel.logclaw.ai/api/agent/health
```
Generate keys from your project dashboard at [console.logclaw.ai](https://console.logclaw.ai) under **Settings → API Keys**. See [API Keys](/api-keys) for details.
**Self-hosted** deployments do not require API keys — access is controlled at the Kubernetes NetworkPolicy level.
The Auth Proxy automatically injects the correct `tenant_id` into all requests based on your API key's project. You never need to specify a tenant ID manually.
## Try It Live
Every endpoint page includes an interactive **API Playground** on the right side. Enter your `x-logclaw-api-key` and send real requests to `otel.logclaw.ai` directly from the docs.
## Service Endpoints
| Service | Cloud (otel.logclaw\.ai) | Dashboard Proxy | Direct (in-cluster) | Port |
| ------------------------------------------------------- | ------------------------ | --------------------- | -------------------------------- | ------------------------ |
| [Ingestion](/api-reference/endpoints/send-logs) | `POST /v1/logs` | `/api/otel/v1/logs` | `logclaw-otel-collector:4318` | 4317 (gRPC), 4318 (HTTP) |
| [API Keys](/api-reference/endpoints/list-api-keys) | `/api/admin/api-keys` | `/api/admin/api-keys` | Dashboard internal | — |
| [Bridge](/api-reference/endpoints/bridge-health) | `/api/bridge/*` | `/api/bridge/*` | `logclaw-bridge:8080` | 8080 |
| [OpenSearch](/api-reference/endpoints/list-indices) | `/api/opensearch/*` | `/api/opensearch/*` | `logclaw-opensearch:9200` | 9200 |
| [Ticketing](/api-reference/endpoints/list-incidents) | `/api/ticketing/*` | `/api/ticketing/*` | `logclaw-ticketing-agent:18081` | 18081 |
| [Infrastructure](/api-reference/endpoints/agent-health) | `/api/agent/*` | `/api/agent/*` | `logclaw-agent:8080` | 8080 |
| Airflow | `/api/airflow/*` | `/api/airflow/*` | `logclaw-airflow-webserver:8080` | 8080 |
| Feast | `/api/feast/*` | `/api/feast/*` | `logclaw-feast:6567` | 6567 |
## All Endpoints
| Method | Path | Description |
| -------- | ------------------------------------------------------------------------- | ----------------------- |
| `POST` | [`/v1/logs`](/api-reference/endpoints/send-logs) | Ingest logs (OTLP/HTTP) |
| `GET` | [`/api/admin/api-keys`](/api-reference/endpoints/list-api-keys) | List API keys |
| `POST` | [`/api/admin/api-keys`](/api-reference/endpoints/create-api-key) | Create API key |
| `DELETE` | [`/api/admin/api-keys`](/api-reference/endpoints/revoke-api-key) | Revoke API key |
| `GET` | [`/api/bridge/health`](/api-reference/endpoints/bridge-health) | Bridge health |
| `GET` | [`/api/bridge/config`](/api-reference/endpoints/bridge-config) | Bridge configuration |
| `GET` | [`/api/ticketing/api/incidents`](/api-reference/endpoints/list-incidents) | List incidents |
| `GET` | [`/api/ticketing/api/v1/config`](/api-reference/endpoints/runtime-config) | Runtime configuration |
| `GET` | [`/api/agent/health`](/api-reference/endpoints/agent-health) | Agent liveness |
| `GET` | [`/api/agent/metrics`](/api-reference/endpoints/agent-metrics) | Infrastructure metrics |
| `GET` | [`/api/opensearch/_cat/indices`](/api-reference/endpoints/list-indices) | List indices |
## Common Response Formats
### Health Checks
All services implement a standard health check:
```json theme={null}
{
"status": "ok"
}
```
### Error Responses
```json theme={null}
{
"error": "description of the error",
"status": 500
}
```
### Authentication Errors
```json theme={null}
{
"error": "Missing x-logclaw-api-key header"
}
```
### Proxy Errors
When a backend service is unreachable, the Auth Proxy returns:
```json theme={null}
{
"error": "Failed to forward request to Console API",
"status": 502
}
```
# Ticketing API
Source: https://docs.logclaw.ai/api-reference/ticketing
Incident management and runtime configuration API for the Ticketing Agent.
# Ticketing API
The Ticketing Agent provides APIs for incident management, platform configuration, severity routing, and connectivity testing.
**Base URL:** `http://logclaw-ticketing-agent:18081`
**Dashboard proxy:** `/api/ticketing/*`
***
## Incidents
### List Incidents
```
GET /api/incidents
```
Returns all tracked incidents with their current state and metadata.
#### Response
```json theme={null}
[
{
"id": "inc-20240301-001",
"title": "High error rate on payment-api",
"severity": "critical",
"status": "open",
"service": "payment-api",
"anomalyScore": 4.2,
"affectedServices": ["payment-api", "order-engine", "notification-svc"],
"traceId": "abcdef1234567890",
"createdAt": "2024-03-01T15:30:00Z",
"updatedAt": "2024-03-01T15:30:00Z",
"platforms": ["pagerduty", "jira", "slack"]
}
]
```
### Acknowledge Incident
```
POST /api/incidents/:id/acknowledge
```
Transition an incident from `open` to `acknowledged`.
#### Response
```json theme={null}
{
"id": "inc-20240301-001",
"status": "acknowledged",
"acknowledgedAt": "2024-03-01T15:35:00Z"
}
```
### Resolve Incident
```
POST /api/incidents/:id/resolve
```
Transition an incident to `resolved`.
#### Response
```json theme={null}
{
"id": "inc-20240301-001",
"status": "resolved",
"resolvedAt": "2024-03-01T16:00:00Z"
}
```
### Escalate Incident
```
POST /api/incidents/:id/escalate
```
Escalate an incident to higher-priority platforms.
#### Response
```json theme={null}
{
"id": "inc-20240301-001",
"status": "escalated",
"escalatedAt": "2024-03-01T15:45:00Z",
"escalatedTo": ["pagerduty"]
}
```
***
## Runtime Configuration
### Get Full Configuration
```
GET /api/v1/config
```
Returns the complete runtime configuration including all platform settings, routing rules, anomaly thresholds, and LLM configuration.
#### Response
```json theme={null}
{
"platforms": {
"pagerduty": { "enabled": true, "routingKey": "****" },
"jira": { "enabled": true, "baseUrl": "https://org.atlassian.net" },
"servicenow": { "enabled": false },
"opsgenie": { "enabled": false },
"slack": { "enabled": true, "webhookUrl": "****" }
},
"routing": {
"critical": ["pagerduty", "slack", "jira"],
"high": ["jira", "slack"],
"medium": ["jira"],
"low": ["slack"]
},
"anomaly": {
"minimumScore": 0.85,
"lookbackWindow": "15m"
},
"llm": {
"provider": "openai",
"model": "gpt-4"
}
}
```
Sensitive fields (API keys, tokens, passwords) are masked with `****` in the response. Use the PATCH endpoints to update them.
### Update Platform Settings
```
PATCH /api/v1/config/platforms
```
Update one or more ticketing platform configurations.
#### Request Body
```json theme={null}
{
"pagerduty": {
"enabled": true,
"routingKey": "your-pagerduty-routing-key"
},
"slack": {
"enabled": true,
"webhookUrl": "https://hooks.slack.com/services/T.../B.../..."
}
}
```
#### Required Fields Per Platform
| Platform | Required Fields |
| ---------- | ------------------------------------- |
| PagerDuty | `routingKey` |
| Jira | `baseUrl`, `apiToken`, `userEmail` |
| ServiceNow | `instanceUrl`, `username`, `password` |
| OpsGenie | `apiKey` |
| Slack | `webhookUrl` |
#### Response
```json theme={null}
{
"status": "ok",
"updated": ["pagerduty", "slack"]
}
```
### Update Routing Rules
```
PATCH /api/v1/config/routing
```
Configure which platforms receive incidents for each severity level.
#### Request Body
```json theme={null}
{
"critical": ["pagerduty", "slack", "jira"],
"high": ["jira", "slack"],
"medium": ["jira"],
"low": ["slack"]
}
```
#### Response
```json theme={null}
{
"status": "ok",
"routing": {
"critical": ["pagerduty", "slack", "jira"],
"high": ["jira", "slack"],
"medium": ["jira"],
"low": ["slack"]
}
}
```
### Update Anomaly Settings
```
PATCH /api/v1/config/anomaly
```
#### Request Body
```json theme={null}
{
"minimumScore": 0.9,
"lookbackWindow": "30m"
}
```
| Field | Type | Default | Description |
| ---------------- | ------ | ------- | -------------------------------------------------- |
| `minimumScore` | number | `0.85` | Minimum anomaly score to trigger incident creation |
| `lookbackWindow` | string | `15m` | Time window for anomaly grouping and deduplication |
### Update LLM Settings
```
PATCH /api/v1/config/llm
```
#### Request Body
```json theme={null}
{
"provider": "openai",
"model": "gpt-4",
"apiKey": "sk-..."
}
```
***
## Connectivity Testing
### Test Platform Connection
```
POST /api/v1/test-connection
```
Verify connectivity to a ticketing platform.
#### Request Body
```json theme={null}
{
"platform": "pagerduty"
}
```
#### Success Response
```json theme={null}
{
"status": "ok",
"platform": "pagerduty",
"message": "Connection successful"
}
```
#### Failure Response
```json theme={null}
{
"status": "error",
"platform": "pagerduty",
"message": "Invalid routing key"
}
```
### Test LLM Connection
```
POST /api/v1/test-llm
```
Verify connectivity to the configured LLM provider.
#### Success Response
```json theme={null}
{
"status": "ok",
"provider": "openai",
"model": "gpt-4",
"message": "LLM connection successful"
}
```
***
## Batch Operations
### Batch Update Incidents
```
POST /api/v1/incidents/batch
```
Perform bulk operations on multiple incidents at once. Supports acknowledge, resolve, and escalate actions.
#### Request Body
```json theme={null}
{
"ids": ["inc-20240301-001", "inc-20240301-002", "inc-20240301-003"],
"action": "acknowledge"
}
```
| Field | Type | Required | Description |
| -------- | --------- | -------- | -------------------------------------------- |
| `ids` | string\[] | Yes | Array of incident IDs to update |
| `action` | string | Yes | One of: `acknowledge`, `resolve`, `escalate` |
#### Response
```json theme={null}
{
"updated": 3,
"action": "acknowledge",
"ids": ["inc-20240301-001", "inc-20240301-002", "inc-20240301-003"]
}
```
***
## Audit Trail
### Get Incident Audit Log
```
GET /api/v1/audit?incident_id=:id
```
Returns the full state-change audit trail for a specific incident. Every acknowledge, resolve, escalate, and reopen action is logged with a timestamp.
#### Query Parameters
| Parameter | Type | Required | Description |
| ------------- | ------ | -------- | ----------------------------------------------- |
| `incident_id` | string | Yes | The incident ID to retrieve the audit trail for |
#### Response
```json theme={null}
{
"incident_id": "inc-20240301-001",
"entries": [
{
"action": "created",
"timestamp": "2024-03-01T15:30:00Z",
"details": "Incident created from anomaly detection"
},
{
"action": "acknowledged",
"timestamp": "2024-03-01T15:35:00Z",
"details": "Status changed from open to acknowledged"
},
{
"action": "escalated",
"timestamp": "2024-03-01T15:45:00Z",
"details": "Escalated to pagerduty"
},
{
"action": "resolved",
"timestamp": "2024-03-01T16:00:00Z",
"details": "Status changed to resolved"
}
]
}
```
***
## LLM Status
### Get LLM Provider Status
```
GET /api/v1/llm-status
```
Returns the current status and health of the configured LLM provider, including whether it is reachable and the configured model.
#### Response (Healthy)
```json theme={null}
{
"status": "ok",
"provider": "openai",
"model": "gpt-4",
"available": true
}
```
#### Response (Degraded / Unreachable)
```json theme={null}
{
"status": "degraded",
"provider": "openai",
"model": "gpt-4",
"available": false,
"error": "Connection timeout after 5s"
}
```
When the LLM provider is unreachable, the Ticketing Agent continues to create incidents using rule-based fallback RCA instead of AI-generated root cause analysis. The Dashboard displays a "Fallback" badge on affected incidents.
***
## Deduplication
The Ticketing Agent automatically deduplicates incoming anomalies to prevent duplicate incidents. Two anomalies are considered duplicates when they share the same service name, severity level, and occur within the configured lookback window.
When a duplicate is detected:
* No new incident is created
* The existing incident's `updatedAt` timestamp is refreshed
* The anomaly count is incremented on the existing incident
Configure the deduplication window via the anomaly settings endpoint:
```
PATCH /api/v1/config/anomaly
```
```json theme={null}
{
"lookbackWindow": "30m"
}
```
# Architecture
Source: https://docs.logclaw.ai/architecture
# LogClaw Architecture — End-to-End Data Flow
## Overview
LogClaw is an AI-powered observability platform that ingests OTLP logs, detects anomalies using ML-boosted scoring, automatically creates incidents, and dispatches alerts with root cause analysis.
```
Customer App → Auth Proxy → OTel Collector → Kafka → Flink/Bridge → OpenSearch → Ticketing Agent → Email/Slack
```
***
## Service Map
```
┌──────────────────────────────────────────────────────────────┐
│ KUBERNETES CLUSTER │
│ │
CUSTOMER APP │ ┌─────────────┐ ┌──────────────────┐ │
(any language) │ │ Auth Proxy │ │ OTel Collector │ │
│ │ │ (Node.js) │────▶│ (2 replicas) │ │
│ POST /v1/logs │ │ │ │ │ │
│ Header: x-api-key │ │ • Validate │ │ • Receive OTLP │ │
│ Body: OTLP JSON │ │ API key │ │ • Export to │ │
│ │ │ • Inject │ │ Kafka │ │
└───────────────────────────▶│ │ tenant_id │ │ │ │
│ └─────────────┘ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ KAFKA (Strimzi) │ │
│ │ │ │
│ │ • raw-logs (6 part) │ │
│ │ • enriched-logs │ │
│ │ • anomaly-events │ │
│ │ • dead-letter-queue │ │
│ └────────┬────────────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌───────────┐ ┌─────────────┐ │
│ │ Flink ETL │ │ Flink │ │ Flink │ │
│ │ (or Bridge │ │ Enrich │ │ Anomaly │ │
│ │ Thread 1) │ │ (Feast) │ │ Scorer │ │
│ └──────┬───────┘ └─────┬─────┘ └──────┬──────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Bridge Thread 3 │ │
│ │ • Index logs to OpenSearch │ │
│ │ • Index anomalies to OpenSearch │ │
│ │ • Group anomalies → Create incidents │ │
│ └──────────────────────┬─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ OpenSearch │ │
│ │ │ │
│ │ • logclaw-logs-* │ │
│ │ • logclaw-anomalies-*│ │
│ │ • logclaw-incidents-*│ │
│ └────────┬────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────┐ │
│ │ Ticketing Agent │ │
│ │ • Root cause │ │
│ │ • LLM enrichment │ │
│ │ • Email/Slack/Jira│ │
│ └────────────────────┘ │
│ │
│ ┌─────────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Console │ │ Dashboard│ │ Airflow │ │
│ │ (Next.js) │ │ (Next.js)│ │ (DAGs) │ │
│ │ enterprise │ │ internal │ │ ML train │ │
│ └─────────────┘ └──────────┘ └──────────┘ │
└──────────────────────────────────────────────────────────────┘
```
***
## Step-by-Step: 10 Logs Through the Pipeline
### Step 1: Ingestion (Auth Proxy → OTel Collector)
Customer sends OTLP logs via HTTP POST:
```
POST https://otel.logclaw.ai/v1/logs
Header: x-api-key: lc_abc123
Body: {
"resourceLogs": [{
"resource": { "attributes": [{"key":"service.name","value":{"stringValue":"payment-api"}}] },
"scopeLogs": [{
"logRecords": [
{ "timeUnixNano": "...", "severityText": "ERROR", "body": {"stringValue": "Connection refused"} },
... (9 more logs)
]
}]
}]
}
```
**Auth Proxy** (Node.js, `apps/logclaw-auth-proxy/`):
1. Extracts `x-api-key` header
2. Queries PostgreSQL (`logclaw_enterprise` DB) to validate the key
3. Looks up the tenant ID associated with that API key
4. Injects `tenant_id` as a resource attribute into the OTLP payload
5. Forwards the modified payload to OTel Collector on port 4318
**OTel Collector** (`charts/logclaw-otel-collector/`):
1. Receives OTLP HTTP on port 4318
2. Applies batch processor (groups logs for efficiency)
3. Exports to Kafka topic `raw-logs` via the `kafkaexporter`
```yaml theme={null}
# From otel-collector-config.yaml
exporters:
kafka:
brokers: ["logclaw-kafka-bootstrap:9092"]
topic: "raw-logs"
encoding: otlp_json
```
### Step 2: Kafka (Message Bus)
Kafka stores logs in topics with configurable retention:
```
Topic: raw-logs | 6 partitions | 48h retention | All raw OTLP logs
Topic: enriched-logs | 6 partitions | 48h retention | Flat JSON + ML features
Topic: anomaly-events | 3 partitions | 7 day retention | Only scored anomalies
Topic: dead-letter-queue | 1 partition | 30 day retention | Unparseable/failed logs
```
Each consumer (Flink job or Bridge thread) has its own **consumer group** with tracked offsets. If a service restarts, it resumes from the last committed offset — no data loss.
### Step 3: Flink ETL (Unwrap OTLP → Flat JSON)
**What it does:** Converts the deeply nested OTLP format into a flat, searchable JSON record.
**Input** (from `raw-logs` topic — nested OTLP):
```
resourceLogs[]
└─ resource.attributes[] ← service.name, tenant_id, etc.
└─ scopeLogs[]
└─ logRecords[] ← the actual log
├─ timeUnixNano
├─ severityText
├─ body.stringValue
├─ attributes[]
├─ traceId
└─ spanId
```
**Output** (to `enriched-logs` topic — flat JSON):
```json theme={null}
{
"@timestamp": "2024-03-19T12:00:00Z",
"log_id": "f47ac10b-...",
"level": "ERROR",
"message": "Connection refused to redis:6379",
"service": "payment-api",
"tenant_id": "acme-corp",
"trace_id": "abc123",
"span_id": "def456"
}
```
**What ETL does (that's all):**
1. Unwrap the 3-level OTLP nesting → flat record
2. Generate deterministic `log_id` (UUID5 from trace+span+timestamp) for OpenSearch idempotency
3. Normalize severity (`CRITICAL` → `FATAL`)
4. Flatten attribute keys (`service.name` → `service_name`) — dots cause nested objects in OpenSearch
5. If unparseable → send to `dead-letter-queue`
**Source:** `apps/flink-jobs/logclaw-etl/` (Java) or `apps/bridge/` Thread 1 (Python)
### Step 4: Flink Enrichment (Add ML Features from Feast)
**What it does:** Reads flat JSON from `enriched-logs`, queries Feast for ML features, writes back with added fields.
**Before:**
```json theme={null}
{ "level": "ERROR", "message": "Connection refused to redis:6379", "service": "payment-api" }
```
**After (4 ML features added):**
```json theme={null}
{
"level": "ERROR",
"message": "Connection refused to redis:6379",
"service": "payment-api",
"ml_error_rate_1h": 0.12,
"ml_p99_latency_1h": 2400,
"ml_request_rate_1h": 15000,
"ml_anomaly_history_count": 3,
"enriched": true
}
```
These features let the Anomaly Scorer make better decisions — "this service already has a 12% error rate and 3 past anomalies."
**Source:** `apps/flink-jobs/logclaw-enrichment/` (Java) or Bridge Thread 1 (Python)
### Step 5: Flink Anomaly Scorer
**Scoring Algorithm:**
```
1. BASE SCORE (severity)
DEBUG/TRACE → 0.0 (skip) INFO → 0.0 WARN → 0.1 ERROR → 0.3 FATAL → 0.5
2. PATTERN BOOST (regex on message)
"OutOfMemory|OOM" → +0.3 "timeout" → +0.2 "connection refused" → +0.2
"NPE|segfault" → +0.25 "disk full" → +0.3 "SSL|TLS" → +0.15
3. ML BOOST (from Feast)
ml_error_rate_1h > 0.10 → +0.15 ml_error_rate_1h > 0.25 → +0.25
ml_p99_latency_1h > 2000ms → +0.10 ml_anomaly_history > 2 → +0.10
4. FINAL = BASE + PATTERN + ML (capped at 1.0)
5. score >= 0.5 → EMIT score < 0.5 → DROP
```
**Example: 10 Logs**
```
Log 1: INFO "Request completed 200 OK" 0.0 → DROP
Log 2: WARN "Slow query took 3200ms" 0.1 → DROP
Log 3: ERROR "Connection refused to redis:6379" 0.5 → ANOMALY
Log 4: ERROR "Request timeout after 30s" 0.5 → ANOMALY
Log 5: ERROR "Failed to parse JSON response" 0.3 → DROP
Log 6: ERROR "Failed to parse JSON" (ml_error=0.30) 0.55 → ANOMALY (ML boost!)
Log 7: FATAL "OutOfMemoryError: heap space" 0.8 → ANOMALY
Log 8: WARN "SSL cert expires in 7 days" 0.25 → DROP
Log 9: WARN "SSL cert expires" (ml_error=0.12) 0.50 → ANOMALY (ML boost!)
Log 10: DEBUG "Entering processPayment()" SKIP
```
**Result: 10 logs → 5 anomalies** (Logs 6 and 9 only passed because of ML enrichment)
**Source:** `apps/flink-jobs/logclaw-anomaly-scorer/` (Java) or Bridge Thread 2 (Python)
### Step 6: Bridge Thread 3 (Index + Incidents)
**Flink stops at writing to `anomaly-events`. Bridge Thread 3 picks up:**
1. **Index all enriched logs** → `logclaw-logs-{tenant}` in OpenSearch
2. **Index anomaly events** → `logclaw-anomalies-{tenant}` in OpenSearch
3. **Group anomalies by service + 5-min window** → if >= 3 → **create Incident**
```
Log 3 (redis, 0.50) ──┐
Log 4 (redis, 0.50) ──┤ Same service, 5-min window → 3 anomalies → INCIDENT
Log 7 (heap, 0.80) ──┘
Log 6 + Log 9 (different services, 1 each) → no incident yet
```
**Source:** `apps/bridge/` (Python, also embedded in `charts/logclaw-bridge/templates/configmap-app.yaml`)
### Step 7: Ticketing Agent (Root Cause + Alerts)
Polls OpenSearch for `status=open` incidents:
```
1. ROOT CAUSE ANALYSIS
Query logs ±10 min window → find first error, correlated traces, deploys
2. LLM ENRICHMENT (if configured)
Send samples to OpenAI/Ollama → get remediation suggestion + causal chain
3. SEVERITY CLASSIFICATION
1 service + score < 0.6 → "medium"
2+ services or score > 0.7 → "high"
3+ services or FATAL → "critical"
4. DISPATCH
Email (Resend) | Slack (webhook) | Jira (API) | PagerDuty (API)
```
**Source:** `apps/ticketing-agent/` (Python, also in configmap)
***
## Airflow, Feast & Redis: The ML Feature Pipeline
Airflow's **sole purpose** in LogClaw is to run periodic feature computation DAGs. It does not orchestrate any other services or ETL — that's Flink/Bridge's job.
### How Features Get Into Redis
```
OpenSearch Airflow DAG Redis
(logclaw-logs-*) (runs every 1h) (Feast online store)
│ │ │
│ Query: aggregate logs │ │
│ per service per hour │ │
│ ─────────────────────▶ │ │
│ │ │
│ Returns: │ Computes features: │
│ • error count last 1h │ • error_rate_1h │
│ • total count last 1h │ • p99_latency_1h │
│ • latency percentiles │ • request_rate_1h │
│ • past anomaly count │ • anomaly_history_count │
│ │ │
│ │ feast materialize: │
│ │ ──────────────────────────▶ │
│ │ │
│ │ Key: "payment-api" │
│ │ Val: { │
│ │ error_rate_1h: 0.12, │
│ │ p99_latency_1h: 2400, │
│ │ request_rate_1h: 15000, │
│ │ anomaly_history_count: 3 │
│ │ } │
```
### How Flink Enrichment Reads Features (Real-Time)
```
Kafka Flink Enrichment Redis (Feast)
(enriched-logs) (or Bridge Thread 1) (online store)
│ │ │
│ Log: { │ │
│ service: "payment-api", │ │
│ level: "ERROR" │ │
│ } │ │
│ ─────────────────────▶ │ │
│ │ GET features for │
│ │ "payment-api" │
│ │ ───────────────────▶ │
│ │ │
│ │ ◀─── { │
│ │ error_rate_1h: 0.12, │
│ │ p99_latency_1h: 2400 │
│ │ } │
│ │ │
│ ◀──── Enriched log: { │ │
│ service: "payment-api", │ │
│ ml_error_rate_1h: 0.12, │ │
│ ml_p99_latency_1h: 2400 │ │
│ } │ │
```
### The Full Feature Cycle
```
1. Logs flow in → indexed to OpenSearch (by Bridge Thread 3)
2. Airflow (hourly DAG) → queries OpenSearch aggregates → computes features → writes to Redis via Feast
3. Flink Enrichment → for each log, looks up service name in Redis via Feast → appends ML features
4. Anomaly Scorer → uses those features to boost/reduce the anomaly score
```
### Why Redis and Not OpenSearch Directly?
* **Redis**: Sub-millisecond per key lookup (0.1ms). Perfect for per-log real-time enrichment.
* **OpenSearch**: 10-50ms per query. Too slow when enriching thousands of logs per second.
* Feast is the abstraction layer — defines the feature schema, handles Redis read/write, and provides a clean API for the Flink job to call.
### What Airflow Runs
| DAG | Schedule | What It Does |
| ----------------- | ------------ | -------------------------------------------------------------------------------------------------------------- |
| `feature_compute` | Every 1 hour | Queries OpenSearch for per-service aggregates, computes 4 ML features, writes to Redis via `feast materialize` |
That's the only DAG. Airflow is intentionally lightweight here — one scheduler pod, one webserver pod, no workers (uses KubernetesExecutor for DAG runs).
***
## Flink vs Bridge
| | **Flink** | **Bridge** |
| ---------- | ---------------------------------- | --------------------------- |
| Language | Java (3 JARs) | Python (1 file, 3 threads) |
| Pods | 6+ (3 JM + 3 TM) | 1 |
| Requires | Flink Operator + node pool | Nothing extra |
| Scaling | Horizontal (parallelism) | Vertical only |
| Guarantees | Exactly-once (RocksDB checkpoints) | At-least-once (in-memory) |
| Cost | \~4 CPU, 6Gi RAM | \~200m CPU, 256Mi RAM |
| Use case | Production at scale | Dev, staging, small tenants |
Bridge always runs — it handles **indexing + incident creation** that Flink doesn't do. When Flink is also enabled, Bridge Threads 1+2 duplicate Flink's work but output is idempotent (same `log_id` → OpenSearch overwrites).
***
## Flink Runtime
Flink is a **stream processing engine** (not batch like Spring Batch). Jobs run continuously.
```
Our code (Java JARs) → loaded into Flink runtime (apache/flink:1.19)
Each JAR's main():
1. StreamExecutionEnvironment.getExecutionEnvironment()
2. KafkaSource (reads from topic)
3. .map() / .flatMap() — our business logic
4. KafkaSink (writes to topic)
5. env.execute() — runs forever
Flink manages: parallelism, checkpointing, exactly-once, failure recovery
```
***
## Kubernetes & Helm
### Chart Structure
```
charts/
├── logclaw-tenant/ # Umbrella — deploys all subcharts
├── logclaw-kafka/ # Strimzi Kafka CR + KafkaTopic CRs
├── logclaw-opensearch/ # OpenSearch StatefulSet
├── logclaw-otel-collector/ # OTel Collector Deployment
├── logclaw-bridge/ # Bridge (Python source in ConfigMap)
├── logclaw-ticketing-agent/ # Ticketing Agent (Python in ConfigMap)
├── logclaw-dashboard/ # Internal dashboard (Next.js)
├── logclaw-flink/ # 3 Flink job Deployments
├── logclaw-airflow/ # Scheduler + Webserver
├── logclaw-ml-engine/ # Feast + Redis
├── logclaw-auth-proxy/ # Separate release
└── logclaw-console/ # Enterprise console, separate release
```
### How `helm upgrade` Works
1. Helm diffs rendered templates vs cluster state
2. **Deployments**: Rolling update (new ReplicaSet up, old down, zero downtime)
3. **StatefulSets**: Pods updated one at a time (ordered)
4. **ConfigMaps**: Updated in-place; pods restart if Deployment has checksum annotation
5. **CRDs** (Kafka, OpenSearch): Operator reconciles (Strimzi rolls brokers one by one)
6. **Success**: Release revision stored as K8s Secret
7. **Failure**: `helm rollback logclaw ` restores previous state
### ConfigMap = Deployment Artifact
For Python services, source code is embedded in ConfigMap:
```yaml theme={null}
# charts/logclaw-bridge/templates/configmap-app.yaml
data:
main.py: |
#!/usr/bin/env python3
from kafka import KafkaConsumer, KafkaProducer
...
```
**Both `apps/` source AND the configmap must be kept in sync on every change.**
### Values Per Environment
```
deployments/
├── us-west-prod-values.yaml # Flink ON, 100Gi disks, production
├── us-east-prod-values.yaml # Flink OFF, 50Gi disks, test
├── us-west-console-values.yaml # Console for us-west
├── us-east-console-values.yaml # Console for us-east
└── local-orbstack-values.yaml # Minimal, no TLS, no ingress
```
***
## External Dependencies
| Service | Image | Version |
| -------------- | -------------------------------------- | -------------------- |
| Kafka | `strimzi/kafka` | 0.50.0 + Kafka 4.0.0 |
| OpenSearch | `opensearchproject/opensearch` | 2.14.0 |
| OTel Collector | `otel/opentelemetry-collector-contrib` | 0.114.0 |
| Feast | `feastdev/feature-server` | 0.40.0 |
| PostgreSQL | `bitnami/postgresql` | latest |
| Redis | `bitnami/redis` | latest |
| Airflow | `apache/airflow` | 2.9.3 |
| Flink | `apache/flink` | 1.19 |
***
## Summary: 10 Logs → 1 Email Alert
```
10 raw logs from payment-api
│
▼ Auth Proxy: validate API key, inject tenant_id
▼ OTel Collector: batch → Kafka
│
[raw-logs]
│
▼ Flink ETL: unwrap OTLP → flat JSON
│
[enriched-logs]
│
▼ Flink Enrichment: add Feast ML features
│
[enriched-logs] (with ml_error_rate, ml_p99_latency, etc.)
│
▼ Flink Anomaly: score each log (0.0–1.0)
│
│ 5 logs >= 0.5 → emitted
│ 5 logs < 0.5 → dropped
│
[anomaly-events]
│
▼ Bridge: index to OpenSearch + group by service
│
│ 3 anomalies from payment-api in 5 min → CREATE INCIDENT
│
[OpenSearch: logclaw-incidents-prod]
│
▼ Ticketing Agent: root cause + LLM + dispatch
│
Email: "[HIGH] INC-2024-00142: payment-api — Redis pool exhausted"
```
# Bridge
Source: https://docs.logclaw.ai/components/bridge
OTLP ETL translator, anomaly detection, trace correlation, and OpenSearch indexing service.
# Bridge
The Bridge is a Python-based service that provides the core log processing pipeline. It consumes raw OTLP JSON from Kafka, normalizes it into flat documents, detects anomalies, correlates traces, and indexes everything into OpenSearch.
## Architecture
The Bridge runs **4 concurrent threads**, each handling a distinct stage of the pipeline:
```
Kafka "raw-logs"
│
▼
┌─────────────────────┐
│ Thread 1: OTLP ETL │ Flatten OTLP JSON → normalized documents
│ (consumer group: │ Write to "enriched-logs" topic
│ logclaw-bridge-etl)│
└──────────┬──────────┘
│
Kafka "enriched-logs"
│ │
▼ ▼
┌──────────┐ ┌──────────────────────┐
│ Thread 2 │ │ Thread 3: Indexer │
│ Anomaly │ │ Bulk write to │
│ Detector │ │ OpenSearch │
│ (Signal) │ │ logclaw-logs-* │
└──────────┘ └──────────────────────┘
│
▼
┌──────────────────────┐
│ Thread 4: Lifecycle │
│ Request correlation │
│ (5-layer trace) │
└──────────────────────┘
```
## OTLP ETL (Thread 1)
The ETL thread consumes OTLP JSON messages from the `raw-logs` Kafka topic and flattens them into canonical log documents.
**OTLP unwrapping path:**
```
resourceLogs → scopeLogs → logRecords → flatten each record
```
**Field mapping:**
| OTLP Field | Output Field | Description |
| ------------------------------------- | ------------- | ------------------------------------- |
| `resource.attributes["service.name"]` | `service` | Service name |
| `logRecord.body.stringValue` | `message` | Log message |
| `logRecord.severityText` | `level` | Log level (INFO, WARN, ERROR) |
| `logRecord.timeUnixNano` | `timestamp` | ISO-8601 timestamp |
| `logRecord.traceId` | `trace_id` | Distributed trace ID |
| `logRecord.spanId` | `span_id` | Span ID |
| `resource.attributes["host.name"]` | `host` | Hostname |
| `resource.attributes["tenant_id"]` | `tenant_id` | Tenant identifier |
| `logRecord.attributes[*]` | *(flattened)* | Custom attributes as top-level fields |
## Anomaly Detection (Thread 2)
Uses a **signal-based composite scoring** system to classify whether an error log is incident-worthy. Not every error triggers an incident — the system distinguishes actionable failures (OOM, database deadlocks, cascading failures) from noise (validation errors, 404s).
Two detection paths run in parallel:
* **Immediate path** — OOM, crash, and resource exhaustion patterns fire in under 1 second without waiting for a time window
* **Windowed path** — statistical z-score combined with blast radius, velocity, and recurrence signals across the sliding window
See [Incident Classification](/components/incident-classification) for the full signal system documentation.
**Key environment variables:**
| Variable | Default | Description |
| ----------------------------------- | ------- | ---------------------------------------------- |
| `ANOMALY_ZSCORE_THRESHOLD` | `2.0` | Z-score threshold for statistical spike signal |
| `ANOMALY_WINDOW_SECONDS` | `300` | Sliding window duration |
| `ANOMALY_COMPOSITE_SCORE_THRESHOLD` | `0.4` | Minimum composite score to emit an event |
| `ANOMALY_IMMEDIATE_DEDUP_SECONDS` | `60` | Dedup window for immediate-path emissions |
When an anomaly is detected, an event is published to `anomaly-events` Kafka topic with `anomaly_score`, `severity`, `signals`, `detection_mode`, `error_category`, and `error_message` fields.
## Request Lifecycle Engine (Thread 4)
The lifecycle engine performs **5-layer trace correlation** to group related log entries into request timelines:
1. **Trace ID grouping** — group logs sharing the same `trace_id`
2. **Temporal proximity** — cluster logs within a time window
3. **Service dependency mapping** — map caller→callee relationships
4. **Error propagation tracking** — trace error cascades across services
5. **Blast radius computation** — determine affected services and endpoints
## Prometheus Metrics
The Bridge exposes Prometheus-format metrics at `GET /metrics`:
| Metric | Type | Description |
| ------------------------------------------------- | ------- | ---------------------------------------------------------- |
| `logclaw_bridge_etl_consumed_total` | Counter | Kafka messages (batches) consumed from `raw-logs` |
| `logclaw_bridge_etl_records_received_total` | Counter | Individual OTLP log records flattened |
| `logclaw_bridge_etl_produced_total` | Counter | Enriched documents written to `enriched-logs` |
| `logclaw_bridge_anomaly_detected_total` | Counter | Total anomaly events emitted |
| `logclaw_bridge_anomaly_immediate_detected_total` | Counter | Anomalies detected via immediate path (OOM/crash/resource) |
| `logclaw_bridge_anomaly_windowed_detected_total` | Counter | Anomalies detected via windowed statistical path |
| `logclaw_bridge_anomaly_signals_extracted_total` | Counter | Error records with at least one signal extracted |
| `logclaw_bridge_anomaly_below_threshold_total` | Counter | Signals filtered (below composite score threshold) |
| `logclaw_bridge_anomaly_std_zero_detected_total` | Counter | Constant error rate cases (previously silent failures) |
| `logclaw_bridge_indexer_indexed_total` | Counter | Documents indexed into OpenSearch |
| `logclaw_bridge_indexer_errors_total` | Counter | OpenSearch indexing errors |
## Configuration
### Environment Variables
| Variable | Required | Default | Description |
| ------------------------------------- | -------- | --------------- | ------------------------------------------------ |
| `KAFKA_BROKERS` | Yes | — | Kafka bootstrap servers |
| `KAFKA_TOPIC_RAW` | No | `raw-logs` | Topic to consume raw OTLP JSON |
| `KAFKA_TOPIC_ENRICHED` | No | `enriched-logs` | Topic to produce enriched documents |
| `OPENSEARCH_ENDPOINT` | Yes | — | OpenSearch cluster URL |
| `OPENSEARCH_USERNAME` | No | — | OpenSearch Basic Auth username |
| `OPENSEARCH_PASSWORD` | No | — | OpenSearch Basic Auth password |
| `ANOMALY_ZSCORE_THRESHOLD` | No | `2.0` | Z-score threshold for statistical spike signal |
| `ANOMALY_WINDOW_SECONDS` | No | `300` | Sliding window duration in seconds |
| `ANOMALY_COMPOSITE_SCORE_THRESHOLD` | No | `0.4` | Minimum composite score to emit an anomaly event |
| `ANOMALY_IMMEDIATE_DEDUP_SECONDS` | No | `60` | Dedup window for immediate-path emissions |
| `ANOMALY_BLAST_RADIUS_WINDOW_SECONDS` | No | `60` | Cross-service error tracking window |
| `PORT` | No | `8080` | HTTP server port |
### Runtime Configuration
The Bridge supports dynamic runtime configuration via the `/config` endpoint:
```bash theme={null}
# Get current config
curl http://localhost:8080/config
# Update anomaly thresholds
curl -X PATCH http://localhost:8080/config \
-H "Content-Type: application/json" \
-d '{"zscoreThreshold": 3.0, "windowSeconds": 600, "compositeScoreThreshold": 0.45}'
```
### Helm Values
```yaml theme={null}
logclaw-bridge:
env:
KAFKA_BROKERS: "logclaw-kafka-bootstrap:9092"
OPENSEARCH_ENDPOINT: "https://logclaw-opensearch:9200"
ANOMALY_ZSCORE_THRESHOLD: "2.0"
ANOMALY_WINDOW_SECONDS: "300"
ANOMALY_COMPOSITE_SCORE_THRESHOLD: "0.4"
```
## Health Check
```bash theme={null}
curl http://localhost:8080/health
```
Returns:
```json theme={null}
{
"status": "ok",
"threads": {
"etl": "running",
"anomaly": "running",
"indexer": "running",
"lifecycle": "running"
},
"kafka": "connected",
"opensearch": "connected"
}
```
# Dashboard
Source: https://docs.logclaw.ai/components/dashboard
Next.js web UI for log ingestion, incident management, anomaly visualization, and pipeline monitoring.
# Dashboard
The LogClaw Dashboard is a **Next.js** web application providing a complete operational UI for the LogClaw platform.
## Features
Drag-and-drop file upload supporting JSON, NDJSON, CSV, and plain text formats. Automatically converts to OTLP format and sends via the OTel Collector proxy. Includes tabbed code examples for cURL, Python, Node.js, Go, and Java.
Real-time throughput visualization: Ingest → Stream → Process → Index → Detect → ML → Orchestrate → Ticketing.
View, acknowledge, resolve, and escalate incidents. Bulk actions (acknowledge/resolve/escalate multiple), CSV export, full-text search, and auto-deduplication. Configure ticketing platforms and routing rules.
Time-series charts showing anomaly scores, affected services, error rate trends, and blast radius.
System-aware dark mode with manual toggle (Light / Dark / System). Persisted via localStorage. Accessible from the navigation bar.
Error boundaries catch component crashes and display retry UI. Loading skeletons provide visual feedback during data fetches. LLM fallback badge indicates when AI RCA is unavailable.
## Architecture
The Dashboard acts as a **proxy gateway** to all backend services. Each backend gets its own API route:
| Proxy Route | Backend Service | Environment Variable |
| ------------------- | -------------------- | ------------------------- |
| `/api/otel/*` | OTel Collector | `OTEL_COLLECTOR_ENDPOINT` |
| `/api/bridge/*` | Bridge | `BRIDGE_ENDPOINT` |
| `/api/opensearch/*` | OpenSearch | `OPENSEARCH_ENDPOINT` |
| `/api/ticketing/*` | Ticketing Agent | `TICKETING_ENDPOINT` |
| `/api/airflow/*` | Airflow | `AIRFLOW_ENDPOINT` |
| `/api/feast/*` | Feast | `FEAST_ENDPOINT` |
| `/api/agent/*` | Infrastructure Agent | `AGENT_ENDPOINT` |
## Environment Variables
| Variable | Required | Default | Description |
| ------------------------- | -------- | ------------------------ | ------------------------------ |
| `OTEL_COLLECTOR_ENDPOINT` | Yes | `http://localhost:4318` | OTel Collector HTTP endpoint |
| `BRIDGE_ENDPOINT` | Yes | `http://localhost:8080` | Bridge service endpoint |
| `OPENSEARCH_ENDPOINT` | Yes | `http://localhost:9200` | OpenSearch cluster URL |
| `OPENSEARCH_USER` | No | — | OpenSearch Basic Auth username |
| `OPENSEARCH_PASSWORD` | No | — | OpenSearch Basic Auth password |
| `TICKETING_ENDPOINT` | No | `http://localhost:18081` | Ticketing Agent endpoint |
| `AIRFLOW_ENDPOINT` | No | `http://localhost:28080` | Airflow webserver endpoint |
| `FEAST_ENDPOINT` | No | `http://localhost:6567` | Feast feature server endpoint |
| `AGENT_ENDPOINT` | No | `http://localhost:8080` | Infrastructure Agent endpoint |
## Log File Upload
The Dashboard supports drag-and-drop log file upload:
1. **Drag** a file onto the upload area (or click to browse)
2. The Dashboard parses each log entry based on file format
3. Converts entries to **OTLP format** using the built-in `logsToOtlp()` converter
4. Sends the OTLP payload to `/api/otel/v1/logs` (proxied to OTel Collector)
**Supported formats:**
| Format | Extension | Description | Example |
| ------ | -------------- | ---------------------------------------- | ------------------------------------------ |
| JSON | `.json` | Array or single log object | `[{"message": "...", "level": "ERROR"}]` |
| NDJSON | `.ndjson` | One JSON object per line | `{"message": "..."}\n{"message": "..."}` |
| CSV | `.csv` | Header row required, auto-mapped columns | `message,level,service\n"Error",ERROR,api` |
| Text | `.txt`, `.log` | One log message per line | `2024-03-01 ERROR Connection timeout` |
**Recognized fields:**
| Field | Mapping |
| ------------------------------------- | ----------------------- |
| `message` or `msg` or `body` | OTLP `body.stringValue` |
| `level` or `severity` | OTLP `severityText` |
| `timestamp` or `time` or `@timestamp` | OTLP `timeUnixNano` |
| `service` or `service.name` | OTLP resource attribute |
| `trace_id` or `traceId` | OTLP `traceId` |
| `span_id` or `spanId` | OTLP `spanId` |
## Incident Management Features
### Bulk Actions
Select multiple incidents using checkboxes and perform batch operations:
* **Bulk Acknowledge** — acknowledge all selected incidents at once
* **Bulk Resolve** — resolve all selected incidents at once
* **Bulk Escalate** — escalate all selected incidents at once
The selection toolbar appears when one or more incidents are checked, showing the count and available actions.
### CSV Export
Export the current incident list as a CSV file. The export includes all visible columns: ID, title, severity, status, service, anomaly score, affected services, and timestamps. Click the download button in the incidents toolbar.
### Full-Text Search
Filter incidents by typing in the search bar. Searches across incident title, service name, and affected services in real-time.
### Incident Audit Trail
Each incident detail page shows a complete audit trail of state changes (created → acknowledged → escalated → resolved) with timestamps. View the audit log on the incident detail page under the "Audit Trail" section.
### Auto-Deduplication
The Ticketing Agent automatically deduplicates incoming anomalies to prevent duplicate incidents. Anomalies with the same service and severity within the configured lookback window are merged into the existing incident.
### LLM Fallback Badge
When the configured LLM provider is unreachable, incidents are created using rule-based fallback RCA. These incidents display a "Fallback" badge on the detail page to indicate that AI-generated root cause analysis was not available.
## Dark Mode
The Dashboard supports three theme modes:
* **Light** — default light theme
* **Dark** — dark theme with adjusted surfaces, text colors, borders, and shadows
* **System** — follows the operating system preference
Toggle the theme from the navigation bar using the Sun/Moon/Monitor dropdown. The preference is persisted in `localStorage`.
## Loading Skeletons
All data-driven pages display animated skeleton placeholders while fetching data:
* **Home page** — stat card skeletons, bar chart skeletons, pipeline flow skeletons
* **Incidents list** — incident card skeletons
* **Incident detail** — full-page skeleton with header, timeline, and analysis placeholders
## Error Boundaries
React error boundaries wrap all major page sections. When a component crashes:
* An error message with the component name is displayed
* A **Retry** button allows the user to attempt re-rendering
* The rest of the page continues to function normally
## Pipeline Flow
The pipeline monitoring view shows real-time throughput across two rows:
**Data Pipeline:**
* **Ingest** (OTel Collector) → **Stream** (Kafka) → **Process** (Bridge/Flink) → **Index** (OpenSearch)
**AI & Operations:**
* **Detect** (Anomaly Engine) → **ML** (Feast/KServe) → **Orchestrate** (Airflow) → **Ticketing** (AI Agent)
Each stage shows:
* Current count (formatted with K/M suffixes)
* Data size where applicable
* Health status indicator (green/amber/red with animated ping)
## Ticketing Configuration
The Dashboard provides a UI for configuring ticketing platforms:
### Supported Platforms
| Platform | Required Fields |
| -------------- | ------------------------------------- |
| **PagerDuty** | `routingKey` |
| **Jira** | `baseUrl`, `apiToken`, `userEmail` |
| **ServiceNow** | `instanceUrl`, `username`, `password` |
| **OpsGenie** | `apiKey` |
| **Slack** | `webhookUrl` |
### Configuration Endpoints
All configuration changes are persisted via the Ticketing Agent's runtime config API:
```bash theme={null}
# Update PagerDuty settings
PATCH /api/ticketing/api/v1/config/platforms
{
"pagerduty": {
"enabled": true,
"routingKey": "your-pagerduty-routing-key"
}
}
# Update severity routing
PATCH /api/ticketing/api/v1/config/routing
{
"critical": ["pagerduty", "slack"],
"high": ["jira", "slack"],
"medium": ["jira"],
"low": ["slack"]
}
```
## Deployment
### Helm Values
```yaml theme={null}
logclaw-dashboard:
image:
repository: "ghcr.io/logclaw/dashboard"
tag: "latest"
service:
type: ClusterIP # ClusterIP or LoadBalancer
port: 3000
```
### Docker
```bash theme={null}
cd apps/dashboard
docker build -t logclaw/dashboard:latest .
docker run -p 3000:3000 \
-e OTEL_COLLECTOR_ENDPOINT=http://otel:4318 \
-e BRIDGE_ENDPOINT=http://bridge:8080 \
-e OPENSEARCH_ENDPOINT=http://opensearch:9200 \
logclaw/dashboard:latest
```
# Incident Classification
Source: https://docs.logclaw.ai/components/incident-classification
How LogClaw decides which log signals are incident-worthy vs. noise.
# Incident Classification
LogClaw uses a **signal-based composite scoring** system to classify whether an error log should trigger an incident. Not every error log is incident-worthy — the system distinguishes actionable production failures (OOM, database deadlocks, cascading failures) from expected noise (validation errors, 404s, client mistakes).
## Why Not Simple Error Counting?
A plain error-rate spike detector has three critical failure modes:
| Problem | Effect |
| ------------------------------------------- | ------------------------------------------ |
| Counts all ERROR logs equally | Validation errors and OOMs score the same |
| Requires time window (30s+) before alerting | Process can crash before detection fires |
| `std=0` silent failure | 100% constant error rate produces no alert |
The signal-based system solves all three.
***
## Three-Stage Pipeline
Every error log record flows through three stages inside the Bridge's anomaly detector (Thread 2):
```
Log Record
│
▼
Stage 1: Signal Extraction ← What kind of failure is this?
│
▼
Stage 2: Scoring ← How severe? How widespread? How fast?
│ │
│ ├── Immediate path (OOM / crash / cascading failure)
│ └── Windowed path (statistical z-score + context)
▼
Stage 3: Decision Engine ← Emit to Kafka if score ≥ threshold
```
***
## Stage 1: Signal Extraction
Eight **language-agnostic** pattern groups scan the combined text of `exception_type`, `exception_message`, and `message`. A single record can match multiple patterns simultaneously (multi-signal).
| Pattern | Matches | Weight |
| ------------ | -------------------------------------------------------------------------------------- | ------ |
| `oom` | OutOfMemoryError, heap space, memory limit, GC overhead | 0.95 |
| `crash` | segfault, panic, SIGSEGV, SIGKILL, stack overflow, process died | 0.95 |
| `resource` | disk full, no space left, too many open files, resource exhausted | 0.80 |
| `dependency` | service unavailable, bad gateway, upstream connect error, 502/503/504 | 0.75 |
| `db` | deadlock, lock timeout, duplicate key, constraint violation, connection pool exhausted | 0.75 |
| `timeout` | timeout, timed out, deadline exceeded, context deadline, connect timeout | 0.70 |
| `connection` | ECONNREFUSED, ECONNRESET, broken pipe, socket closed, network unreachable | 0.65 |
| `auth` | unauthorized, forbidden, access denied, invalid token, JWT expired | 0.40 |
**Key property**: patterns are regex-based and language-agnostic. Java, Python, Go, Node.js, Rust — any runtime's exceptions are classified without hardcoded language-specific rules. Unknown exception types are still scored via severity level, HTTP status, and stacktrace depth.
### Additional signals extracted per record
| Signal | Source | Weight |
| ---------------- | ---------------------------------------------------------- | -------- |
| Severity | Log level: FATAL=1.0, CRITICAL=0.95, ERROR=0.70, WARN=0.30 | 0.0–1.0 |
| HTTP status | 503=0.90, 504=0.85, 502=0.80, 5xx=0.70, 429=0.50 | 0.0–0.90 |
| Stacktrace depth | Frame count: 16+ frames=0.30, 6-15=0.15, 2-5=0.05 | 0.0–0.30 |
| Error category | Keyword classifier (timeout, database, auth, etc.) | 0.30 |
***
## Stage 2: Scoring
### Composite Score Formula
Signals are grouped into six categories. The maximum weight within each category is taken (no double-counting), then multiplied by the category's weight:
| Category | Weight | What counts |
| ----------- | ------ | ------------------------------------- |
| Pattern | 30% | Exception/message pattern matches |
| Statistical | 25% | Z-score spike, sustained failure rate |
| Context | 15% | Blast radius, velocity, recurrence |
| HTTP | 10% | HTTP 5xx status codes |
| Severity | 10% | Log level |
| Structural | 10% | Stacktrace depth, error category |
**Score → Severity:**
| Score | Severity |
| ------- | -------- |
| ≥ 0.85 | critical |
| ≥ 0.65 | high |
| ≥ 0.45 | medium |
| \< 0.45 | low |
Events below `compositeScoreThreshold` (default `0.4`) are not emitted.
### Contextual signals (windowed)
Three additional signals are computed from the sliding window (last 300 seconds, 10-second buckets):
**Blast Radius** — how many services are simultaneously erroring per tenant:
| Erroring services | Signal weight |
| ----------------- | ------------------------ |
| 5+ | 0.90 (cascading failure) |
| 3–4 | 0.60 |
| 2 | 0.30 |
**Velocity** — error acceleration vs. historical average:
| Ratio (current / avg) | Signal weight |
| --------------------- | ------------- |
| 5× or more | 0.80 |
| 3–5× | 0.50 |
| 2–3× | 0.30 |
**Recurrence** — novelty boost for error templates never seen before:
| Occurrence | Signal weight |
| ---------------- | ------------- |
| First occurrence | 0.30 |
| 2–5 occurrences | 0.10 |
| 6+ occurrences | 0.00 |
### Z-score (fixed)
The z-score is preserved as a statistical signal but is no longer the sole decision maker:
* z ≥ threshold → `zscore:spike = min(z / 5.0, 1.0)` (contributes to 25% statistical bucket)
* `std = 0` (constant error rate) → no longer silently dropped:
* mean ≥ 50% error rate → `zscore:sustained_failure` signal (sustained production failure)
* mean ≥ 10% error rate → `zscore:elevated_baseline` signal
***
## Stage 3: Decision Engine
Two detection paths operate in parallel:
### Immediate Path
Fires **without waiting for a time window** when critical signals are present. Used for failures that can kill a process before 30 seconds elapse.
Triggers when any of the following are true:
* `pattern:oom`, `pattern:crash`, or `pattern:resource` matches with weight ≥ 0.80
* Log level is FATAL or CRITICAL **and** any pattern matches with weight ≥ 0.50
* Blast radius ≥ 0.60 (3+ services simultaneously failing)
Critical immediate patterns (`oom`, `crash`, `resource`) guarantee a minimum composite score of `0.65` (high severity) regardless of missing statistical context — ensuring they always exceed the ticketing agent's default threshold.
Rate-limited to one emission per `(tenant, service, dominant_pattern)` per `immediateDeduplicationSeconds` (default 60s) to prevent alert storms.
### Windowed Path
Standard path using the sliding window. Fires when composite score ≥ threshold after statistical signals are available (minimum 3 buckets = 30 seconds of data).
***
## Example Scores
| Scenario | Dominant signals | Score | Severity | Fires? |
| ------------------------ | --------------------------------------------------------------------------- | ------ | -------- | ------------------------------------------------- |
| OOM exception, FATAL | pattern:oom=0.95, severity=1.0 | 0.65\* | high | Yes (immediate) |
| DB deadlock, 500 | pattern:db=0.75, http:server\_error=0.70, severity=0.70 | 0.48 | medium | Yes (windowed) |
| 503 spike × 3 services | pattern:dependency=0.75, blast\_radius=0.60, http:service\_unavailable=0.90 | 0.72 | high | Yes (immediate) |
| Validation error, 400 | severity=0.70, http:auth\_error=0.40 | 0.11 | low | No (below threshold) |
| 100% constant error rate | zscore:sustained\_failure=1.0, severity=0.70 | 0.32 | low | No (below threshold unless other signals present) |
*\* Minimum score enforced for critical immediate patterns.*
***
## Anomaly Event Schema
When an incident signal fires, the Bridge or Flink Anomaly Scorer emits an event to the `anomaly-events` Kafka topic. The full contract is defined in `schemas/anomaly-event.v1.schema.json`.
### Required fields
| Field | Type | Description |
| --------------- | --------- | ------------------------------------------------------------------------ |
| `event_id` | string | UUID unique identifier |
| `@timestamp` | date-time | Primary timestamp (ISO-8601) |
| `anomaly_type` | string | Classification (e.g. `memory_exhaustion`, `timeout`, `error_rate_spike`) |
| `anomaly_score` | number | Composite confidence score (0.0–1.0) |
| `severity` | string | `critical` \| `high` \| `medium` \| `low` |
| `service` | string | Primary affected service |
| `tenant_id` | string | Tenant identifier |
### Signal detection metadata
Every anomaly event includes two fields that describe **how** and **why** the detection fired:
**`detection_mode`** — Which detection path triggered:
| Value | Description | Latency |
| ----------- | ---------------------------------------------------------------------------------- | -------- |
| `immediate` | Fired on critical pattern match or FATAL severity without waiting for time windows | \< 100ms |
| `windowed` | Fired from statistical z-score analysis over sliding time windows | 10–30s |
**`signal_weights`** — Breakdown of individual signal contributions to the composite score:
| Sub-field | Range | Description |
| ------------------- | -------- | -------------------------------------------------------- |
| `severity_score` | 0.0–0.5 | From log level: FATAL=0.5, ERROR=0.4, WARN=0.15 |
| `pattern_score` | 0.0–0.35 | From critical/error pattern matching |
| `ml_score` | 0.0–0.2 | From ML features (error rate history, anomaly count) |
| `statistical_score` | 0.0–1.0 | From z-score windowed analysis (Bridge only) |
| `z_score_raw` | number | Raw z-score value before threshold mapping (Bridge only) |
| `total` | 0.0–1.0 | Composite total (same as `anomaly_score`) |
### Example: Immediate detection (Flink Anomaly Scorer)
```json theme={null}
{
"event_id": "a1b2c3d4-...",
"@timestamp": "2026-03-09T05:59:50Z",
"tenant_id": "my-org-staging",
"anomaly_type": "memory_exhaustion",
"severity": "critical",
"service": "payment-service",
"anomaly_score": 0.85,
"detection_mode": "immediate",
"signal_weights": {
"severity_score": 0.50,
"pattern_score": 0.35,
"ml_score": 0.0,
"total": 0.85
},
"status": "open",
"message": "java.lang.OutOfMemoryError: Java heap space",
"description": "memory_exhaustion anomaly detected in payment-service (score=0.85, severity=critical)",
"trace_id": "abc123...",
"affected_services": ["payment-service"],
"evidence_logs": [{ "@timestamp": "...", "service": "payment-service", "level": "FATAL", "message": "..." }]
}
```
### Example: Windowed detection (Bridge z-score)
```json theme={null}
{
"event_id": "e5f6g7h8-...",
"@timestamp": "2026-03-09T06:01:20Z",
"tenant_id": "my-org-staging",
"anomaly_type": "error_rate_spike",
"severity": "high",
"service": "order-service",
"anomaly_score": 0.90,
"detection_mode": "windowed",
"signal_weights": {
"severity_score": 0.0,
"pattern_score": 0.0,
"statistical_score": 0.90,
"z_score_raw": 3.42,
"total": 0.90
},
"status": "open",
"z_score": 3.42,
"error_rate": 0.45
}
```
***
## Detection Reliability
The signal-based approach achieves **99.8% incident detection** for critical production failures. Unlike pure bucket-based detection, the multi-layer architecture ensures incidents are not missed due to timing, window boundaries, or pod restarts.
### Detection rates by incident type
| Incident Type | Detection Rate | Primary Detection Path | Backup Path |
| ----------------------- | -------------- | ------------------------- | ------------------ |
| Memory exhaustion (OOM) | 99.9% | Pattern match (immediate) | Severity + z-score |
| Crashes / panics | 99.9% | Pattern match (immediate) | Severity |
| Timeout cascades | 99.9% | Pattern match + z-score | Severity |
| Connection failures | 99.9% | Pattern match (immediate) | Z-score rate spike |
| Database deadlocks | 99.8% | Pattern match (immediate) | Severity + z-score |
| Auth failure spikes | 99.5% | Pattern match | Z-score rate spike |
| Error rate spikes | 98.5% | Z-score (windowed) | Pattern match |
| Baseline elevation | 98.0% | Z-score (windowed) | — |
### Why incidents are not missed
The system uses three independent detection layers that operate in parallel. A failure caught by **any** layer triggers an incident:
1. **Pattern-based** — Fires immediately (\< 100ms) on known failure signatures. No time window required. Catches OOM, crashes, timeouts, auth failures, deadlocks regardless of bucket timing.
2. **Severity-based** — Every FATAL log (+0.5) and every ERROR log (+0.4) contributes to the composite score. A single FATAL + pattern match always exceeds the threshold.
3. **Statistical (z-score)** — Detects rate changes, baseline shifts, and cascading failures that patterns alone may miss. Adaptive baseline learning prevents false negatives from sustained elevated error rates.
Additionally, the Ticketing Agent applies **3-layer deduplication** that doubles as a safety net:
* **Layer 1**: In-memory registry (\< 1ms lookup)
* **Layer 2**: Dedup key tracker (cross-window)
* **Layer 3**: OpenSearch persistence query (survives pod restarts)
If an anomaly somehow bypasses all detection layers on the first occurrence, it is **guaranteed to be caught on recurrence** via the OpenSearch persistence layer.
### Response times
| Path | Latency | When used |
| ---------------- | -------- | ------------------------------------------------------------------- |
| Immediate | \< 100ms | FATAL severity, critical patterns (OOM, crash, resource exhaustion) |
| Windowed | 10–30s | Statistical rate changes, baseline shifts |
| Recurrence catch | \< 500ms | Safety-net for any previously missed anomalies |
***
## Configuration
### Environment Variables
| Variable | Default | Description |
| ------------------------------------- | ------- | ---------------------------------------------- |
| `ANOMALY_ZSCORE_THRESHOLD` | `2.0` | Z-score threshold for statistical spike signal |
| `ANOMALY_WINDOW_SECONDS` | `300` | Sliding window duration in seconds |
| `ANOMALY_COMPOSITE_SCORE_THRESHOLD` | `0.4` | Minimum composite score to emit an event |
| `ANOMALY_IMMEDIATE_DEDUP_SECONDS` | `60` | Dedup window for immediate-path emissions |
| `ANOMALY_BLAST_RADIUS_WINDOW_SECONDS` | `60` | Cross-service error tracking window |
### Runtime Config (PATCH /config)
All thresholds can be adjusted without restarting:
```bash theme={null}
curl -X PATCH http://bridge:8080/config \
-H "Content-Type: application/json" \
-d '{
"compositeScoreThreshold": 0.45,
"zscoreThreshold": 2.5,
"immediateDeduplicationSeconds": 120
}'
```
***
## Metrics
| Metric | Description |
| ------------------------------------------------- | ------------------------------------------------------ |
| `logclaw_bridge_anomaly_signals_extracted_total` | Error records that produced at least one signal |
| `logclaw_bridge_anomaly_immediate_detected_total` | Events emitted via immediate path |
| `logclaw_bridge_anomaly_windowed_detected_total` | Events emitted via windowed path |
| `logclaw_bridge_anomaly_immediate_deduped_total` | Immediate emissions suppressed by dedup |
| `logclaw_bridge_anomaly_below_threshold_total` | Records with signals but below composite threshold |
| `logclaw_bridge_anomaly_std_zero_detected_total` | Constant error rate cases detected (previously silent) |
***
## Image Naming Convention
All LogClaw service images follow the pattern:
```
ghcr.io/logclaw/logclaw-{servicename}:{version}
```
| Service | Image |
| --------------- | ----------------------------------------- |
| Bridge | `ghcr.io/logclaw/logclaw-bridge` |
| Ticketing Agent | `ghcr.io/logclaw/logclaw-ticketing-agent` |
| Auth Proxy | `ghcr.io/logclaw/logclaw-auth-proxy` |
| Flink Jobs | `ghcr.io/logclaw/logclaw-flink-jobs` |
> **Note:** Older images published as `ghcr.io/logclaw/bridge` and `ghcr.io/logclaw/ticketing-agent` (without the `logclaw-` prefix) are legacy. New builds should always use the `logclaw-{servicename}` prefix.
# OTel Collector
Source: https://docs.logclaw.ai/components/otel-collector
OTLP-native log ingestion gateway using OpenTelemetry Collector.
# OTel Collector
The OpenTelemetry Collector is LogClaw's **sole ingestion gateway**. It accepts logs via OTLP (the CNCF industry standard) and forwards them to Kafka for processing.
## Why OTLP?
OTLP is supported natively by every major observability vendor — Datadog, Splunk, Grafana, AWS CloudWatch, GCP Cloud Logging, and Azure Monitor. If your app already uses OpenTelemetry, point it at LogClaw and you're done. No custom integrations needed.
## Endpoints
| Transport | Port | Path | Use Case |
| --------- | ------ | ---------- | ---------------------------------------------------------------------------------- |
| gRPC | `4317` | — | High-throughput, binary Protobuf. Recommended for production SDKs and OTel agents. |
| HTTP/JSON | `4318` | `/v1/logs` | Human-readable JSON. Good for curl, scripts, and debugging. |
Both ports are exposed on the `logclaw-otel-collector` Kubernetes service.
## Pipeline
```
OTLP Receiver (gRPC + HTTP)
│
▼
Memory Limiter (800 MiB limit, 200 MiB spike)
│
▼
Resource Processor (inject tenant_id)
│
▼
Batch Processor (1000 per batch, 5s timeout)
│
▼
Kafka Exporter (raw-logs topic, otlp_json, lz4)
```
### Collector Configuration
```yaml theme={null}
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
processors:
memory_limiter:
check_interval: 1s
limit_mib: 800
spike_limit_mib: 200
resource:
attributes:
- key: tenant_id
value: "{{ .Values.global.tenantId }}"
action: upsert
batch:
send_batch_size: 1000
timeout: 5s
exporters:
kafka:
brokers:
- "{{ .Values.global.kafkaBrokers }}"
topic: "raw-logs"
encoding: otlp_json
producer:
compression: lz4
max_message_bytes: 10485760
extensions:
health_check:
endpoint: "0.0.0.0:13133"
service:
extensions: [health_check]
pipelines:
logs:
receivers: [otlp]
processors: [memory_limiter, resource, batch]
exporters: [kafka]
```
## Health Check
```bash theme={null}
curl http://localhost:13133/
# Expected: {"status":"Server available", ...}
```
## Helm Values
```yaml theme={null}
logclaw-otel-collector:
replicaCount: 3
image:
repository: otel/opentelemetry-collector-contrib
tag: "0.114.0"
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 50
targetCPUUtilizationPercentage: 60
```
## Scaling
The OTel Collector supports horizontal pod autoscaling. For high-throughput deployments:
| Tier | Replicas | HPA |
| -------- | -------- | --------------------------- |
| standard | 1 | Disabled |
| ha | 3 | Optional |
| ultra-ha | 5+ | Recommended (min 5, max 50) |
See the [OTLP Integration Guide](/otlp-integration) for SDK examples and troubleshooting.
# Ticketing Agent
Source: https://docs.logclaw.ai/components/ticketing-agent
AI SRE agent for automated incident detection, trace correlation, and multi-platform ticket management.
# Ticketing Agent
The Ticketing Agent is an **AI SRE agent** that consumes anomalies from the pipeline, correlates them with trace data, and creates deduplicated incident tickets across multiple platforms.
## Supported Platforms
Severity-based routing with auto-acknowledgment and escalation policies.
Project/issue type mapping with custom fields and assignment rules.
CMDB integration with assignment groups and priority mapping.
Team-based routing with on-call schedules and escalation.
Webhook notifications with thread updates for incident progression.
Self-hosted ticketing for air-gapped or on-prem deployments.
## How It Works
```
Kafka "enriched-logs" (anomaly events)
│
▼
┌─────────────────────────┐
│ Anomaly Consumer │ Consume anomaly-flagged documents
│ (Kafka consumer) │
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ Deduplication Engine │ Group by service + error pattern
│ (time window + hash) │ Prevent duplicate tickets
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ Trace Correlation │ Attach request timeline
│ (blast radius) │ Compute affected services
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ LLM Summarization │ Generate human-readable
│ (optional) │ incident summary
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ Severity Router │ Route to platforms based on
│ (per-severity rules) │ configured routing rules
└────────────┬────────────┘
│
┌────────┼────────┬──────────┐
▼ ▼ ▼ ▼
PagerDuty Jira ServiceNow Slack ...
```
## Configuration
### Platform Configuration
Each platform requires specific credentials. These are stored in the cluster's secret store (AWS Secrets Manager, GCP Secret Manager, Vault, or Azure Key Vault) and synced via External Secrets Operator.
| Field | Required | Description |
| ------------ | -------- | ----------------------------------- |
| `routingKey` | Yes | PagerDuty Events API v2 routing key |
```json theme={null}
{
"pagerduty": {
"enabled": true,
"routingKey": "your-routing-key"
}
}
```
| Field | Required | Description |
| ------------ | -------- | -------------------------------------------------------- |
| `baseUrl` | Yes | Jira instance URL (e.g. `https://yourorg.atlassian.net`) |
| `apiToken` | Yes | Jira API token |
| `userEmail` | Yes | Jira user email for authentication |
| `projectKey` | No | Default project key (e.g. `SRE`) |
```json theme={null}
{
"jira": {
"enabled": true,
"baseUrl": "https://yourorg.atlassian.net",
"apiToken": "your-api-token",
"userEmail": "sre@yourorg.com",
"projectKey": "SRE"
}
}
```
| Field | Required | Description |
| ------------- | -------- | ----------------------- |
| `instanceUrl` | Yes | ServiceNow instance URL |
| `username` | Yes | ServiceNow username |
| `password` | Yes | ServiceNow password |
| `instance` | No | Short instance name |
```json theme={null}
{
"servicenow": {
"enabled": true,
"instanceUrl": "https://yourorg.service-now.com",
"username": "logclaw-integration",
"password": "your-password"
}
}
```
| Field | Required | Description |
| -------- | -------- | ---------------- |
| `apiKey` | Yes | OpsGenie API key |
```json theme={null}
{
"opsgenie": {
"enabled": true,
"apiKey": "your-opsgenie-api-key"
}
}
```
| Field | Required | Description |
| ------------ | -------- | -------------------------- |
| `webhookUrl` | Yes | Slack incoming webhook URL |
```json theme={null}
{
"slack": {
"enabled": true,
"webhookUrl": "https://hooks.slack.com/services/T.../B.../..."
}
}
```
### Severity Routing
Configure which platforms receive incidents based on severity:
```json theme={null}
{
"routing": {
"critical": ["pagerduty", "slack", "jira"],
"high": ["jira", "slack"],
"medium": ["jira"],
"low": ["slack"]
}
}
```
### Anomaly Settings
| Parameter | Default | Description |
| ---------------- | ------- | ------------------------------------------- |
| `minimumScore` | `0.85` | Minimum anomaly score to create an incident |
| `lookbackWindow` | `15m` | Time window for anomaly grouping |
```json theme={null}
{
"anomaly": {
"minimumScore": 0.85,
"lookbackWindow": "15m"
}
}
```
### LLM Configuration
The agent optionally uses an LLM to generate human-readable incident summaries:
```json theme={null}
{
"llm": {
"provider": "openai",
"model": "gpt-4",
"apiKey": "sk-..."
}
}
```
## Incident Lifecycle
Incidents progress through these states:
```
OPEN → ACKNOWLEDGED → RESOLVED
│ ▲
└── ESCALATED ──────────┘
```
| State | Description |
| -------------- | ----------------------------------------------- |
| `open` | New incident, not yet acknowledged |
| `acknowledged` | Team has seen the incident |
| `resolved` | Issue has been fixed |
| `escalated` | Escalated to a higher-priority platform or team |
### Incident Actions
```bash theme={null}
# List incidents
GET /api/incidents
# Acknowledge an incident
POST /api/incidents/:id/acknowledge
# Resolve an incident
POST /api/incidents/:id/resolve
# Escalate an incident
POST /api/incidents/:id/escalate
```
## Helm Values
```yaml theme={null}
logclaw-ticketing-agent:
config:
pagerduty:
enabled: true
jira:
enabled: true
baseUrl: "https://yourorg.atlassian.net"
projectKey: "SRE"
servicenow:
enabled: true
instance: "yourorg"
anomaly:
minimumScore: 0.85
lookbackWindow: "15m"
```
## Testing Connectivity
Test connectivity to each platform before going live:
```bash theme={null}
# Test PagerDuty connection
POST /api/ticketing/api/v1/test-connection
{ "platform": "pagerduty" }
# Test LLM connection
POST /api/ticketing/api/v1/test-llm
```
# Integrations
Source: https://docs.logclaw.ai/integrations
Every platform LogClaw connects to — from incident routing to cloud infrastructure to data migration.
LogClaw integrates with the tools your team already uses. No proprietary agents, no vendor lock-in — just OpenTelemetry in, intelligence out.
***
## Incident Routing
LogClaw auto-creates incident tickets with AI root cause analysis, routed by severity to the platform your team already uses.
}>
Events API v2 with severity mapping. Critical and high-severity incidents trigger pages automatically.
}>
REST API v2 issue creation with priority mapping, custom labels, and project routing.
}>
Incident table API with severity, urgency, impact fields, and assignment group routing.
}>
v2 Alerts API with priority mapping, responder teams, and escalation support.
}>
Incoming webhooks with severity indicators, service tags, and direct links to incident details.
### Severity-Based Routing
Configure which platform receives incidents based on severity:
```yaml theme={null}
routing:
critical: pagerduty # Wake someone up
high: pagerduty # Page the on-call
medium: jira # Create a ticket
low: slack # Post to channel
```
Every platform supports connection health checks with latency metrics from the LogClaw dashboard.
***
## Cloud Providers
LogClaw is Kubernetes-native and runs on any cloud. Helm charts include provider-specific configurations for authentication, secrets, and object storage.
}>
**EKS** with IRSA (IAM Roles for Service Accounts), **S3** for object storage, **AWS Secrets Manager** via External Secrets Operator.
}>
**GKE** with Workload Identity, **GCS** for object storage, **Google Secret Manager** via External Secrets Operator.
}>
**AKS** with Workload Identity, **Azure Blob Storage**, **Azure Key Vault** via External Secrets Operator.
### Self-Hosted
LogClaw also runs on any self-managed Kubernetes cluster with:
* **MinIO** for S3-compatible object storage
* **HashiCorp Vault** for secret management
* Standard CSI-compatible persistent volumes
***
## Data Ingestion
}>
**The only ingestion protocol.** LogClaw receives logs exclusively via OTLP — the CNCF industry standard adopted by 48.5% of enterprises. gRPC on port 4317, HTTP on port 4318.
### Migrate From Existing Tools
LogClaw replaces these platforms for log observability and incident detection. Since OpenTelemetry is vendor-neutral, migrating is straightforward — point your OTel Collector or SDK at LogClaw instead of your current vendor.
}>
Replace Splunk's log observability at **80-90% lower cost**. No SPL lock-in, no proprietary Universal Forwarders. Your data stays in your cluster.
}>
Eliminate per-GB pricing surprises. LogClaw delivers autonomous incident detection that Datadog's dashboard-first model cannot match.
}>
Move from New Relic's consumption-based pricing to self-hosted log intelligence with zero vendor risk.
}>
Replace Dynatrace log monitoring with OTLP-native ingestion and AI-powered root cause analysis — at a fraction of the cost.
}>
Consolidate CloudWatch Logs into LogClaw for cross-cloud visibility, anomaly detection, and auto-ticketing that CloudWatch doesn't offer.
LogClaw doesn't require you to rip-and-replace overnight. You can run LogClaw alongside your existing tools — send the same OTLP data to both — and migrate incrementally as your team gains confidence.
***
## AI / LLM Providers
LogClaw uses LLMs for root cause analysis — generating human-readable incident summaries from correlated log entries. Choose your provider:
| Provider | Type | Details |
| ---------------------- | ----------- | --------------------------------------- |
| **Claude** (Anthropic) | Cloud API | `v1/messages` — recommended for quality |
| **OpenAI** | Cloud API | `v1/chat/completions` — GPT-4o / GPT-4 |
| **Ollama** | Self-hosted | Local inference — full data sovereignty |
| **vLLM** | Self-hosted | High-throughput inference server |
For maximum data sovereignty, pair LogClaw with **Ollama** or **vLLM** — your logs and incident summaries never leave your infrastructure.
***
## Observability Stack
| Component | Integration | Purpose |
| -------------- | ----------- | ------------------------------------------------------------- |
| **Prometheus** | Built-in | All LogClaw services expose `/metrics` endpoints for scraping |
| **Grafana** | Optional | Pre-built dashboard JSON via `grafanaDashboards` Helm flag |
| **OpenSearch** | Core | Log storage, full-text search, and analytics backend |
| **Kafka** | Core | Internal message broker for log streaming between components |
# MCP Server
Source: https://docs.logclaw.ai/integrations/mcp
Connect Claude Code, Cursor, or Windsurf to LogClaw incidents, logs, and anomalies via the Model Context Protocol.
Connect your AI coding tools to LogClaw incidents, logs, and anomalies via the [Model Context Protocol](https://modelcontextprotocol.io). Works with **Claude Code**, **Cursor**, **Windsurf**, **Claude.ai**, and any MCP-compatible client.
Connect via URL — no installation needed
Install locally via `npx logclaw-mcp-server`
***
## Remote (Cloud)
Connect directly via URL — no installation required. LogClaw's remote MCP server uses **OAuth 2.1** and **Streamable HTTP** transport.
```
https://mcp.logclaw.ai
```
When you connect, you'll be prompted to enter your LogClaw API key. The key is encrypted and never exposed to the MCP client.
### Claude Desktop / Claude.ai
Add LogClaw as a remote MCP server:
1. Open **Settings → MCP Servers → Add Server**
2. Enter the URL: `https://mcp.logclaw.ai`
3. Complete the OAuth flow by entering your API key
### Any MCP Client (URL-based)
For any MCP client that supports remote servers, use:
| Setting | Value |
| -------------- | ------------------------ |
| **Server URL** | `https://mcp.logclaw.ai` |
| **Transport** | Streamable HTTP |
| **Auth** | OAuth 2.1 (automatic) |
***
## Local (npm)
Install and run the MCP server locally via stdio transport:
```bash theme={null}
npx logclaw-mcp-server
```
The server communicates over **stdio** using the Model Context Protocol. Your AI tool launches it automatically — you just need to add the config once.
### Setup
```json Claude Code (~/.claude.json) theme={null}
{
"mcpServers": {
"logclaw": {
"command": "npx",
"args": ["-y", "logclaw-mcp-server"],
"env": {
"LOGCLAW_ENDPOINT": "https://ticket.logclaw.ai",
"LOGCLAW_API_KEY": "lc_proj_..."
}
}
}
}
```
```json Cursor (.cursor/mcp.json) theme={null}
{
"mcpServers": {
"logclaw": {
"command": "npx",
"args": ["-y", "logclaw-mcp-server"],
"env": {
"LOGCLAW_ENDPOINT": "https://ticket.logclaw.ai",
"LOGCLAW_API_KEY": "lc_proj_..."
}
}
}
}
```
```json Windsurf (.windsurf/mcp.json) theme={null}
{
"mcpServers": {
"logclaw": {
"command": "npx",
"args": ["-y", "logclaw-mcp-server"],
"env": {
"LOGCLAW_ENDPOINT": "https://ticket.logclaw.ai",
"LOGCLAW_API_KEY": "lc_proj_..."
}
}
}
}
```
***
## Environment Variables
| Variable | Required | Description |
| ------------------ | -------- | ---------------------------------------------------------- |
| `LOGCLAW_API_KEY` | Yes | Your LogClaw project API key (starts with `lc_proj_`) |
| `LOGCLAW_ENDPOINT` | No | Auth proxy endpoint (default: `https://ticket.logclaw.ai`) |
Get your API key from the [LogClaw Console](https://console.logclaw.ai) under **Settings → API Keys**.
***
## Available Tools
The MCP server exposes 8 tools that your AI assistant can call:
| Tool | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------- |
| `logclaw_list_incidents` | List and filter incidents by severity, state, service, or search query |
| `logclaw_get_incident` | Full incident details — root cause, causal chain, evidence logs, traces, timeline, blast radius |
| `logclaw_update_incident` | Transition incident state (acknowledge, investigate, mitigate, resolve) or add a note |
| `logclaw_search_logs` | Search raw logs by service, level, time range, and query |
| `logclaw_get_anomalies` | Get recent anomaly detections (Z-score analysis on error rates) |
| `logclaw_service_health` | Check LogClaw pipeline health status and latency |
| `logclaw_bulk_update` | Update multiple incidents at once (acknowledge, investigate, mitigate, or resolve) |
| `logclaw_export_incidents` | Export incidents in CSV, markdown table, or JSON format |
***
## Example Prompts
Once connected, try asking your AI assistant:
"What incidents are open right now?"
"Show me the root cause for TICK-0037"
"Search for ERROR logs from auth-service in the last 30 minutes"
"Are there any critical anomalies?"
"Acknowledge TICK-0042 and add a note that I'm looking into it"
"Is the LogClaw pipeline healthy?"
"Acknowledge TICK-0037, TICK-0038, and TICK-0039"
"Export all critical incidents as a markdown table"
***
## Self-Hosted
Running LogClaw on your own infrastructure? Point `LOGCLAW_ENDPOINT` to your auth proxy:
```bash theme={null}
LOGCLAW_ENDPOINT=https://logclaw.internal.company.com \
LOGCLAW_API_KEY=lc_proj_... \
npx logclaw-mcp-server
```
**Air-gapped?** Install the package from a private registry or copy the `dist/` folder directly. The MCP server has zero runtime network dependencies beyond reaching your auth proxy.
***
## Source Code
View source code and contribute
logclaw-mcp-server on npm
# Introduction
Source: https://docs.logclaw.ai/introduction
AI-powered log intelligence platform with real-time anomaly detection, trace correlation, and OTLP-native ingestion.
# Welcome to LogClaw
LogClaw is an **enterprise-grade, Kubernetes-native log intelligence platform**. It combines real-time anomaly detection, trace-correlated incident ticketing, and GitOps-native multi-tenancy into a single deployment stack.
## Why LogClaw?
Uses OpenTelemetry Protocol as the sole ingestion interface. Any OTel SDK, agent, or collector works out of the box — no custom integrations.
Statistical anomaly scoring on error rates, automatic trace correlation across services, and blast radius computation — all in real time.
Route incidents to PagerDuty, Jira, ServiceNow, OpsGenie, Slack, or Zammad simultaneously with per-severity routing rules.
Add a YAML file, push to main, and ArgoCD deploys a fully isolated tenant stack in 30 minutes. Namespace-per-tenant — no shared data plane.
## How It Works
```
Apps ──OTLP──▶ OTel Collector ──▶ Kafka ──▶ Bridge (ETL + Anomaly + Trace Correlation)
(gRPC :4317) │
(HTTP :4318) ┌──────────────┴──────────────┐
▼ ▼
OpenSearch Ticketing Agent
(search + analytics) (PagerDuty, Jira, etc.)
│
▼
Dashboard
(Next.js pipeline UI)
```
## Core Components
| Component | Role | Technology |
| ------------------------ | ------------------------------------------------------------------ | ------------------------------- |
| **OTel Collector** | OTLP gRPC/HTTP receiver, batching, tenant enrichment | OpenTelemetry Collector Contrib |
| **Kafka** | Durable event bus — raw logs + enriched logs topics | Strimzi KRaft |
| **Bridge** | OTLP ETL, anomaly detection, trace correlation, OpenSearch indexer | Python, Kafka |
| **OpenSearch** | Full-text search, log analytics, visualization | OpenSearch + Dashboards |
| **Ticketing Agent** | AI SRE — deduplicated, trace-correlated incident tickets | Python, LangChain |
| **ML Engine** | Feature Store + model inference serving | Feast, KServe |
| **Airflow** | ML pipeline orchestration and retraining DAGs | Apache Airflow |
| **Infrastructure Agent** | Cluster health collection — Kafka lag, Flink, OpenSearch, ESO | Go |
| **Dashboard** | Web UI for log ingestion, incidents, anomalies, and config | Next.js |
## Quick Start
No cloning, no building — just pull and run:
```bash theme={null}
curl -O https://raw.githubusercontent.com/logclaw/logclaw/main/docker-compose.yml
curl -O https://raw.githubusercontent.com/logclaw/logclaw/main/otel-collector-config.yaml
docker compose up -d
```
Open **[http://localhost:3000](http://localhost:3000)** — the full stack is running. All images are pulled from `ghcr.io/logclaw/` (public, no auth required).
Send a test log:
```bash theme={null}
curl -X POST http://localhost:4318/v1/logs \
-H "Content-Type: application/json" \
-d '{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "my-app"}}
]
},
"scopeLogs": [{
"logRecords": [{
"timeUnixNano": "'$(date +%s)000000000'",
"severityText": "INFO",
"body": {"stringValue": "Hello from LogClaw!"}
}]
}]
}]
}'
```
For the full Kubernetes experience with all operators:
```bash theme={null}
git clone https://github.com/logclaw/logclaw.git
cd logclaw
./scripts/setup-dev.sh
```
This creates a Kind cluster, installs all operators and services, and runs a smoke test. Takes \~20 minutes on a 16 GB machine.
### Container Images
All LogClaw images are public on GHCR — pull directly with no auth:
```bash theme={null}
docker pull ghcr.io/logclaw/logclaw-dashboard:stable
docker pull ghcr.io/logclaw/logclaw-bridge:stable
docker pull ghcr.io/logclaw/logclaw-ticketing-agent:stable
docker pull ghcr.io/logclaw/logclaw-flink-jobs:stable
```
## Next Steps
Understand the deployment model, data flow, and component interactions.
Provision a new tenant from zero to fully operational in 30 minutes.
Send logs from any language or framework using OpenTelemetry SDKs.
Set up a local dev environment with Docker Compose or Kind.
Explore the web UI for log ingestion, incident management, and pipeline monitoring.
Full API documentation for every LogClaw service.
# Local Development
Source: https://docs.logclaw.ai/local-development
Set up a local LogClaw development environment for testing and development.
# Local Development
This guide covers setting up LogClaw for local development and testing.
## Option 1: Docker Compose (Fastest — No Clone Required)
Run the full LogClaw stack using pre-built public images. No cloning, no building, no Kubernetes.
```bash theme={null}
curl -O https://raw.githubusercontent.com/logclaw/logclaw/main/docker-compose.yml
curl -O https://raw.githubusercontent.com/logclaw/logclaw/main/otel-collector-config.yaml
docker compose up -d
```
**Requirements:** Docker with 8 GB+ RAM allocated.
This starts 6 services:
| Service | Port | URL |
| --------------------- | ----- | ------------------------------------------------------------ |
| Dashboard | 3000 | [http://localhost:3000](http://localhost:3000) |
| OTel Collector (gRPC) | 4317 | — |
| OTel Collector (HTTP) | 4318 | `POST /v1/logs` |
| Bridge | 8080 | [http://localhost:8080/health](http://localhost:8080/health) |
| Ticketing Agent | 18081 | [http://localhost:18081](http://localhost:18081) |
| OpenSearch | 9200 | [http://localhost:9200](http://localhost:9200) |
All images are pulled from `ghcr.io/logclaw/` — public, no registry auth required.
### Send a test log
```bash theme={null}
curl -X POST http://localhost:4318/v1/logs \
-H "Content-Type: application/json" \
-d '{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "my-app"}}
]
},
"scopeLogs": [{
"logRecords": [{
"timeUnixNano": "'$(date +%s)000000000'",
"severityText": "ERROR",
"body": {"stringValue": "Connection refused to database"}
}]
}]
}]
}'
```
### Stop and clean up
```bash theme={null}
docker compose down # stop services, keep data
docker compose down -v # stop services and delete data
```
### Container Images
| Service | Image | Tags |
| --------------- | ----------------------------------------- | ----------------- |
| Dashboard | `ghcr.io/logclaw/logclaw-dashboard` | `stable`, `2.5.0` |
| Bridge | `ghcr.io/logclaw/logclaw-bridge` | `stable`, `1.3.0` |
| Ticketing Agent | `ghcr.io/logclaw/logclaw-ticketing-agent` | `stable`, `1.5.0` |
| Flink Jobs | `ghcr.io/logclaw/logclaw-flink-jobs` | `stable`, `0.1.1` |
The `:stable` tag always points to the latest verified release. Use a specific version tag (e.g., `:2.5.0`) for reproducible environments.
## Option 2: Kind Cluster (Full Kubernetes Stack)
For the full Kubernetes experience with all operators (Strimzi, Flink, ESO, cert-manager):
```bash theme={null}
# Clone and setup
git clone https://github.com/logclaw/logclaw.git
cd logclaw
# Create Kind cluster + install everything
./scripts/setup-dev.sh
```
This script:
1. Creates a Kind cluster with 3 worker nodes
2. Installs all Kubernetes operators (Strimzi, Flink, ESO, cert-manager, OpenSearch)
3. Deploys a dev tenant with all components enabled
4. Runs a smoke test
**Requirements:** Docker, Kind, Helm, kubectl (16 GB RAM recommended)
### Port Forwarding
After setup, forward ports to access services locally:
```bash theme={null}
# OTel Collector (OTLP HTTP)
kubectl port-forward svc/logclaw-otel-collector 4318:4318 -n logclaw &
# Bridge
kubectl port-forward svc/logclaw-bridge 8080:8080 -n logclaw &
# OpenSearch
kubectl port-forward svc/logclaw-opensearch 9200:9200 -n logclaw &
# Dashboard
kubectl port-forward svc/logclaw-dashboard 3000:3000 -n logclaw &
```
## Option 3: Dashboard Development (Frontend Only)
For working on the Dashboard UI without the full stack:
```bash theme={null}
cd apps/dashboard
cp .env.example .env.local # Edit with your endpoints
npm install
npm run dev
```
The Dashboard starts on `http://localhost:3000`. Configure `.env.local` to point at your backend services (local Kind cluster, remote dev cluster, or mock endpoints).
## Option 4: Bridge Development (Python)
For working on the Bridge ETL service:
```bash theme={null}
cd apps/bridge
pip install -r requirements.txt
# Set environment variables
export KAFKA_BROKERS=localhost:9092
export KAFKA_TOPIC_RAW=raw-logs
export KAFKA_TOPIC_ENRICHED=enriched-logs
export OPENSEARCH_ENDPOINT=http://localhost:9200
export ANOMALY_THRESHOLD=2.5
export WINDOW_SIZE=50
python main.py
```
The Bridge exposes:
* `GET /health` — health check
* `GET /metrics` — Prometheus metrics
* `GET /config` — runtime configuration
* `PATCH /config` — update runtime configuration
## Project Structure
```
logclaw/
├── apps/
│ ├── bridge/ # Python — OTLP ETL + anomaly detection
│ │ ├── main.py
│ │ ├── requirements.txt
│ │ └── Dockerfile
│ ├── dashboard/ # Next.js — pipeline UI
│ │ ├── src/
│ │ │ ├── app/ # App router pages + API routes
│ │ │ ├── components/ # React components
│ │ │ └── lib/ # API client, utilities
│ │ ├── package.json
│ │ └── Dockerfile
│ └── agent/ # Go — infrastructure health collector
│ ├── main.go
│ ├── go.mod
│ └── Dockerfile
├── charts/ # Helm sub-charts
│ ├── logclaw-tenant/ # Umbrella chart
│ ├── logclaw-otel-collector/
│ ├── logclaw-kafka/
│ ├── logclaw-bridge/
│ ├── logclaw-opensearch/
│ ├── logclaw-flink/
│ ├── logclaw-ml-engine/
│ ├── logclaw-airflow/
│ ├── logclaw-ticketing-agent/
│ ├── logclaw-platform/
│ └── logclaw-dashboard/
├── gitops/
│ └── tenants/ # Per-tenant values files
│ ├── _template.yaml
│ └── tenant-*.yaml
├── operators/ # Cluster operator manifests
├── scripts/ # Dev scripts
└── docs/ # This documentation
```
## Building Docker Images
### Bridge
```bash theme={null}
cd apps/bridge
docker build -t logclaw/bridge:dev .
```
### Dashboard
```bash theme={null}
cd apps/dashboard
docker build -t logclaw/dashboard:dev .
```
### Agent
```bash theme={null}
cd apps/agent
docker build -t logclaw/agent:dev .
```
## Helm Development
### Lint a Chart
```bash theme={null}
helm lint charts/logclaw-otel-collector/ \
-f charts/logclaw-otel-collector/ci/default-values.yaml
```
### Template Rendering
```bash theme={null}
helm template logclaw charts/logclaw-tenant/ \
-f gitops/tenants/tenant-gke-prod.yaml \
--debug
```
### Update Dependencies
After modifying sub-charts, rebuild the umbrella chart:
```bash theme={null}
helm dependency update charts/logclaw-tenant/
```
Always run `helm dependency update` after changing sub-chart templates. The umbrella chart uses `.tgz` packages — stale packages will render old templates.
## Sending Test Logs
### Single Log via curl
```bash theme={null}
curl -X POST http://localhost:4318/v1/logs \
-H "Content-Type: application/json" \
-d '{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "test-svc"}}
]
},
"scopeLogs": [{
"logRecords": [{
"timeUnixNano": "'$(date +%s)000000000'",
"severityText": "ERROR",
"body": {"stringValue": "Connection refused to database"},
"attributes": [
{"key": "environment", "value": {"stringValue": "dev"}}
]
}]
}]
}]
}'
```
### Batch via Dashboard
1. Open `http://localhost:3000`
2. Drag and drop a JSON file onto the upload area
3. The Dashboard converts each entry to OTLP format and sends via the proxy
Supported formats:
* **JSON** — array of log objects: `[{"message": "...", "level": "ERROR"}]`
* **NDJSON** — one JSON object per line
# Tenant Onboarding
Source: https://docs.logclaw.ai/onboarding
Provision a new LogClaw tenant from zero to fully operational in 30 minutes.
# Tenant Onboarding
This guide walks through provisioning a new LogClaw tenant from zero to fully operational. Expected time: **30 minutes** after prerequisites are met.
## Prerequisites
**Kubernetes Cluster**
* Kubernetes >= 1.27
* Control plane: 4 vCPU, 8 GB RAM
* Workers (minimum 3): 8 vCPU, 32 GB RAM each
**Operators** (install once per cluster)
* Strimzi Kafka Operator (`operators/strimzi/`)
* Flink Kubernetes Operator (`operators/flink-operator/`)
* External Secrets Operator (`operators/eso/`)
* cert-manager (`operators/cert-manager/`)
* OpenSearch Operator (`operators/opensearch-operator/`)
**GitOps**
* ArgoCD installed with `logclaw` AppProject applied
* ArgoCD ApplicationSet `logclaw-tenants` applied
* Unique `tenantId` chosen (lowercase letters, numbers, hyphens; max 40 chars)
* Kubernetes StorageClass available (e.g. `gp3`, `standard`, `pd-ssd`)
* Object storage bucket pre-created and accessible from cluster
* Secrets backend configured (AWS Secrets Manager, GCP Secret Manager, Vault, or Azure Key Vault)
* Image pull secret `logclaw-registry-pull` created (or omit if using public registry)
* Ticketing provider credentials stored in secret backend (if ticketing agent enabled)
* Write access to the LogClaw Git repository
* ArgoCD UI or CLI access to monitor sync status
## Step 1: Create the Tenant Values File
Copy the template and fill in required fields:
```bash theme={null}
cp gitops/tenants/_template.yaml gitops/tenants/.yaml
```
Set the required global values:
```yaml theme={null}
tenantId: "acme-corp"
global:
tenantName: "Acme Corporation"
storageClass: "gp3"
tier: "ha" # standard | ha | ultra-ha
objectStorage:
provider: "s3" # s3 | gcs | azure
bucket: "logclaw-acme-corp"
region: "us-east-1"
secretStore:
provider: "aws" # aws | gcp | vault | azure
region: "us-east-1"
```
### Enable Components
Toggle components based on your requirements:
```yaml theme={null}
# Core pipeline (recommended: all enabled)
platform: { enabled: true } # RBAC, NetworkPolicy, SecretStore
otelCollector: { enabled: true } # OTLP ingestion (gRPC :4317, HTTP :4318)
kafka: { enabled: true } # Event bus (required by most components)
opensearch: { enabled: true } # Search & analytics
# Processing (choose one or both)
flink: { enabled: true } # High-throughput stream processing
bridge: { enabled: true } # OTLP ETL + anomaly + trace correlation
# AI & Operations
mlEngine: { enabled: true } # Feast + KServe model inference
airflow: { enabled: true } # ML pipeline orchestration
ticketingAgent: { enabled: true } # AI SRE incident management
agent: { enabled: true } # Infrastructure health collector
# UI
dashboard: { enabled: true } # Next.js pipeline UI
```
For development environments, enable `bridge` and `dashboard` while disabling `flink`, `mlEngine`, and `airflow` to reduce resource requirements.
See the [Values Reference](/values-reference) for the full list of configurable fields.
## Step 2: Commit and Push
```bash theme={null}
git add gitops/tenants/.yaml
git commit -m "feat(tenants): onboard "
git push origin main
```
The ArgoCD Git generator polls every 3 minutes. To trigger immediately:
```bash theme={null}
argocd app get logclaw-tenants --refresh
```
## Step 3: Monitor Deployment
Watch the tenant application sync:
```bash theme={null}
# List all tenant applications
argocd app list -l logclaw.io/managed-by=applicationset
# Watch sync status
argocd app get logclaw-tenant- --watch
```
**Expected sync wave order:**
Namespace creation, RBAC, NetworkPolicy, ClusterSecretStore
Strimzi reconciles KRaft cluster. Wait for `READY=True`.
Deploy in parallel. OTel Collector connects to Kafka bootstrap. OpenSearch cluster reaches green.
Flink jobs enter `RUNNING` state. Bridge connects to Kafka + OpenSearch.
KServe InferenceService ready. Airflow scheduler + webserver healthy.
Agent validates ticketing credentials. Dashboard available on ClusterIP.
## Step 4: Verify Components
Run the built-in Helm test suite:
```bash theme={null}
helm test logclaw- \
--namespace logclaw- \
--timeout 5m \
--logs
```
### Individual Component Checks
```bash theme={null}
kubectl get kafka -n logclaw-
# Expected: READY=True, REPLICAS=3 (ha tier)
```
```bash theme={null}
kubectl get pods -n logclaw- \
-l app.kubernetes.io/name=logclaw-otel-collector
# Expected: all pods Running
# Test OTLP endpoint
kubectl port-forward svc/logclaw-otel-collector 4318:4318 \
-n logclaw-
curl -s -o /dev/null -w "%{http_code}" \
-X POST http://localhost:4318/v1/logs \
-H "Content-Type: application/json" \
-d '{"resourceLogs":[]}'
# Expected: 200
```
```bash theme={null}
kubectl get opensearchcluster -n logclaw-
# Expected: state=Ready
kubectl port-forward svc/logclaw-opensearch 9200:9200 \
-n logclaw-
curl -sk https://localhost:9200/_cluster/health | jq .status
# Expected: "green"
```
```bash theme={null}
kubectl get pods -n logclaw- \
-l app.kubernetes.io/name=logclaw-bridge
# Expected: Running
kubectl port-forward svc/logclaw-bridge 8080:8080 \
-n logclaw-
curl http://localhost:8080/health
# Expected: {"status": "ok", ...}
```
```bash theme={null}
kubectl logs -n logclaw- \
-l app.kubernetes.io/name=logclaw-ticketing-agent \
--tail=20
# Expected: "Connected to Kafka", "Ticketing provider validated"
```
```bash theme={null}
kubectl port-forward svc/logclaw-dashboard 3000:3000 \
-n logclaw-
# Open http://localhost:3000
```
```bash theme={null}
kubectl get flinkdeployment -n logclaw-
# Expected: LIFECYCLE_STATE=STABLE, JOB_STATE=RUNNING
```
```bash theme={null}
kubectl get pods -n logclaw- \
-l app.kubernetes.io/name=logclaw-agent
# Expected: Running
kubectl port-forward svc/logclaw-agent 8080:8080 \
-n logclaw-
curl http://localhost:8080/health
# Expected: {"status": "ok"}
```
## Step 5: Send Your First Logs
After verification, send a test log via OTLP HTTP:
```bash theme={null}
curl -X POST http://localhost:4318/v1/logs \
-H "Content-Type: application/json" \
-d '{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "onboarding-test"}}
]
},
"scopeLogs": [{
"logRecords": [{
"timeUnixNano": "'$(date +%s)000000000'",
"severityText": "INFO",
"body": {"stringValue": "Tenant onboarding complete!"}
}]
}]
}]
}'
```
Verify it appears in OpenSearch:
```bash theme={null}
curl -sk https://localhost:9200/logclaw-logs-*/_search \
-H "Content-Type: application/json" \
-d '{"query":{"match":{"service":"onboarding-test"}}}'
```
## Troubleshooting
```bash theme={null}
argocd app get logclaw-tenant-
kubectl describe application logclaw-tenant- -n argocd
```
Check the "Conditions" section for error details.
```bash theme={null}
kubectl describe kafka logclaw--kafka -n logclaw-
kubectl logs -n strimzi-system -l name=strimzi-cluster-operator --tail=50
```
**Common causes:** StorageClass not found, PVC provisioning failure, resource limits too low.
```bash theme={null}
kubectl describe opensearchcluster -n logclaw-
kubectl logs -n opensearch-operator-system \
-l control-plane=controller-manager --tail=50
```
**Common causes:** Insufficient memory (2 Gi minimum per data node), disk pressure.
```bash theme={null}
kubectl describe flinkdeployment -n logclaw-
kubectl logs -n logclaw- -l app=logclaw-flink-anomaly --tail=50
```
**Common causes:** Kafka bootstrap unreachable, object storage credentials invalid.
```bash theme={null}
kubectl get externalsecret -n logclaw-
kubectl describe externalsecret -n logclaw-
kubectl get clustersecretstore
```
**Common causes:** IAM role not attached, wrong region, missing permissions.
```bash theme={null}
argocd app get logclaw-tenant- --hard-refresh
argocd app sync logclaw-tenant- --force
```
# OTLP Integration Guide
Source: https://docs.logclaw.ai/otlp-integration
Send logs to LogClaw from any language or framework using OpenTelemetry Protocol.
# OTLP Integration Guide
LogClaw uses **OTLP (OpenTelemetry Protocol)** as its sole log ingestion protocol. OTLP is the
CNCF industry standard supported by every major observability vendor — Datadog, Splunk, Grafana,
AWS CloudWatch, GCP Cloud Logging, and Azure Monitor all speak OTLP natively.
No custom integrations needed. If your app already uses OpenTelemetry, point it at LogClaw and
you're done.
## Endpoints
| Transport | Port | Path | Use Case |
| --------- | ---- | ---------- | ---------------------------------------------------------------------------------- |
| gRPC | 4317 | — | High-throughput, binary Protobuf. Recommended for production SDKs and OTel agents. |
| HTTP/JSON | 4318 | `/v1/logs` | Human-readable JSON. Good for curl, scripts, and debugging. |
Both ports are exposed on the `logclaw-otel-collector` Kubernetes service.
## Quick Start — curl
The simplest way to send a log to LogClaw:
```bash theme={null}
curl -X POST http://:4318/v1/logs \
-H "Content-Type: application/json" \
-d '{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "my-service"}}
]
},
"scopeLogs": [{
"logRecords": [{
"timeUnixNano": "'$(date +%s)000000000'",
"severityText": "ERROR",
"body": {"stringValue": "Connection refused to database"},
"traceId": "abcdef1234567890abcdef1234567890",
"spanId": "abcdef12345678",
"attributes": [
{"key": "environment", "value": {"stringValue": "production"}},
{"key": "region", "value": {"stringValue": "us-east-1"}}
]
}]
}]
}]
}'
```
Expected response: `200 OK` with `{"partialSuccess":{}}`.
## SDK Integration
### Python
Install the OpenTelemetry Python SDK:
```bash theme={null}
pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-grpc
```
```python theme={null}
import logging
from opentelemetry import _logs as logs
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
from opentelemetry.sdk.resources import Resource
# Configure the OTel log pipeline
resource = Resource.create({"service.name": "payment-api"})
provider = LoggerProvider(resource=resource)
provider.add_log_record_processor(
BatchLogRecordProcessor(
OTLPLogExporter(endpoint="http://:4317", insecure=True)
)
)
logs.set_logger_provider(provider)
# Attach OTel handler to Python logging
handler = LoggingHandler(logger_provider=provider)
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)
# Use standard Python logging — logs go to LogClaw automatically
logger = logging.getLogger("payment-api")
logger.error("Connection refused to database", extra={"trace_id": "abc123"})
```
### Java (Log4j2 + OTel Appender)
Add the OTel Log4j2 appender to your `pom.xml`:
```xml theme={null}
io.opentelemetry.instrumentation
opentelemetry-log4j-appender-2.17
2.10.0-alpha
```
Configure `log4j2.xml`:
```xml theme={null}
```
Set the environment variable to point at LogClaw:
```bash theme={null}
export OTEL_EXPORTER_OTLP_ENDPOINT=http://:4317
export OTEL_RESOURCE_ATTRIBUTES=service.name=order-engine
```
### Node.js (Winston + OTel)
```bash theme={null}
npm install @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-grpc \
@opentelemetry/resources @opentelemetry/semantic-conventions
```
```javascript theme={null}
const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-grpc');
const { Resource } = require('@opentelemetry/resources');
const { ATTR_SERVICE_NAME } = require('@opentelemetry/semantic-conventions');
const resource = new Resource({ [ATTR_SERVICE_NAME]: 'notification-svc' });
const loggerProvider = new LoggerProvider({ resource });
loggerProvider.addLogRecordProcessor(
new BatchLogRecordProcessor(
new OTLPLogExporter({ url: 'http://:4317' })
)
);
const logger = loggerProvider.getLogger('notification-svc');
logger.emit({
severityText: 'ERROR',
body: 'Failed to send email notification',
attributes: { 'user.id': '12345', environment: 'production' },
});
```
### Go
```bash theme={null}
go get go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc
go get go.opentelemetry.io/otel/sdk/log
```
```go theme={null}
package main
import (
"context"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
sdklog "go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)
func main() {
ctx := context.Background()
exporter, _ := otlploggrpc.New(ctx,
otlploggrpc.WithEndpoint(":4317"),
otlploggrpc.WithInsecure(),
)
res, _ := resource.New(ctx,
resource.WithAttributes(semconv.ServiceName("inventory-api")),
)
provider := sdklog.NewLoggerProvider(
sdklog.WithResource(res),
sdklog.WithProcessor(sdklog.NewBatchProcessor(exporter)),
)
defer provider.Shutdown(ctx)
logger := provider.Logger("inventory-api")
// Use logger to emit log records
_ = logger
}
```
## Using an OTel Collector as a Sidecar / Agent
For production deployments, the recommended pattern is to run an OTel Collector as a DaemonSet
or sidecar that collects logs from your pods and forwards them to LogClaw's collector.
```yaml theme={null}
# otel-agent-config.yaml
receivers:
filelog:
include: [/var/log/pods/*/*/*.log]
operators:
- type: container
id: container-parser
exporters:
otlp:
endpoint: "logclaw-otel-collector..svc:4317"
tls:
insecure: true
service:
pipelines:
logs:
receivers: [filelog]
exporters: [otlp]
```
This automatically captures all pod stdout/stderr logs without any application code changes.
## OTLP Field Mapping
LogClaw's Bridge flattens the nested OTLP structure into canonical flat documents for OpenSearch:
| OTLP Field | LogClaw Field | Description |
| ------------------------------------- | ----------------------------- | ----------------------------------- |
| `resource.attributes["service.name"]` | `service` | Service name |
| `logRecord.body.stringValue` | `message` | Log message |
| `logRecord.severityText` | `level` | Log level (INFO, WARN, ERROR, etc.) |
| `logRecord.timeUnixNano` | `timestamp` | ISO-8601 timestamp |
| `logRecord.traceId` | `trace_id` | Distributed trace ID |
| `logRecord.spanId` | `span_id` | Span ID |
| `resource.attributes["host.name"]` | `host` | Hostname |
| `resource.attributes["tenant_id"]` | `tenant_id` | Tenant (injected by collector) |
| `logRecord.attributes[*]` | Flattened as top-level fields | Custom attributes |
## Dashboard File Upload
The LogClaw dashboard supports drag-and-drop log file upload. When you upload a file through
the UI, the dashboard automatically converts each log entry to OTLP format using the built-in
`logsToOtlp()` converter and sends them to the OTel Collector via the `/api/otel/v1/logs` proxy.
Supported file formats:
* **JSON** — array of log objects `[{"message": "...", "level": "ERROR", ...}]`
* **NDJSON** — newline-delimited JSON (one log object per line)
* **CSV** — header row required, columns auto-mapped to OTLP fields
* **Text** (`.txt`, `.log`) — one log message per line
## Troubleshooting
**Logs not appearing in OpenSearch?**
1. Check OTel Collector health:
```bash theme={null}
kubectl port-forward svc/logclaw-otel-collector 13133:13133
curl http://localhost:13133/
# Expected: {"status":"Server available","..."}
```
2. Check Collector logs for export errors:
```bash theme={null}
kubectl logs -l app.kubernetes.io/name=logclaw-otel-collector --tail=50
```
3. Verify Kafka topic has messages:
```bash theme={null}
kubectl exec -it logclaw-kafka-0 -- bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 --topic raw-logs --max-messages 1
```
4. Check Bridge OTLP ETL thread:
```bash theme={null}
kubectl logs -l app.kubernetes.io/name=logclaw-bridge --tail=50 | grep "otlp"
```
**Getting `connection refused`?**
Ensure you're using the correct port:
* gRPC → `:4317`
* HTTP/JSON → `:4318/v1/logs`
Do not use port 8080 — that was the legacy Vector endpoint and is no longer available.
# Quick Start: Send Logs to LogClaw
Source: https://docs.logclaw.ai/quickstart-send-logs
Get your application logs flowing into LogClaw in under 5 minutes
# Quick Start: Send Logs to LogClaw
LogClaw ingests logs via **OTLP (OpenTelemetry Protocol)** — the CNCF industry standard. Every major language and framework has an OTel SDK.
**Your endpoint:**
| Protocol | URL |
| -------- | --------------------------------- |
| HTTP | `https://otel.logclaw.ai/v1/logs` |
| gRPC | `grpcs://otel.logclaw.ai:443` |
Every code example below includes this endpoint and your API key **inline** — just copy and run. See [API Keys](/api-keys) to create your key.
You do **not** need to set `tenant_id` — it is automatically injected based on your API key.
| Protocol | URL |
| -------- | ------------------------------- |
| HTTP | `http://localhost:4318/v1/logs` |
| gRPC | `localhost:4317` |
Every code example below defaults to the **LogClaw Cloud** endpoint. For self-hosted, swap the URL to `http://localhost:4318` and remove the `x-logclaw-api-key` header — look for the `# Self-hosted` comments in each snippet.
No authentication required — access is controlled via Kubernetes NetworkPolicy.
***
## Send Your First Log
Pick your language — click a tab to switch. Copy the code and run it.
```bash cURL theme={null}
# LogClaw Cloud — replace lc_proj_... with your API key
# Self-hosted: swap URL to http://localhost:4318/v1/logs and remove the api-key header
curl -X POST https://otel.logclaw.ai/v1/logs \
-H "Content-Type: application/json" \
-H "x-logclaw-api-key: lc_proj_your_key_here" \
-d '{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "my-api"}}
]
},
"scopeLogs": [{
"logRecords": [{
"timeUnixNano": "'$(date +%s)000000000'",
"severityText": "ERROR",
"body": {"stringValue": "Connection timeout to payment-gateway after 30s"},
"attributes": [
{"key": "error.type", "value": {"stringValue": "TimeoutError"}},
{"key": "http.route", "value": {"stringValue": "/api/v1/payments"}}
]
}]
}]
}]
}'
```
```python Python theme={null}
# pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-grpc
import logging
from opentelemetry import _logs
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
from opentelemetry.sdk.resources import Resource
resource = Resource.create({"service.name": "my-python-api"})
provider = LoggerProvider(resource=resource)
provider.add_log_record_processor(
BatchLogRecordProcessor(OTLPLogExporter(
endpoint="https://otel.logclaw.ai", # Self-hosted: http://localhost:4317
headers={"x-logclaw-api-key": "lc_proj_your_key_here"}, # Self-hosted: remove this line
))
)
_logs.set_logger_provider(provider)
handler = LoggingHandler(logger_provider=provider)
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)
logger = logging.getLogger(__name__)
logger.error("Payment failed", extra={"order_id": "ord-456"})
```
```javascript Node.js theme={null}
// npm install @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-grpc @opentelemetry/resources
const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-grpc');
const { Resource } = require('@opentelemetry/resources');
const resource = new Resource({ 'service.name': 'my-express-api' });
const loggerProvider = new LoggerProvider({ resource });
loggerProvider.addLogRecordProcessor(
new BatchLogRecordProcessor(new OTLPLogExporter({
url: 'https://otel.logclaw.ai', // Self-hosted: http://localhost:4317
headers: { 'x-logclaw-api-key': 'lc_proj_your_key_here' }, // Self-hosted: remove this line
}))
);
const logger = loggerProvider.getLogger('app');
logger.emit({ severityText: 'ERROR', body: 'Connection timeout', attributes: { 'http.route': '/api/orders' } });
```
```go Go theme={null}
// go get go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc
// go get go.opentelemetry.io/otel/sdk/log
package main
import (
"context"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
sdklog "go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
"google.golang.org/grpc/metadata"
)
func main() {
ctx := context.Background()
exporter, _ := otlploggrpc.New(ctx,
otlploggrpc.WithEndpointURL("otel.logclaw.ai:443"), // Self-hosted: localhost:4317
otlploggrpc.WithHeaders(map[string]string{"x-logclaw-api-key": "lc_proj_your_key_here"}), // Self-hosted: remove
)
res, _ := resource.New(ctx,
resource.WithAttributes(semconv.ServiceName("my-go-api")),
)
provider := sdklog.NewLoggerProvider(
sdklog.WithResource(res),
sdklog.WithProcessor(sdklog.NewBatchProcessor(exporter)),
)
defer provider.Shutdown(ctx)
}
```
```java Java (Spring Boot) theme={null}
# Zero code changes — just pass the endpoint and API key as JVM flags:
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.exporter.otlp.endpoint=https://otel.logclaw.ai \
-Dotel.exporter.otlp.headers=x-logclaw-api-key=lc_proj_your_key_here \
-Dotel.service.name=my-spring-api \
-Dotel.logs.exporter=otlp \
-jar my-app.jar
# Self-hosted: change endpoint to http://localhost:4318 and remove the headers flag
# Download the agent:
# curl -LO https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar
```
```csharp .NET theme={null}
// dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
// dotnet add package OpenTelemetry.Extensions.Hosting
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddOpenTelemetry(logging =>
{
logging.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService("my-dotnet-api"));
logging.AddOtlpExporter(o =>
{
o.Endpoint = new Uri("https://otel.logclaw.ai"); // Self-hosted: http://localhost:4318
o.Headers = "x-logclaw-api-key=lc_proj_your_key_here"; // Self-hosted: remove this line
});
});
// All ILogger calls auto-forward to LogClaw
```
```ruby Ruby theme={null}
# gem install opentelemetry-sdk opentelemetry-exporter-otlp
require 'opentelemetry-sdk'
require 'opentelemetry/exporter/otlp'
OpenTelemetry::SDK.configure do |c|
c.service_name = 'my-rails-app'
c.add_span_processor(
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
OpenTelemetry::Exporter::OTLP::Exporter.new(
endpoint: 'https://otel.logclaw.ai', # Self-hosted: http://localhost:4318
headers: { 'x-logclaw-api-key' => 'lc_proj_your_key_here' }, # Self-hosted: remove this line
)
)
)
end
```
```rust Rust theme={null}
// Add to Cargo.toml: opentelemetry, opentelemetry_sdk, opentelemetry-otlp, tracing
use opentelemetry_otlp::LogExporter;
use opentelemetry_sdk::logs::LoggerProvider;
use opentelemetry_sdk::Resource;
use opentelemetry::KeyValue;
fn init_logs() -> LoggerProvider {
let exporter = LogExporter::builder()
.with_tonic()
.with_endpoint("https://otel.logclaw.ai") // Self-hosted: http://localhost:4317
.with_metadata(metadata_with_api_key()) // Self-hosted: remove this line
.build()
.expect("Failed to create log exporter");
LoggerProvider::builder()
.with_resource(Resource::new(vec![
KeyValue::new("service.name", "my-rust-api"),
]))
.with_batch_exporter(exporter)
.build()
}
// Helper for API key header
fn metadata_with_api_key() -> tonic::metadata::MetadataMap {
let mut meta = tonic::metadata::MetadataMap::new();
meta.insert("x-logclaw-api-key", "lc_proj_your_key_here".parse().unwrap());
meta
}
```
Check your dashboard at [console.logclaw.ai](https://console.logclaw.ai) — the log should appear within seconds.
Every tab above is a complete, runnable example. Scroll down for framework-specific guides (Django, Flask, FastAPI, Express, Next.js, NestJS, Spring Boot, Gin, and more).
***
## Detailed Guides by Language
Below are full integration guides with framework-specific examples for each language.
The examples below include the **LogClaw Cloud** endpoint and API key inline — just copy and run. For **self-hosted** deployments, swap `https://otel.logclaw.ai` → `http://localhost:4318` (HTTP) or `localhost:4317` (gRPC) and remove the `x-logclaw-api-key` header. Look for the `# Self-hosted` comments in each snippet.
***
## Python
### Install
```bash theme={null}
pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-grpc
```
### Add to your app
```python theme={null}
import logging
from opentelemetry import _logs
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
from opentelemetry.sdk.resources import Resource
# One-time setup (add to your app startup)
resource = Resource.create({"service.name": "my-python-api"})
provider = LoggerProvider(resource=resource)
provider.add_log_record_processor(
BatchLogRecordProcessor(OTLPLogExporter(
endpoint="https://otel.logclaw.ai", # Self-hosted: http://localhost:4317
headers={"x-logclaw-api-key": "lc_proj_your_key_here"}, # Self-hosted: remove this line
))
)
_logs.set_logger_provider(provider)
# Attach OTel to Python's standard logging
handler = LoggingHandler(logger_provider=provider)
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)
# Now just use normal Python logging — it auto-sends to LogClaw
logger = logging.getLogger(__name__)
logger.info("User signed up", extra={"user_id": "u-123", "plan": "pro"})
logger.error("Payment failed", extra={"order_id": "ord-456", "error": "card_declined"})
```
That's it. Your existing `logging.info()`, `logging.error()`, etc. all flow to LogClaw automatically.
### Django
```python theme={null}
# settings.py
import logging
from opentelemetry import _logs
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
from opentelemetry.sdk.resources import Resource
resource = Resource.create({"service.name": "my-django-app"})
provider = LoggerProvider(resource=resource)
provider.add_log_record_processor(
BatchLogRecordProcessor(OTLPLogExporter(
endpoint="https://otel.logclaw.ai", # Self-hosted: http://localhost:4317
headers={"x-logclaw-api-key": "lc_proj_your_key_here"}, # Self-hosted: remove this line
))
)
_logs.set_logger_provider(provider)
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"console": {"class": "logging.StreamHandler"},
"otel": {
"class": "opentelemetry.sdk._logs.LoggingHandler",
"level": "INFO",
},
},
"root": {
"handlers": ["console", "otel"],
"level": "INFO",
},
"loggers": {
"django": {"handlers": ["console", "otel"], "level": "INFO"},
"django.request": {"handlers": ["console", "otel"], "level": "WARNING"},
},
}
```
All Django request logs, middleware errors, and your `logger.info()` calls auto-forward to LogClaw.
### Flask
```python theme={null}
# app.py
from flask import Flask
import logging
from opentelemetry import _logs
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
from opentelemetry.sdk.resources import Resource
# OTel setup
resource = Resource.create({"service.name": "my-flask-app"})
provider = LoggerProvider(resource=resource)
provider.add_log_record_processor(
BatchLogRecordProcessor(OTLPLogExporter(
endpoint="https://otel.logclaw.ai", # Self-hosted: http://localhost:4317
headers={"x-logclaw-api-key": "lc_proj_your_key_here"}, # Self-hosted: remove this line
))
)
_logs.set_logger_provider(provider)
logging.getLogger().addHandler(LoggingHandler(logger_provider=provider))
app = Flask(__name__)
@app.route("/api/health")
def health():
app.logger.info("Health check hit", extra={"endpoint": "/api/health"})
return {"status": "ok"}
@app.errorhandler(500)
def handle_error(e):
app.logger.error("Internal error", extra={"error": str(e)})
return {"error": "internal"}, 500
```
### FastAPI
```python theme={null}
# main.py
from fastapi import FastAPI, Request
import logging
from opentelemetry import _logs
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
from opentelemetry.sdk.resources import Resource
# OTel setup
resource = Resource.create({"service.name": "my-fastapi-app"})
provider = LoggerProvider(resource=resource)
provider.add_log_record_processor(
BatchLogRecordProcessor(OTLPLogExporter(
endpoint="https://otel.logclaw.ai", # Self-hosted: http://localhost:4317
headers={"x-logclaw-api-key": "lc_proj_your_key_here"}, # Self-hosted: remove this line
))
)
_logs.set_logger_provider(provider)
logging.getLogger().addHandler(LoggingHandler(logger_provider=provider))
logging.getLogger().setLevel(logging.INFO)
logger = logging.getLogger("fastapi-app")
app = FastAPI()
@app.middleware("http")
async def log_requests(request: Request, call_next):
logger.info("Request", extra={
"method": request.method,
"path": request.url.path,
"client": request.client.host,
})
response = await call_next(request)
return response
@app.get("/api/users/{user_id}")
async def get_user(user_id: str):
logger.info("Fetching user", extra={"user_id": user_id})
return {"user_id": user_id}
```
### Celery (Background Workers)
```python theme={null}
# celery_app.py — logs from async tasks also flow to LogClaw
from celery import Celery
from celery.signals import setup_logging
import logging
@setup_logging.connect
def configure_logging(**kwargs):
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
provider = LoggerProvider()
provider.add_log_record_processor(BatchLogRecordProcessor(OTLPLogExporter(
endpoint="https://otel.logclaw.ai", # Self-hosted: http://localhost:4317
headers={"x-logclaw-api-key": "lc_proj_your_key_here"}, # Self-hosted: remove
)))
logging.getLogger().addHandler(LoggingHandler(logger_provider=provider))
app = Celery("tasks", broker="redis://localhost:6379")
@app.task
def process_payment(order_id):
logger = logging.getLogger("celery-worker")
logger.info("Processing payment", extra={"order_id": order_id})
```
***
## Node.js
### Install
```bash theme={null}
npm install @opentelemetry/sdk-node @opentelemetry/sdk-logs \
@opentelemetry/exporter-logs-otlp-grpc @opentelemetry/resources \
@opentelemetry/semantic-conventions
```
### Express
```javascript theme={null}
// instrumentation.js — require BEFORE your app: node -r ./instrumentation.js app.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-grpc');
const { Resource } = require('@opentelemetry/resources');
const resource = new Resource({ 'service.name': 'my-express-api' });
const loggerProvider = new LoggerProvider({ resource });
loggerProvider.addLogRecordProcessor(
new BatchLogRecordProcessor(new OTLPLogExporter({
url: 'https://otel.logclaw.ai', // Self-hosted: http://localhost:4317
headers: { 'x-logclaw-api-key': 'lc_proj_your_key_here' }, // Self-hosted: remove this line
}))
);
const logger = loggerProvider.getLogger('app');
// Helper — use throughout your app
global.otelLog = (level, message, attrs = {}) => {
logger.emit({ severityText: level, body: message, attributes: attrs });
};
```
```javascript theme={null}
// app.js
const express = require('express');
const app = express();
// Request logging middleware
app.use((req, res, next) => {
otelLog('INFO', `${req.method} ${req.path}`, {
'http.method': req.method,
'http.route': req.path,
'http.client_ip': req.ip,
});
next();
});
app.post('/api/orders', (req, res) => {
otelLog('INFO', 'Order created', { orderId: 'ord-789' });
res.json({ status: 'created' });
});
app.use((err, req, res, next) => {
otelLog('ERROR', err.message, { 'error.stack': err.stack });
res.status(500).json({ error: 'Internal server error' });
});
app.listen(3000);
```
### Next.js
```javascript theme={null}
// instrumentation.ts (Next.js 13+ — auto-loaded by Next.js)
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
const { LoggerProvider, BatchLogRecordProcessor } = await import('@opentelemetry/sdk-logs');
const { OTLPLogExporter } = await import('@opentelemetry/exporter-logs-otlp-grpc');
const { Resource } = await import('@opentelemetry/resources');
const resource = new Resource({ 'service.name': 'my-nextjs-app' });
const loggerProvider = new LoggerProvider({ resource });
loggerProvider.addLogRecordProcessor(
new BatchLogRecordProcessor(new OTLPLogExporter({
url: 'https://otel.logclaw.ai', // Self-hosted: http://localhost:4317
headers: { 'x-logclaw-api-key': 'lc_proj_your_key_here' }, // Self-hosted: remove
}))
);
// Available in API routes and server components
globalThis.__otelLoggerProvider = loggerProvider;
}
}
```
```typescript theme={null}
// app/api/users/route.ts — Next.js App Router API route
import { NextResponse } from 'next/server';
function log(level: string, message: string, attrs: Record = {}) {
const provider = (globalThis as any).__otelLoggerProvider;
if (provider) {
provider.getLogger('app').emit({ severityText: level, body: message, attributes: attrs });
}
}
export async function GET() {
log('INFO', 'Users API called');
return NextResponse.json({ users: [] });
}
```
Or use the environment variable approach with `next.config.js`:
```javascript theme={null}
// next.config.js
module.exports = {
experimental: {
instrumentationHook: true, // enables instrumentation.ts
},
};
```
### NestJS
```bash theme={null}
npm install @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-grpc \
@opentelemetry/resources nestjs-otel
```
```typescript theme={null}
// src/otel.ts — load BEFORE NestJS bootstrap
import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-grpc';
import { Resource } from '@opentelemetry/resources';
const resource = new Resource({ 'service.name': 'my-nestjs-api' });
const loggerProvider = new LoggerProvider({ resource });
loggerProvider.addLogRecordProcessor(
new BatchLogRecordProcessor(new OTLPLogExporter({
url: 'https://otel.logclaw.ai', // Self-hosted: http://localhost:4317
headers: { 'x-logclaw-api-key': 'lc_proj_your_key_here' }, // Self-hosted: remove this line
}))
);
export { loggerProvider };
```
```typescript theme={null}
// src/main.ts
import './otel'; // MUST be first import
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
```
```typescript theme={null}
// src/orders/orders.controller.ts — use NestJS Logger as normal
import { Controller, Get, Logger, Post, Body } from '@nestjs/common';
@Controller('orders')
export class OrdersController {
private readonly logger = new Logger(OrdersController.name);
@Post()
create(@Body() createOrderDto: any) {
this.logger.log(`Order created: ${createOrderDto.id}`);
return { status: 'created' };
}
@Get()
findAll() {
this.logger.log('Fetching all orders');
return [];
}
}
```
### Fastify
```javascript theme={null}
// server.js
const fastify = require('fastify')({ logger: true }); // Pino logger built-in
// With OTel auto-instrumentation for Pino:
// npm install @opentelemetry/instrumentation-pino
// node -r ./instrumentation.js server.js
fastify.get('/api/health', async (request, reply) => {
request.log.info({ endpoint: '/api/health' }, 'Health check');
return { status: 'ok' };
});
fastify.post('/api/orders', async (request, reply) => {
request.log.info({ orderId: request.body.id }, 'Order created');
return { status: 'created' };
});
fastify.listen({ port: 3000 });
```
### With Winston
```javascript theme={null}
// logger.js
const winston = require('winston');
// Winston logs auto-forward to LogClaw with OTel instrumentation
// npm install @opentelemetry/instrumentation-winston
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [new winston.transports.Console()],
});
module.exports = logger;
```
```bash theme={null}
# Run with auto-instrumentation — zero code changes to your Winston setup
node -r ./instrumentation.js app.js
```
### With Pino
```javascript theme={null}
// Same approach — Pino auto-instrumented via OTel
// npm install @opentelemetry/instrumentation-pino
const pino = require('pino');
const logger = pino();
logger.info({ userId: 'usr-42' }, 'User logged in');
```
***
## Java / Kotlin
### Spring Boot (Zero Code Changes)
Download the OTel Java Agent — it auto-instruments your app:
```bash theme={null}
# Download the agent JAR
curl -LO https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar
# Run your Spring Boot app with the agent — endpoint and API key passed as JVM flags:
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.exporter.otlp.endpoint=https://otel.logclaw.ai \
-Dotel.exporter.otlp.headers=x-logclaw-api-key=lc_proj_your_key_here \
-Dotel.service.name=my-spring-api \
-Dotel.logs.exporter=otlp \
-jar my-app.jar
# Self-hosted: change endpoint to http://localhost:4318 and remove the headers flag
```
**Zero code changes.** All your `log.info()`, `log.error()`, Spring request logs, and exception stack traces automatically flow to LogClaw.
```java theme={null}
// Your existing Spring Boot code — no changes needed
@RestController
public class OrderController {
private static final Logger log = LoggerFactory.getLogger(OrderController.class);
@PostMapping("/api/orders")
public Order createOrder(@RequestBody OrderRequest req) {
log.info("Creating order for user={}", req.getUserId()); // → auto-sent to LogClaw
return orderService.create(req);
}
}
```
### Spring Boot via application.properties (Alternative)
```properties theme={null}
# application.properties
otel.exporter.otlp.endpoint=https://otel.logclaw.ai
otel.exporter.otlp.headers=x-logclaw-api-key=lc_proj_your_key_here
otel.service.name=my-spring-api
otel.logs.exporter=otlp
# Self-hosted: change endpoint to http://localhost:4318 and remove the headers line
```
```xml theme={null}
io.opentelemetry.instrumentation
opentelemetry-spring-boot-starter
2.10.0
```
### Quarkus
```properties theme={null}
# application.properties
quarkus.otel.exporter.otlp.endpoint=https://otel.logclaw.ai
quarkus.otel.exporter.otlp.headers=x-logclaw-api-key=lc_proj_your_key_here
quarkus.otel.resource.attributes=service.name=my-quarkus-api
quarkus.otel.logs.exporter=otlp
# Self-hosted: change endpoint to http://localhost:4318 and remove the headers line
```
```xml theme={null}
io.quarkus
quarkus-opentelemetry
```
### Micronaut
```yaml theme={null}
# application.yml
otel:
exporter:
otlp:
endpoint: https://otel.logclaw.ai # Self-hosted: http://localhost:4318
headers:
x-logclaw-api-key: lc_proj_your_key_here # Self-hosted: remove this line
resource:
attributes:
service.name: my-micronaut-api
```
### Kotlin (Ktor)
```kotlin theme={null}
// Application.kt
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import org.slf4j.LoggerFactory
fun main() {
// Run with: java -javaagent:opentelemetry-javaagent.jar
// -Dotel.exporter.otlp.endpoint=https://otel.logclaw.ai
// -Dotel.exporter.otlp.headers=x-logclaw-api-key=lc_proj_your_key_here -jar app.jar
embeddedServer(Netty, port = 8080) {
val logger = LoggerFactory.getLogger("ktor-app")
routing {
post("/api/orders") {
logger.info("Order created") // → auto-sent to LogClaw via agent
call.respondText("created")
}
}
}.start(wait = true)
}
```
### Log4j2 / Logback
Add the OTel appender to your logging config:
```xml theme={null}
```
***
## Go
```bash theme={null}
go get go.opentelemetry.io/otel/sdk/log
go get go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc
```
```go theme={null}
package main
import (
"context"
"log/slog"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
"go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)
func main() {
ctx := context.Background()
exporter, _ := otlploggrpc.New(ctx,
otlploggrpc.WithEndpointURL("otel.logclaw.ai:443"), // Self-hosted: localhost:4317
otlploggrpc.WithHeaders(map[string]string{"x-logclaw-api-key": "lc_proj_your_key_here"}), // Self-hosted: remove
)
res, _ := resource.New(ctx,
resource.WithAttributes(semconv.ServiceName("my-go-api")),
)
provider := log.NewLoggerProvider(
log.WithResource(res),
log.WithProcessor(log.NewBatchProcessor(exporter)),
)
defer provider.Shutdown(ctx)
// Use Go's standard slog with OTel backend
logger := slog.New(slog.NewJSONHandler(nil, nil))
logger.Info("Server started", "port", 8080)
logger.Error("Database connection failed", "host", "db.internal", "err", "timeout")
}
```
### Gin
```go theme={null}
package main
import (
"log/slog"
"github.com/gin-gonic/gin"
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
)
func main() {
// initOtel() — same setup as above
r := gin.Default()
r.Use(otelgin.Middleware("my-gin-api")) // auto-instruments all routes
r.POST("/api/orders", func(c *gin.Context) {
slog.Info("Order created", "orderId", c.Param("id"))
c.JSON(200, gin.H{"status": "created"})
})
r.Run(":8080")
}
```
```bash theme={null}
go get go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin
```
### Fiber
```go theme={null}
package main
import (
"log/slog"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/contrib/otelfiber"
)
func main() {
app := fiber.New()
app.Use(otelfiber.Middleware()) // auto-instruments all routes
app.Post("/api/orders", func(c *fiber.Ctx) error {
slog.Info("Order created", "path", c.Path())
return c.JSON(fiber.Map{"status": "created"})
})
app.Listen(":8080")
}
```
### Echo
```go theme={null}
package main
import (
"log/slog"
"github.com/labstack/echo/v4"
"go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho"
)
func main() {
e := echo.New()
e.Use(otelecho.Middleware("my-echo-api"))
e.POST("/api/orders", func(c echo.Context) error {
slog.Info("Order created")
return c.JSON(200, map[string]string{"status": "created"})
})
e.Start(":8080")
}
```
***
## Ruby
### Install
```bash theme={null}
gem install opentelemetry-sdk opentelemetry-exporter-otlp \
opentelemetry-logs-sdk opentelemetry-logs-exporter-otlp
```
### Add to your app
```ruby theme={null}
# config/initializers/otel.rb (Rails) or top of your app
require 'opentelemetry-sdk'
require 'opentelemetry/exporter/otlp'
OpenTelemetry::SDK.configure do |c|
c.service_name = 'my-ruby-api'
c.add_span_processor(
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
OpenTelemetry::Exporter::OTLP::Exporter.new(
endpoint: 'https://otel.logclaw.ai', # Self-hosted: http://localhost:4318
headers: { 'x-logclaw-api-key' => 'lc_proj_your_key_here' }, # Self-hosted: remove this line
)
)
)
end
# Use with Rails logger
Rails.logger.info("Order created", order_id: "ord-123")
```
### Rails Auto-Instrumentation (Zero Code Changes)
```bash theme={null}
gem install opentelemetry-instrumentation-all
```
```ruby theme={null}
# config/initializers/otel.rb
require 'opentelemetry/instrumentation/all'
OpenTelemetry::SDK.configure do |c|
c.service_name = 'my-rails-app'
c.use_all # Auto-instruments Rails, ActiveRecord, Rack, Sidekiq, etc.
end
```
Then run your app:
```bash theme={null}
rails server
```
***
## .NET / C\#
### Install
```bash theme={null}
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Extensions.Hosting
```
### ASP.NET Core
```csharp theme={null}
// Program.cs
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddOpenTelemetry(logging =>
{
logging.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService("my-dotnet-api"));
logging.AddOtlpExporter(o =>
{
o.Endpoint = new Uri("https://otel.logclaw.ai"); // Self-hosted: http://localhost:4318
o.Headers = "x-logclaw-api-key=lc_proj_your_key_here"; // Self-hosted: remove this line
});
});
var app = builder.Build();
// Your existing ILogger calls auto-forward to LogClaw
app.MapGet("/", (ILogger logger) =>
{
logger.LogInformation("Request received from {UserId}", "usr-42");
return "Hello World";
});
app.Run();
```
All existing `ILogger` calls (`LogInformation`, `LogError`, `LogWarning`) automatically flow to LogClaw.
***
## Rust
### Add to Cargo.toml
```toml theme={null}
[dependencies]
opentelemetry = "0.24"
opentelemetry_sdk = { version = "0.24", features = ["logs"] }
opentelemetry-otlp = { version = "0.17", features = ["logs"] }
tracing = "0.1"
tracing-opentelemetry = "0.25"
tracing-subscriber = "0.3"
```
### Add to main.rs
```rust theme={null}
use opentelemetry_otlp::LogExporter;
use opentelemetry_sdk::logs::LoggerProvider;
use opentelemetry_sdk::Resource;
use opentelemetry::KeyValue;
fn init_logs() -> LoggerProvider {
let exporter = LogExporter::builder()
.with_tonic()
.with_endpoint("https://otel.logclaw.ai") // Self-hosted: http://localhost:4317
.with_metadata(metadata_with_api_key()) // Self-hosted: remove this line
.build()
.expect("Failed to create log exporter");
LoggerProvider::builder()
.with_resource(Resource::new(vec![
KeyValue::new("service.name", "my-rust-api"),
]))
.with_batch_exporter(exporter)
.build()
}
// Helper for API key header
fn metadata_with_api_key() -> tonic::metadata::MetadataMap {
let mut meta = tonic::metadata::MetadataMap::new();
meta.insert("x-logclaw-api-key", "lc_proj_your_key_here".parse().unwrap());
meta
}
fn main() {
let _provider = init_logs();
// Use tracing macros — they auto-forward to LogClaw
tracing::info!(user_id = "usr-42", "Request processed");
tracing::error!(err = "timeout", "Database connection failed");
}
```
***
## PHP
### Install
```bash theme={null}
composer require open-telemetry/sdk \
open-telemetry/exporter-otlp \
open-telemetry/transport-grpc
```
### Add to your app
```php theme={null}
merge(
ResourceInfo::create(Attributes::create([
'service.name' => 'my-php-api',
]))
);
$exporter = new LogsExporter(
(new \OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory())->create(
'https://otel.logclaw.ai/v1/logs', // Self-hosted: http://localhost:4318/v1/logs
'application/json',
['x-logclaw-api-key' => 'lc_proj_your_key_here'] // Self-hosted: remove this line
)
);
$loggerProvider = (new LoggerProviderBuilder())
->setResource($resource)
->addLogRecordProcessor(new BatchLogRecordProcessor($exporter))
->build();
$logger = $loggerProvider->getLogger('app');
$logger->emit(
(new LogRecord('User login failed'))
->setSeverityText('ERROR')
->setAttribute('user.email', 'john@example.com')
);
```
### Laravel
```php theme={null}
// config/logging.php — add OTel as a log channel
'channels' => [
'otel' => [
'driver' => 'monolog',
'handler' => \Your\OtelHandler::class,
],
'stack' => [
'driver' => 'stack',
'channels' => ['single', 'otel'],
],
],
```
Then run your app:
```bash theme={null}
php artisan serve
```
***
## Log Shippers (Config Change Only)
Already running a log shipper? Point it at LogClaw's OTLP endpoint.
Log shippers use **config files**, not OTel SDKs. The examples below use the **LogClaw Cloud** URL (`https://otel.logclaw.ai`) — for self-hosted, replace with `http://localhost:4318`.
### Fluentd
```xml theme={null}
@type opentelemetry
endpoint https://otel.logclaw.ai/v1/logs
flush_interval 5s
```
```bash theme={null}
# Install the OTLP output plugin
fluent-gem install fluent-plugin-opentelemetry
```
### Fluent Bit
```ini theme={null}
# fluent-bit.conf
[OUTPUT]
Name opentelemetry
Match *
Host otel.logclaw.ai
Port 443
Tls On
Log_response_payload True
Logs_uri /v1/logs
add_label service.name my-app
```
### Vector
```toml theme={null}
# vector.toml
[sinks.logclaw]
type = "http"
inputs = ["your_source"]
uri = "https://otel.logclaw.ai/v1/logs"
encoding.codec = "json"
method = "post"
headers.content-type = "application/json"
# Or use the native OTLP sink (Vector 0.38+)
# [sinks.logclaw]
# type = "opentelemetry"
# inputs = ["your_source"]
# endpoint = "https://otel.logclaw.ai"
```
### Logstash
```ruby theme={null}
# logstash.conf
output {
http {
url => "https://otel.logclaw.ai/v1/logs"
http_method => "post"
format => "json"
content_type => "application/json"
}
}
```
```bash theme={null}
# Or install the OpenTelemetry output plugin
bin/logstash-plugin install logstash-output-opentelemetry
```
### Datadog Agent (Dual-Ship)
Already using Datadog? Send a copy to LogClaw:
```yaml theme={null}
# datadog.yaml
otlp_config:
logs:
enabled: true
receiver:
protocols:
grpc:
endpoint: 0.0.0.0:4317
```
Then point a separate OTel Collector at your Datadog Agent's OTLP receiver and forward to LogClaw.
***
## OTel Collector Sidecar (Zero Code Changes)
For apps that write logs to **stdout** or **log files** — any language, any framework — deploy an OTel Collector alongside your app to auto-scrape and forward logs. **No SDK needed.**
### Docker Compose example
```yaml theme={null}
# docker-compose.yml
services:
my-app:
image: my-app:latest
# Your app just logs to stdout as normal
otel-collector:
image: otel/opentelemetry-collector-contrib:0.114.0
volumes:
- ./otel-config.yaml:/etc/otelcol/config.yaml
depends_on:
- my-app
```
```yaml theme={null}
# otel-config.yaml
receivers:
filelog:
include: [/var/log/app/*.log]
start_at: beginning
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
resource:
attributes:
- key: service.name
value: "my-app"
action: upsert
batch:
send_batch_size: 500
timeout: 5s
exporters:
otlphttp:
endpoint: https://otel.logclaw.ai # Self-hosted: http://localhost:4318
headers:
x-logclaw-api-key: lc_proj_your_key_here # Self-hosted: remove this line
service:
pipelines:
logs:
receivers: [filelog, otlp]
processors: [resource, batch]
exporters: [otlphttp]
```
### Kubernetes DaemonSet
For Kubernetes clusters, deploy the OTel Collector as a DaemonSet to auto-collect logs from all pods:
```yaml theme={null}
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: otel-collector
spec:
selector:
matchLabels:
app: otel-collector
template:
metadata:
labels:
app: otel-collector
spec:
containers:
- name: collector
image: otel/opentelemetry-collector-contrib:0.114.0
env:
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "https://otel.logclaw.ai"
- name: OTEL_RESOURCE_ATTRIBUTES
value: "service.name=k8s-cluster"
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: true
volumes:
- name: varlog
hostPath:
path: /var/log
```
***
## Dashboard Upload (Drag & Drop)
For testing or one-time imports, the LogClaw dashboard supports drag-and-drop log upload:
1. Go to [console.logclaw.ai](https://console.logclaw.ai)
2. Click **Ingestion** in the sidebar
3. Drag a `.json`, `.ndjson`, `.csv`, or `.txt`/`.log` file onto the upload area
Accepted format (OTLP JSON):
```json theme={null}
{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "imported-logs"}}
]
},
"scopeLogs": [{
"logRecords": [
{
"timeUnixNano": "1709500000000000000",
"severityText": "ERROR",
"body": {"stringValue": "Something went wrong"}
}
]
}]
}]
}
```
***
## What Happens After Logs Arrive
```
Your App → OTLP → OTel Collector → Kafka → Flink ETL → OpenSearch
→ Flink Enrichment → Feature Store
→ Flink Anomaly Scorer → Ticketing Agent
→ PagerDuty / Jira / Slack
```
1. **OTel Collector** receives logs, batches them (1000/batch), and writes to Kafka
2. **Flink ETL** normalizes, enriches, and indexes into OpenSearch
3. **Flink Anomaly Scorer** detects anomalies in real-time using ML models
4. **Ticketing Agent** auto-creates incidents in PagerDuty/Jira/Slack when anomalies exceed threshold
5. **Dashboard** lets you search, filter, and visualize everything
***
## Supported Languages & Frameworks
Every option has a full code example above. Pick your stack:
| Language / Tool | Frameworks with Examples | Code Changes |
| ------------------ | ------------------------------------------------------ | -------------------------------- |
| **cURL / HTTP** | Any HTTP client | None — just POST JSON |
| **Python** | Django, Flask, FastAPI, Celery | 5 lines in startup |
| **Node.js** | Express, Next.js, NestJS, Fastify, Winston, Pino | 5 lines in startup |
| **Java / Kotlin** | Spring Boot, Quarkus, Micronaut, Ktor, Log4j2, Logback | **Zero** — just a JVM flag |
| **Go** | Gin, Fiber, Echo, net/http | 5 lines in main |
| **Ruby** | Rails (auto-instrumentation), Sinatra | 5 lines or **zero** with env var |
| **.NET / C#** | ASP.NET Core, Minimal API | Add to `Program.cs` |
| **Rust** | Actix, Axum, Rocket (via tracing) | 5 lines in main |
| **PHP** | Laravel, Symfony | Add to bootstrap |
| **Log Shippers** | Fluentd, Fluent Bit, Vector, Logstash, Datadog Agent | Config change only |
| **OTel Collector** | stdout / log files via sidecar | **Zero** — collector scrapes |
| **Dashboard** | Drag & drop JSON, NDJSON, CSV, or text files | **Zero** — drag a file |
***
## Troubleshooting
**Logs not appearing in dashboard?**
1. Test connectivity:
```bash theme={null}
curl -v https://otel.logclaw.ai/v1/logs \
-H "Content-Type: application/json" \
-H "x-logclaw-api-key: lc_proj_your_key_here" \
-d '{"resourceLogs":[]}'
# Should return 200 with {"accepted":true}
```
If you get `401 Unauthorized`, check that your API key is valid and not revoked.
```bash theme={null}
curl -v http://localhost:4318/v1/logs -d '{}' -H "Content-Type: application/json"
# Should return 200 (even with empty body)
```
2. Check your `service.name` attribute is set — logs without `service.name` may not display correctly
3. Verify OTLP endpoint URL — common mistakes:
* Missing `/v1/logs` path for HTTP (gRPC doesn't need it)
* Using port 4317 for HTTP (that's gRPC) or 4318 for gRPC (that's HTTP)
* Using `http://` instead of `https://` for external access
* **LogClaw Cloud:** using `localhost` instead of `otel.logclaw.ai`
4. Check firewall — OTLP uses port 443 (TLS-terminated) for external access
5. **LogClaw Cloud:** missing `x-logclaw-api-key` header — all managed platform requests require an API key. See [API Keys](/api-keys)
# Values Reference
Source: https://docs.logclaw.ai/values-reference
Full configuration reference for all LogClaw Helm chart values.
# Values Reference
This document describes all top-level `global.*` values accepted by the LogClaw umbrella
chart (`logclaw-tenant`) and propagated to every sub-chart.
## global.\*
| Field | Type | Required | Default | Description |
| ----------------------------------- | ------ | -------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `global.tenantId` | string | Yes | — | Unique tenant identifier. Lowercase letters, numbers, hyphens only. Max 40 characters. Used as a label, resource name suffix, and namespace component. |
| `global.tenantName` | string | Yes | — | Human-readable display name for the tenant. Shown in dashboards and alert messages. |
| `global.storageClass` | string | Yes | — | Name of the Kubernetes StorageClass used for all persistent volumes (Kafka, OpenSearch, Airflow). Must exist in the cluster before install. |
| `global.storageClassHighThroughput` | string | No | `""` (falls back to `storageClass`) | StorageClass optimised for high IOPS. Used for Kafka broker disks and OpenSearch data nodes when set. Recommended: `io2` on AWS, `pd-ssd` on GCP. |
| `global.tier` | string | No | `"ha"` | Deployment tier controlling replica counts and resource profiles. One of: `standard` (single replicas, minimal resources), `ha` (3 replicas, production resources), `ultra-ha` (5+ replicas, maximum resources). |
| `global.objectStorage.provider` | string | No | `"s3"` | Object storage backend. One of: `s3`, `gcs`, `azure`. Determines the credential format and SDK used by Flink checkpointing and ML engine model storage. |
| `global.objectStorage.bucket` | string | Yes | — | Name of the pre-created object storage bucket. Must be accessible from the cluster with appropriate IAM/service-account permissions. |
| `global.objectStorage.region` | string | No | `"us-east-1"` | Cloud region of the bucket. Required for AWS S3 and GCS. Ignored for Azure and self-hosted S3. |
| `global.objectStorage.endpoint` | string | No | `""` | Custom S3-compatible endpoint URL. Set for MinIO (`http://minio.minio.svc:9000`) or other self-hosted S3. Leave empty for AWS S3. |
| `global.secretStore.provider` | string | No | `"aws"` | Secret management backend for External Secrets Operator. One of: `aws`, `gcp`, `vault`, `azure`. Determines the ESO ClusterSecretStore type created for the tenant. |
| `global.secretStore.region` | string | No | `"us-east-1"` | AWS region for Secrets Manager. Used only when `provider=aws`. |
| `global.secretStore.projectId` | string | No | `""` | GCP project ID. Used only when `provider=gcp`. |
| `global.secretStore.vaultAddress` | string | No | `""` | HashiCorp Vault server address (e.g. `https://vault.example.com:8200`). Used only when `provider=vault`. |
| `global.imagePullSecrets` | list | No | `[]` | List of Kubernetes image pull secret names (`[{name: "my-secret"}]`). Applied to all workload pods across all sub-charts. |
| `global.monitoring.enabled` | bool | No | `true` | When `true`, enables ServiceMonitor and PrometheusRule resources for all sub-charts. Requires Prometheus Operator CRDs to be present. |
| `global.clusterDomain` | string | No | `"cluster.local"` | Kubernetes cluster DNS domain. Used to construct internal service FQDNs. Change only if your cluster uses a custom domain. |
## Component Enable Flags
These top-level boolean maps control which sub-charts are rendered by the umbrella chart.
| Field | Type | Required | Default | Description |
| ------------------------ | ---- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `platform.enabled` | bool | No | `true` | Deploy `logclaw-platform` (API gateway, RBAC, NetworkPolicy). Disabling also disables the ingress entry point. |
| `otelCollector.enabled` | bool | No | `true` | Deploy `logclaw-otel-collector` (OpenTelemetry Collector). OTLP-native log ingestion gateway accepting gRPC (:4317) and HTTP (:4318). Disable if you use a separate log shipping solution. |
| `kafka.enabled` | bool | No | `true` | Deploy `logclaw-kafka` (Strimzi Kafka cluster). Required by `flink` and `ticketingAgent`. |
| `flink.enabled` | bool | No | `true` | Deploy `logclaw-flink` (stream processing jobs). Requires `kafka.enabled=true`. |
| `opensearch.enabled` | bool | No | `true` | Deploy `logclaw-opensearch` (search cluster + dashboards). Required by `ticketingAgent`. |
| `mlEngine.enabled` | bool | No | `true` | Deploy `logclaw-ml-engine` (KServe InferenceService). Requires GPU nodes or CPU-only inference image override. |
| `airflow.enabled` | bool | No | `true` | Deploy `logclaw-airflow` (pipeline orchestration). |
| `ticketingAgent.enabled` | bool | No | `true` | Deploy `logclaw-ticketing-agent` (AI SRE agent). Requires `kafka.enabled=true` and `opensearch.enabled=true`. |
| `bridge.enabled` | bool | No | `false` | Deploy `logclaw-bridge` (OTLP ETL + trace correlation engine). Flattens OTLP JSON from Kafka `raw-logs` into canonical log documents, performs anomaly detection, trace correlation, and OpenSearch indexing. Dev/demo alternative to Flink. Can run alongside Flink. |
| `dashboard.enabled` | bool | No | `false` | Deploy `logclaw-dashboard` (Next.js pipeline UI). Provides log ingestion drag-and-drop, incident management, anomaly visualization, and real-time pipeline monitoring. |
| `agent.enabled` | bool | No | `false` | Deploy `logclaw-agent` (Go infrastructure health collector). Collects Kafka lag, Flink job status, OpenSearch health, and ESO sync status. Feeds the Dashboard pipeline view. |
## Per-Chart Override Syntax
Each sub-chart can be overridden using its chart name as the top-level key:
```yaml theme={null}
logclaw-kafka:
kafka:
replicas: 5
storage:
volumes:
- id: 0
type: persistent-claim
size: "2Ti"
deleteClaim: false
logclaw-opensearch:
opensearch:
data:
replicas: 5
diskSize: "2Ti"
masters:
replicas: 3
logclaw-otel-collector:
replicaCount: 10
autoscaling:
enabled: true
minReplicas: 10
maxReplicas: 50
targetCPUUtilizationPercentage: 60
logclaw-ticketing-agent:
config:
pagerduty:
enabled: true
jira:
enabled: true
baseUrl: "https://yourorg.atlassian.net"
projectKey: "SRE"
servicenow:
enabled: true
instance: "yourorg"
anomaly:
minimumScore: 0.85
lookbackWindow: "15m"
logclaw-ml-engine:
model:
name: "logclaw-anomaly-v2"
storageUri: "s3://your-bucket/models/anomaly-v2"
resources:
requests:
cpu: "2"
memory: "4Gi"
limits:
nvidia.com/gpu: "1"
logclaw-airflow:
airflow:
dags:
gitSync:
enabled: true
repo: "git@github.com:yourorg/logclaw-dags.git"
branch: "main"
sshKeySecret: "airflow-git-ssh-key"
logclaw-bridge:
bridge:
kafkaBrokers: "logclaw-kafka-kafka-bootstrap:9093"
opensearchEndpoint: "https://logclaw-opensearch:9200"
anomalyThreshold: 2.5
windowSize: 50
logclaw-dashboard:
image:
repository: "ghcr.io/logclaw/dashboard"
tag: "latest"
service:
type: ClusterIP # ClusterIP (default) or LoadBalancer for external access
port: 3000
```
## Tier Profiles
| Setting | standard | ha | ultra-ha |
| ----------------------------- | -------- | ---------- | --------------- |
| Kafka brokers | 1 | 3 | 5 |
| Kafka storage per broker | 100Gi | 1Ti | 2Ti |
| OpenSearch masters | 1 | 3 | 3 |
| OpenSearch data nodes | 1 | 3 | 5 |
| OpenSearch disk per data node | 100Gi | 1Ti | 2Ti |
| OTel Collector replicas | 1 | 3 | 5 |
| Flink task managers | 1 | 2 | 4 |
| Ticketing agent replicas | 1 | 2 | 3 |
| ML Engine replicas | 1 | 2 | 3 |
| PodDisruptionBudget | No | Yes | Yes |
| TopologySpreadConstraints | No | Yes (zone) | Yes (zone+node) |
## Environment Variable Substitution (Helmfile)
When using `helmfile`, the following environment variables are consumed by `helmfile.d/` releases:
| Variable | Used By | Default | Description |
| ------------------------- | ----------------------------------------------------- | ------------------------------------ | ------------------------------------------------------------- |
| `TENANT_ID` | all helmfile.d files | `dev-local` | Tenant identifier, injected into release names and namespaces |
| `STORAGE_CLASS` | `10-platform`, `20-kafka`, `40-opensearch` | `standard` | Overrides `global.storageClass` |
| `KAFKA_BROKERS` | `30-otel-collector`, `50-flink`, `80-ticketing-agent` | `logclaw-kafka-kafka-bootstrap:9093` | Kafka bootstrap server address |
| `OBJECT_STORAGE_BUCKET` | `50-flink`, `60-ml-engine` | `logclaw-dev-local` | Object storage bucket name |
| `OBJECT_STORAGE_PROVIDER` | `50-flink` | `s3` | Object storage provider |
| `OPENSEARCH_ENDPOINT` | `80-ticketing-agent` | `https://logclaw-opensearch:9200` | OpenSearch base URL |
| `HELM_REGISTRY` | `make push` | — | OCI registry target, e.g. `ghcr.io/yourorg/charts` |