This article walks you through configuring log streaming via REST API.
All configuration is stored in etcd and served by api-service on port 443 with JWT authentication - the same API endpoint you use for storage management. On each server, lightbox-exporter reads the configuration from etcd and renders a pipeline config that Alloy polls via the remotecfg protocol.
User ──(HTTPS + JWT)──► api-service:443 ──► etcd
│
lightbox-exporter ◄────┘
│ (remotecfg)
Alloy ──► Loki / Rsyslog
Prerequisites
Step 1: Setting your JWT and Server
Export the server hostname and your system:cluster-admin JWT, so that all subsequent commands can reference them:
export SERVER=server00 # replace with your server hostname or IP
export JWT=eyJhbGciOiJSUzI1NiIsImtpZCI6InN5c3RlbTpyb290IiwidHlwIjoiSldUIn0...
# ^ obtain from /etc/cluster-manager/system_jwt on any cluster node, or from
# the Ansible output after cluster installation.
Info
All curl commands below use $SERVER and $JWT.
Step 2: (Optional) Creating a TLS Bundle
A TLS bundle holds the certificate material used for mTLS connections to an Rsyslog target. Skip this step if you only need a Loki target.
curl -sk -XPOST https://$SERVER/api/v2/logs/tls-bundles \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/json' \
-d "$(jq -n \
--arg name dc-rsyslog \
--rawfile ca ./scripts/certs/ca.crt \
--rawfile cert ./scripts/certs/rsyslog-server.crt \
--rawfile key ./scripts/certs/rsyslog-server.key \
'{name:$name, caPem:$ca, certPem:$cert, keyPem:$key}')" | jq
List the bundles to verify:
curl -sk -XGET https://$SERVER/api/v2/logs/tls-bundles \
-H "Authorization: Bearer $JWT" | jq
Step 3: Creating Sources
A source tells Alloy which log stream to collect on each server. You can collect from journald (by syslog identifier) or from a file path.
Control-plane Services (journald)
for svc in node-manager cluster-manager lightbox-exporter api-service \
upgrade-manager discovery-service etcd alloy; do
curl -sk -XPOST https://$SERVER/api/v2/logs/sources \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/json' \
-d "{\"name\":\"$svc\",\"enabled\":true,\"logFormat\":\"ZAP_JSON\",
\"minLevel\":\"INFO\",\"journald\":{\"syslogIdentifier\":\"$svc\"}}" | jq .source.name
done
Data-plane Services (journald)
curl -sk -XPOST https://$SERVER/api/v2/logs/sources \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/json' \
-d '{"name":"duroslight-0","enabled":true,"minLevel":"INFO",
"journald":{"syslogIdentifier":"duroslight-0"}}' | jq
curl -sk -XPOST https://$SERVER/api/v2/logs/sources \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/json' \
-d '{"name":"gftl","enabled":true,"minLevel":"INFO",
"journald":{"syslogIdentifier":"gftl"}}' | jq
Event Log Files (Optional)
Event logs are written to files only when emit_service_events_to_file is enabled in the Ansible config.
for svc in node-manager cluster-manager api-service upgrade-manager; do
curl -sk -XPOST https://$SERVER/api/v2/logs/sources \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/json' \
-d "{\"name\":\"$svc-events\",\"enabled\":true,\"logFormat\":\"ZAP_JSON\",
\"minLevel\":\"INFO\",
\"file\":{\"paths\":[\"/var/log/$svc-events.log\"],
\"labels\":{\"job\":\"$svc-events\"}}}" | jq .source.name
done
List all sources to verify:
curl -sk -XGET https://$SERVER/api/v2/logs/sources \
-H "Authorization: Bearer $JWT" | jq
Expected output (abbreviated):
{
"sources": [
{
"name": "node-manager",
"enabled": true,
"logFormat": "ZAP_JSON",
"minLevel": "INFO",
"journald": { "syslogIdentifier": "node-manager" }
},
{
"name": "duroslight-0",
"enabled": true,
"minLevel": "INFO",
"journald": { "syslogIdentifier": "duroslight-0" }
}
]
}
Step 4: Creating Targets
A target is a log destination. You can have multiple targets; every collected log line is forwarded to all enabled targets.
Loki Target (No TLS)
curl -sk -XPOST https://$SERVER/api/v2/logs/targets \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/json' \
-d '{"name":"loki1","type":"LOKI","enabled":true,
"loki":{"url":"http://loki.example.com:3100/loki/api/v1/push"}}' | jq
Rsyslog Target (mTLS, Requires TLS Bundle from Step 2)
curl -sk -XPOST https://$SERVER/api/v2/logs/targets \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/json' \
-d '{"name":"rsyslog1","type":"RSYSLOG","enabled":true,
"rsyslog":{"endpoint":"rsyslog.example.com","port":6514,
"timeout":"5s","tlsBundle":"dc-rsyslog"}}' | jq
List the targets to verify:
curl -sk -XGET https://$SERVER/api/v2/logs/targets \
-H "Authorization: Bearer $JWT" | jq
Expected output:
{
"targets": [
{
"name": "loki1",
"type": "LOKI",
"enabled": true,
"loki": { "url": "http://loki.example.com:3100/loki/api/v1/push" }
},
{
"name": "rsyslog1",
"type": "RSYSLOG",
"enabled": true,
"rsyslog": {
"endpoint": "rsyslog.example.com",
"port": 6514,
"timeout": "5s",
"tlsBundle": "dc-rsyslog"
}
}
]
}
Step 5: Verifying Collector Status
Once the configuration is applied, Alloy polls lightbox-exporter for the rendered pipeline config (via the remotecfg protocol) and begins shipping logs. Use the collectors API to check that each server's Alloy agent is healthy:
curl -sk -XGET https://$SERVER/api/v2/logs/collectors \
-H "Authorization: Bearer $JWT" | jq
Expected output when everything is healthy:
{
"collectors": [
{
"id": "c00-s00",
"lastSeen": "2026-02-24T15:46:29Z",
"alloyStatus": "ALLOY_ONLINE",
"remotecfgStatus": "REMOTECFG_UP",
"pipelineHealth": "OK",
"failingExporters": [],
"totalExporters": 2
}
]
}
If pipelineHealth is DEGRADED or ERROR, check failingExporters for which targets are unreachable.
Modifying the Configuration
Sources and targets can be updated or deleted without restarting any service. Alloy picks up the new pipeline config on its next remotecfg poll (default: 1 minute).
# Update a source's minimum log level
curl -sk -XPUT https://$SERVER/api/v2/logs/sources/node-manager \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/json' \
-d '{"name":"node-manager","enabled":true,"logFormat":"ZAP_JSON",
"minLevel":"WARN","journald":{"syslogIdentifier":"node-manager"}}' | jq
# Disable a target temporarily
curl -sk -XPUT https://$SERVER/api/v2/logs/targets/loki1 \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/json' \
-d '{"name":"loki1","type":"LOKI","enabled":false,
"loki":{"url":"http://loki.example.com:3100/loki/api/v1/push"}}' | jq
# Delete a source
curl -sk -XDELETE https://$SERVER/api/v2/logs/sources/gftl \
-H "Authorization: Bearer $JWT" | jq
Inspecting the Rendered Alloy Pipeline Config
You can inspect the pipeline config that lightbox-exporter renders for Alloy by querying the remotecfg endpoint on the server directly (no auth required — node-local only):
curl -s --header "Content-Type: application/json" -H "Connect-Protocol-Version: 1" http://localhost:8090/api/v1/remote/config | jq -r .content | base64 -d