# Grafana Alloy Logs

> Ship logs from an existing Grafana Alloy (Loki) pipeline to Last9 over OTLP

Source: https://last9.io/docs/integrations/observability/grafana-alloy-logs/

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.

```alloy
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

```alloy
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.

:::caution
The transform processor is not optional. Without it, your logs arrive with an **empty service name** — see below for why.
:::

## 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

1. Restart Alloy and wait a minute.
2. Open [Logs Explorer](https://app.last9.io/logs) 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:

```alloy
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

- Explore your logs in [Logs Explorer](/docs/logs-explorer/)
- Set up [log analytics dashboards](/docs/creating-log-analytics-dashboards-from-logs-explorer/)
