Skip to content
Last9
Book demo

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_username and $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 labelOTel resource attribute
serviceservice.name
envdeployment.environment
hosthost.name

The original labels stay on the log records as attributes, so existing queries and dashboards that reference them keep working.

Verify

  1. Restart Alloy and wait a minute.
  2. Open Logs Explorer and check the service facet — your service name should appear.
  3. Filter by service.name to 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.hostname

Logs 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