Cosmian KMS exports metrics via OpenTelemetry Protocol (OTLP) over gRPC. This allows you to send metrics directly to any OTLP-compatible backend without exposing an HTTP endpoint.
flowchart LR
kms["KMS Server"]
collector["OTLP Collector"]
jaeger["Jaeger"]
cloud["Cloud Provider"]
custom["Custom Backend"]
kms -->|"OTLP/gRPC (port 4317)"| collector
collector --> jaeger
collector --> cloud
collector --> custom
Configure the OTLP endpoint in your kms.toml:
[logging]
# OTLP endpoint for metrics export
otlp = "http://localhost:4317"Or via environment variable:
export KMS_OTLP_URL="http://localhost:4317"
export KMS_ENABLE_METERING="true"Or via command-line flag:
cosmian_kms --otlp http://localhost:4317 --enable-metering- Automatic: Metrics are automatically sent when
otlpURL is configured - Interval: Metrics are pushed every 30 seconds
- Protocol: gRPC transport (OTLP/gRPC)
- No HTTP endpoint: KMS does not expose any HTTP
/metricsendpoint
# Start OTLP Collector and Jaeger
docker compose --profile otel-test up -d otel-collector jaegerThis starts:
- OTLP Collector on host ports 14317 (gRPC) and 14318 (HTTP)
- Collector Prometheus export on http://localhost:18889/metrics
- Jaeger UI on http://localhost:16686
# Configure KMS to send metrics to OTLP Collector
cosmian_kms --otlp-url http://localhost:4317 \
--database-type sqlite \
--sqlite-path /tmp/kms-data- Jaeger UI: http://localhost:16686 (metrics and traces)
- Collector /metrics: http://localhost:18889/metrics
The server exposes the following instruments via OTLP, as implemented in crate/server/src/core/otel_metrics.rs.
| Metric | Type | Labels |
|---|---|---|
kms.kmip.operations.total |
counter | operation |
kms.kmip.operations.per_user.total |
counter | operation, user |
kms.kmip.operation.duration |
histogram (s) | operation |
Note: The
userlabel inkms.kmip.operations.per_user.totalandkms.permissions.granted.per_user.totalcarries whatever string the authentication middleware extracts (e.g. an OAuth subject, email address, or service-account identifier); operators connecting these metrics to a cloud OTLP backend should be aware that this value will be stored in the backend.
| Metric | Type | Labels |
|---|---|---|
kms.active.users |
up-down counter | — |
kms.permissions.granted.per_user.total |
counter | user, permission_type |
kms.permissions.granted.total |
counter | — |
| Metric | Type | Labels |
|---|---|---|
kms.database.operations.total |
counter | operation, backend, outcome |
kms.database.operation.duration |
histogram (s) | operation, backend, outcome |
backend: sqlite · postgresql · mysql · redis — outcome: success · error
| Metric | Type | Labels |
|---|---|---|
kms.http.requests.total |
counter | method, path, status |
kms.http.request.duration |
histogram (s) | method, path, status |
| Metric | Type | Labels |
|---|---|---|
kms.server.uptime |
counter (monotonic, s) | — |
kms.server.start_time |
up-down counter | — |
kms.active.connections |
up-down counter | — |
kms.errors.total |
counter | error_type |
| Metric | Type | Labels |
|---|---|---|
kms.objects.total |
gauge | — |
kms.keys.active.count |
gauge | — |
kms.keys.active.count counts all non-destroyed key objects (SymmetricKey, PrivateKey,
PublicKey, SplitKey) across all non-terminal states: PreActive, Active, Deactivated, Compromised.
| Metric | Type | Labels |
|---|---|---|
kms.cache.operations.total |
counter | operation, result |
| Metric | Type | Labels |
|---|---|---|
kms.hsm.operations.total |
counter | operation, hsm_model |
The otel-collector-config.yaml receives metrics from KMS and forwards to backends:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
exporters:
otlp:
endpoint: ${env:OTLP_ENDPOINT} # Forward to Jaeger, etc.
service:
pipelines:
metrics:
receivers: [otlp]
processors: [resource, batch]
exporters: [otlp, debug]# Configure OTLP Collector to export to Datadog
export DD_SITE="datadoghq.com"
export DD_API_KEY="your-api-key"
# Update otel-collector-config.yaml
exporters:
datadog:
api:
key: ${env:DD_API_KEY}
site: ${env:DD_SITE}export NEW_RELIC_LICENSE_KEY="your-license-key"
# Update otel-collector-config.yaml
exporters:
otlp:
endpoint: otlp.nr-data.net:4317
headers:
api-key: ${env:NEW_RELIC_LICENSE_KEY}export GRAFANA_INSTANCE_ID="your-instance-id"
export GRAFANA_API_KEY="your-api-key"
# Update otel-collector-config.yaml
exporters:
otlp:
endpoint: otlp-gateway-${GRAFANA_INSTANCE_ID}.grafana.net:4317
headers:
authorization: "Bearer ${GRAFANA_API_KEY}"- Use TLS for OTLP transport:
[logging]
otlp = "https://collector.example.com:4317"- Authentication: Configure API keys in OTLP Collector:
exporters:
otlp:
headers:
authorization: "Bearer ${API_TOKEN}"- Network isolation: Run OTLP Collector in private network
Deploy multiple OTLP Collectors with load balancing:
[logging]
otlp = "https://otlp-lb.example.com:4317"-
Check KMS logs for OTLP connection errors:
cosmian_kms --log-level debug
-
Check Collector logs:
docker compose --profile otel-test logs -f otel-collector- Ensure
otlpURL is correct in configuration - Check network connectivity to OTLP Collector
- Verify Collector has correct exporters configured
| File | Purpose |
|---|---|
otel-collector-config.yaml |
OTLP Collector configuration |
docker-compose.yml |
Local development stack (use profile otel-test) |
crate/server/src/core/otel_metrics.rs |
Metrics instruments and recording helpers |
Previous Architecture (Removed):
- KMS exposed HTTP
/metricsendpoint - External scrapers pulled metrics
- Security concerns with exposed endpoint
Current Architecture:
- KMS pushes metrics via OTLP
- No HTTP endpoint exposure
- More secure and flexible
- Cloud-native standard