@primafuture/telemetry-stack
v0.3.0
Published
Reusable PrimaFuture telemetry stack launcher powered by Docker Compose.
Readme
@primafuture/telemetry-stack
Reusable telemetry stack.
This package does not contain Grafana, Loki, Tempo, Mimir, MinIO, Redpanda, Alloy, or Prometheus binaries. It contains a Docker Compose recipe and a small CLI that starts those Docker images for you.
Requirements
- Node.js 18+
- Docker
- Docker Compose plugin
Quick Start
Run a development telemetry stack and store its data in ./data/telemetry:
npx @primafuture/telemetry-stack up ./data/telemetryOpen Grafana:
npx @primafuture/telemetry-stack open ./data/telemetry grafanaStop the stack:
npx @primafuture/telemetry-stack down ./data/telemetryLocal Development
From this package directory:
node bin/telemetry-stack.mjs doctor
node bin/telemetry-stack.mjs config /tmp/telemetry-stack-test
node bin/telemetry-stack.mjs up /tmp/telemetry-stack-testCommands
telemetry-stack init <data-dir> [options]
telemetry-stack up <data-dir> [options]
telemetry-stack down <data-dir> [options]
telemetry-stack restart <data-dir> [options]
telemetry-stack ps <data-dir> [options]
telemetry-stack logs <data-dir> [service] [options]
telemetry-stack open <data-dir> [target] [options]
telemetry-stack config <data-dir> [options]
telemetry-stack doctor<data-dir> is where the stack stores runtime data and the instance config. Use a different directory for a different instance.
Common options:
--name <name> Override Docker Compose project name.
--mode <dev|prod> Instance mode for newly created config. Default: dev.Port options:
--grafana-port <port> Default: 3000
--otlp-grpc-port <port> Default: 4317
--otlp-http-port <port> Default: 4318
--tempo-port <port> Default: 3200
--loki-port <port> Default: 3100
--prometheus-port <port> Default: 9090
--mimir-port <port> Default: 9009
--minio-console-port <port> Default: 9001
--redpanda-port <port> Default: 9092
--redpanda-console-port <port> Default: 8080
--alloy-port <port> Default: 12345Logs options:
--follow, -f Follow log output. Only for logs.
--tail <lines> Number of log lines to show. Only for logs.Modes
The stack has two modes:
dev: default mode. Uses simple non-secure credentials, enables anonymous Grafana Admin, and binds public ports to0.0.0.0.prod: single-node production-oriented mode. Generates random credentials, disables anonymous Grafana, enables the login form, and binds public ports to127.0.0.1.
Create an instance config without starting containers:
telemetry-stack init ./data/telemetry --mode prodThe instance config is stored here:
<data-dir>/telemetry-stack.envGenerated service configs are stored here:
<data-dir>/generated-configs/For prod, find the Grafana admin user and password in telemetry-stack.env. The generated configs can also contain credentials, so do not commit the data directory. The prod mode is still single-node and not high availability. Put a reverse proxy, SSH tunnel, or private network in front of it if it needs remote access.
Multiple Instances
The simplest command uses default ports:
telemetry-stack up ./data/telemetryIf another stack already uses those ports, override them:
telemetry-stack up ./data/telemetry-project-b \
--grafana-port 3300 \
--otlp-grpc-port 14317 \
--otlp-http-port 14318 \
--tempo-port 13200 \
--loki-port 13100 \
--prometheus-port 19090 \
--mimir-port 19009 \
--minio-console-port 19001 \
--redpanda-port 19092 \
--redpanda-console-port 18080 \
--alloy-port 22345Project name is derived automatically from the absolute data directory. If you need a specific Docker Compose project name:
telemetry-stack up ./data/telemetry --name my-telemetryLogs
Follow all stack logs:
telemetry-stack logs ./data/telemetry --follow --tail 100Follow one service:
telemetry-stack logs ./data/telemetry alloy --follow --tail 100Useful service names:
alloy
tempo
loki
prometheus
mimir
grafana
minio
redpanda
redpanda-consoleSmoke Test
The stack no longer generates fake telemetry by default. To test the log path, send one OTLP HTTP log record to Alloy:
TS="$(date +%s%N)"
curl -sS -X POST http://localhost:4318/v1/logs \
-H 'Content-Type: application/json' \
-d "{
\"resourceLogs\": [{
\"resource\": {
\"attributes\": [{
\"key\": \"service.name\",
\"value\": {\"stringValue\": \"telemetry-smoke-test\"}
}]
},
\"scopeLogs\": [{
\"scope\": {\"name\": \"manual-curl\"},
\"logRecords\": [{
\"timeUnixNano\": \"${TS}\",
\"severityText\": \"INFO\",
\"body\": {\"stringValue\": \"hello from telemetry-stack smoke test\"},
\"attributes\": [
{
\"key\": \"app.value.log_type\",
\"value\": {\"stringValue\": \"smoke_test\"}
},
{
\"key\": \"app.value.route\",
\"value\": {\"stringValue\": \"/smoke\"}
},
{
\"key\": \"app.value.count\",
\"value\": {\"intValue\": \"3\"}
},
{
\"key\": \"app.value.parallel\",
\"value\": {\"boolValue\": true}
},
{
\"key\": \"app.type.deletedAt\",
\"value\": {\"stringValue\": \"null\"}
},
{
\"key\": \"app.meta.json\",
\"value\": {\"stringValue\": \"{\\\"log_type\\\":\\\"smoke_test\\\",\\\"route\\\":\\\"/smoke\\\",\\\"count\\\":3,\\\"parallel\\\":true,\\\"deletedAt\\\":null}\"}
}
]
}]
}]
}]
}"Query it from Loki:
curl -G 'http://localhost:3100/loki/api/v1/query_range' \
--data-urlencode 'query={loki_service_name="telemetry-smoke-test"}' \
--data-urlencode 'limit=5'For Grafana trace-to-logs compatibility, the same log is also indexed by the
legacy service_name label:
curl -G 'http://localhost:3100/loki/api/v1/query_range' \
--data-urlencode 'query={service_name="telemetry-smoke-test"}' \
--data-urlencode 'limit=5'The returned log should expose application metadata under structured names such
as app_value_log_type, app_value_route, app_value_count,
app_value_parallel, app_type_deletedAt, and app_meta_json.
OpenTelemetry metadata should be under otel_*, for example
otel_resource_service_name and otel_scope_name.
Log Metadata in Loki
PrimaFuture telemetry libraries encode application metadata before export. The
query-friendly view uses app.value.* for values, app.type.* for type markers
such as null or undefined, and app.meta.json for the JSON representation.
Loki displays those dotted names as underscore names such as app_value_route.
Legacy applications can still log plain metadata such as count, parallel, or
route. Alloy wraps only those unprefixed legacy fields into app.*; it leaves
already namespaced app.* metadata untouched.
The stack uses these names:
app_value_* query-friendly application metadata values
app_type_* application metadata type markers
app_meta_json JSON application metadata representation
app_* legacy application log attributes wrapped by Alloy
otel_resource_* OpenTelemetry resource attributes
otel_scope_* OpenTelemetry instrumentation scope attributes
otel_log_* OpenTelemetry log record fields
loki_* explicit Loki index labels
service_name legacy compatibility label for Grafana trace-to-logsExamples:
count -> app_value_count
route -> app_value_route
deletedAt: null -> app_type_deletedAt = "null"
metadata JSON -> app_meta_json
service.name -> otel_resource_service_name
instrumentation scope -> otel_scope_name
trace id -> otel_log_trace_idLoki indexes only the low-cardinality resource labels configured in
loki.yaml, such as loki_service_name and the compatibility service_name.
Application and OpenTelemetry detail fields are stored as structured metadata,
not as index labels. Query them after selecting a stream:
{loki_service_name="telemetry-dice-roller-example"}
| trace_id="0242ac120002"
| app_value_route="/roll"Current Loki versions return structured metadata merged into the stream labels
map in query_range responses. This is a Loki API behavior: the fields are still
not index labels. Use /loki/api/v1/labels or /loki/api/v1/series to see the
real indexed labels.
If a query response is too noisy, reduce returned fields at query time without changing stored data:
{service_name="telemetry-dice-roller-example"}
| trace_id="0242ac120002"
| keep service_name, loki_service_name, trace_id, span_idUseful URLs
Default ports:
- Grafana:
http://localhost:3000 - Tempo:
http://localhost:3200 - Loki:
http://localhost:3100 - Prometheus:
http://localhost:9090 - Mimir:
http://localhost:9009 - MinIO Console:
http://localhost:9001 - Redpanda Console:
http://localhost:8080 - OTLP gRPC:
localhost:4317 - OTLP HTTP:
http://localhost:4318
Notes
devis for local development and demos.prodis a safer single-node mode, not a full HA observability platform.- Grafana runs with anonymous Admin access only in
devmode by default. - Grafana runtime data is stored under
<data-dir>/grafana. - Docker images are pinned by digest in
stack/docker-compose.yaml.
