Grafana Alloy Logs
Ship logs from an existing Grafana Alloy (Loki) pipeline to Last9 over OTLP
If you already tail log files with Grafana Alloy and push them to Loki, you can fan out the same log stream to Last9 over OTLP — without touching your existing Loki setup.
Alloy provides an otelcol.receiver.loki component that bridges Loki log entries into the OpenTelemetry pipeline. One extra forward_to entry and three otelcol blocks get your logs into Last9.
Prerequisites
Get your OTLP credentials from the Last9 control plane: Integrations → OpenTelemetry → Connect. Keep these handy:
$last9_otlp_endpoint— OTLP ingestion endpoint$last9_otlp_usernameand$last9_otlp_password— basic auth credentials
Setup
1. Fan out the existing tailer
Add the Loki→OTLP bridge as a second destination on your existing loki.source.file component. The current Loki sink keeps working unchanged.
loki.source.file "all_logs_tailer" { targets = discovery.relabel.my_targets.output forward_to = [ loki.write.default.receiver, // existing Loki sink, unchanged otelcol.receiver.loki.last9.receiver, // added: Last9 ]}2. Bridge, transform, and export
otelcol.auth.basic "last9" { username = "$last9_otlp_username" password = "$last9_otlp_password"}
otelcol.receiver.loki "last9" { output { logs = [otelcol.processor.transform.last9.input] }}
otelcol.processor.transform "last9" { error_mode = "ignore"
log_statements { context = "log" statements = [ `set(resource.attributes["service.name"], attributes["service"]) where attributes["service"] != nil`, `set(resource.attributes["deployment.environment"], attributes["env"]) where attributes["env"] != nil`, `set(resource.attributes["host.name"], attributes["host"]) where attributes["host"] != nil`, ] }
output { logs = [otelcol.processor.batch.last9.input] }}
otelcol.processor.batch "last9" { output { logs = [otelcol.exporter.otlp.last9.input] }}
otelcol.exporter.otlp "last9" { client { endpoint = "$last9_otlp_endpoint" auth = otelcol.auth.basic.last9.handler }}Restart Alloy after the change.
Why the transform processor is required
otelcol.receiver.loki converts every Loki label (service, job, env, host, …) into a plain OTLP log record attribute, keeping the label names as-is. Last9 derives the service name from the OTel resource attribute service.name — which nothing in the bridge sets.
You also cannot work around this upstream: Loki label names cannot contain dots, so a service.name label is not valid in path_targets or relabel rules.
The transform statements promote the conventional labels to their OTel resource-attribute equivalents:
| Loki label | OTel resource attribute |
|---|---|
service | service.name |
env | deployment.environment |
host | host.name |
The original labels stay on the log records as attributes, so existing queries and dashboards that reference them keep working.
Verify
- Restart Alloy and wait a minute.
- Open Logs Explorer and check the service facet — your service name should appear.
- Filter by
service.nameto confirm logs are flowing.
Troubleshooting
Service name is empty, but labels like job and host show up.
The transform processor is missing or not wired between otelcol.receiver.loki and the batch processor. Check that the receiver’s output.logs points at the transform’s input, not directly at batch.
Multiple machines report as the same host.
Hardcoded host labels get copy-pasted across machines. Derive the label from the agent instead:
host = constants.hostnameLogs stop after an initial burst on a new machine. The first batch is Alloy catching up on existing file content. If nothing follows, check that the configured log paths receive new writes on that machine and that the Alloy service is running.
Next steps
- Explore your logs in Logs Explorer
- Set up log analytics dashboards