Centralized Logging for Microservices
This cookbook walks through setting up centralized log collection from multiple microservices into IceGate using the OpenTelemetry Collector.
Architecture
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Service A │ │ Service B │ │ Service C │
│ (Python) │ │ (Go) │ │ (Node.js) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ OTLP gRPC │ OTLP gRPC │
▼ ▼ ▼
┌─────────────────────────────────────────────────┐
│ OpenTelemetry Collector │
│ receivers: [otlp] │
│ processors: [batch, resource] │
│ exporters: [otlp/icegate] │
└──────────────────────┬──────────────────────────┘
│ OTLP gRPC (port 4317)
▼
┌────────────────┐
│ IceGate │
│ Ingest (4317) │
└────────┬───────┘
│ WAL → Shift
▼
┌────────────────┐
│ IceGate │
│ Query (3100) │◄── Grafana
└────────────────┘
Step 1: Deploy the OpenTelemetry Collector
The Collector acts as a central aggregation point, decoupling your services from IceGate.
# otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
send_batch_size: 1024
send_batch_max_size: 2048
timeout: 5s
resource:
attributes:
- key: environment
value: production
action: upsert
exporters:
otlp/icegate:
endpoint: icegate-ingest:4317
tls:
insecure: true
headers:
X-Scope-OrgID: my-tenant
retry_on_failure:
enabled: true
initial_interval: 1s
max_interval: 30s
sending_queue:
enabled: true
num_consumers: 10
queue_size: 1000
service:
pipelines:
logs:
receivers: [otlp]
processors: [batch, resource]
exporters: [otlp/icegate]
traces:
receivers: [otlp]
processors: [batch, resource]
exporters: [otlp/icegate]
Deploy with Docker Compose
# docker-compose.yml
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
command: ["--config=/etc/otel/config.yaml"]
volumes:
- ./otel-collector-config.yaml:/etc/otel/config.yaml
ports:
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
Step 2: Instrument Your Services
Python (with OpenTelemetry SDK)
import logging
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter
from opentelemetry.sdk.resources import Resource
# Configure OpenTelemetry
resource = Resource.create({
"service.name": "order-service",
"service.version": "1.2.0",
"deployment.environment": "production",
})
provider = LoggerProvider(resource=resource)
provider.add_log_record_processor(
BatchLogRecordProcessor(
OTLPLogExporter(
endpoint="otel-collector:4317",
insecure=True,
)
)
)
# Use standard Python logging - bridged to OTLP
logger = logging.getLogger("order-service")
logger.info("Order created", extra={"order.id": "ORD-12345", "user.id": "usr-42"})
Go (with OpenTelemetry SDK)
import (
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
"go.opentelemetry.io/otel/sdk/resource"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)
res := resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("payment-service"),
semconv.ServiceVersionKey.String("2.0.1"),
)
exporter, _ := otlploggrpc.New(ctx,
otlploggrpc.WithEndpoint("otel-collector:4317"),
otlploggrpc.WithInsecure(),
)
Direct Ingestion (without Collector)
For simple setups, send logs directly to IceGate:
curl -X POST http://localhost:4318/v1/logs \
-H "Content-Type: application/json" \
-H "X-Scope-OrgID: my-tenant" \
-d '{
"resourceLogs": [{
"resource": {
"attributes": [
{"key": "service.name", "value": {"stringValue": "order-service"}},
{"key": "deployment.environment", "value": {"stringValue": "production"}}
]
},
"scopeLogs": [{
"logRecords": [{
"timeUnixNano": "1704067200000000000",
"body": {"stringValue": "Order ORD-12345 created for user usr-42"},
"severityText": "INFO",
"severityNumber": 9,
"attributes": [
{"key": "order.id", "value": {"stringValue": "ORD-12345"}},
{"key": "user.id", "value": {"stringValue": "usr-42"}}
]
}]
}]
}]
}'
Step 3: Query Logs Across Services
Find Errors Across All Services
curl -G http://localhost:3100/loki/api/v1/query_range \
--data-urlencode 'query={severity_text="ERROR"}' \
--data-urlencode 'start=1704067200' \
--data-urlencode 'end=1704153600' \
--data-urlencode 'limit=100' \
-H "X-Scope-OrgID: my-tenant"
Filter by Service
curl -G http://localhost:3100/loki/api/v1/query_range \
--data-urlencode 'query={service_name="order-service"} |= "error"' \
--data-urlencode 'start=1704067200' \
--data-urlencode 'end=1704153600' \
-H "X-Scope-OrgID: my-tenant"
Error Rate by Service
curl -G http://localhost:3100/loki/api/v1/query_range \
--data-urlencode 'query=sum by (service_name) (rate({severity_text="ERROR"}[5m]))' \
--data-urlencode 'start=1704067200' \
--data-urlencode 'end=1704153600' \
--data-urlencode 'step=300' \
-H "X-Scope-OrgID: my-tenant"
Search by Custom Attribute
curl -G http://localhost:3100/loki/api/v1/query_range \
--data-urlencode 'query={service_name="order-service"} |= "ORD-12345"' \
--data-urlencode 'start=1704067200' \
--data-urlencode 'end=1704153600' \
-H "X-Scope-OrgID: my-tenant"
List All Services Sending Logs
curl http://localhost:3100/loki/api/v1/label/service_name/values \
-H "X-Scope-OrgID: my-tenant"
Step 4: Set Up Grafana
Add IceGate as a Loki data source in Grafana:
# grafana/provisioning/datasources/icegate.yaml
apiVersion: 1
datasources:
- name: IceGate Logs
type: loki
access: proxy
url: http://icegate-query:3100
jsonData:
httpHeaderName1: X-Scope-OrgID
secureJsonData:
httpHeaderValue1: my-tenant
isDefault: true
See Grafana Integration for dashboards and advanced configuration.
Step 5: Per-Team Isolation
Use X-Scope-OrgID to isolate logs by team or environment:
# Team A collector config
exporters:
otlp/icegate:
endpoint: icegate-ingest:4317
tls:
insecure: true
headers:
X-Scope-OrgID: team-platform
# Team B collector config
exporters:
otlp/icegate:
endpoint: icegate-ingest:4317
tls:
insecure: true
headers:
X-Scope-OrgID: team-backend
Each team queries only their own data. See Multi-Tenancy for details.
Next Steps
- Add distributed tracing to correlate logs with traces
- Set up cross-signal correlation between logs and traces
- Configure data retention policies
Previous