> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rootprint.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Send logs with the OpenTelemetry Collector

> Tail log files with the OpenTelemetry Collector — on bare metal or in Docker — and ship them to Rootprint over OTLP.

The [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) reads, processes, and exports telemetry without an SDK in your application. The `filelog` receiver tails files under `/var/log/`, and the `otlphttp` exporter ships each record to Rootprint's OTLP endpoint with a Bearer token. Records land in the index your ingest token is scoped to: `otel-logs-v0_9` in this guide. Run the Collector on bare metal or in Docker — pick the tab below.

Use the **Contrib** distribution (`otelcol-contrib`). The `filelog` receiver lives in Contrib, not in the core release.

<Warning>
  The `filelog` paths below assume a Linux host. The Collector runs on
  macOS and Windows too, but this page does not cover those platforms.
</Warning>

## Prerequisites

* A running Rootprint instance and its base URL — you'll substitute it for `<your-rootprint>`.
* A Linux host. The **Bare Metal** tab runs the Collector directly or under `systemd`; the **Docker** tab uses Docker Compose.
* An **ingest API key** scoped to your target index. In **Settings → API keys**, click **Create ingest key**, give it a name, and pick the index (`otel-logs-v0_9` here; see [Indexes](/indexes) for its schema). See [API keys](/api/overview).

<Tabs>
  <Tab title="Bare Metal">
    <Steps>
      <Step title="Install the Collector">
        Install the Contrib distribution (`otelcol-contrib`) for your platform — Debian/Ubuntu apt,
        RHEL/Fedora rpm, or a standalone binary, all maintained upstream on the
        [Collector installation page](https://opentelemetry.io/docs/collector/installation/).
      </Step>

      <Step title="Write /etc/otel/config.yaml">
        Save the following at `/etc/otel/config.yaml`. Replace `<your-rootprint>` with your
        Rootprint base URL, `<your-ingest-token>` with the API key you copied in step 1, and
        `/var/log/myapp/*.log` with the glob that matches your application's log files.

        ```yaml theme={"theme":"github-light"}
        receivers:
          filelog:
            include:
              - /var/log/myapp/*.log
            start_at: end

        processors:
          batch: {}

        exporters:
          otlphttp:
            logs_endpoint: https://<your-rootprint>/v1/logs
            compression: gzip
            headers:
              Authorization: "Bearer <your-ingest-token>"

        service:
          pipelines:
            logs:
              receivers: [filelog]
              processors: [batch]
              exporters: [otlphttp]
        ```

        `start_at: end` skips existing content on first start, so pointing the Collector at an existing
        log file does not replay everything already in it. Set it to `beginning` for a one-time backfill.
      </Step>

      <Step title="Grant read access to the log files">
        The user the Collector runs as may not be able to read files under `/var/log/`. Add it to
        the group that owns them (the Debian/RPM package runs as `otelcol-contrib`; the group is
        often `adm` on Debian/Ubuntu):

        ```bash theme={"theme":"github-light"}
        sudo usermod -aG adm otelcol-contrib
        ```

        Alternatively `sudo chmod a+r` the log files, which loosens permissions for every user on
        the host.
      </Step>

      <Step title="Start the Collector">
        Point the Collector at the config:

        ```bash theme={"theme":"github-light"}
        otelcol-contrib --config /etc/otel/config.yaml
        ```

        If you run it under a service manager instead, point that unit at `/etc/otel/config.yaml` and
        restart it. Either way, the startup log lines should not contain config-parse or exporter errors.
      </Step>

      <Step title="Send a test log line">
        Create the application log directory if it doesn't exist yet and append a single line:

        ```bash theme={"theme":"github-light"}
        sudo mkdir -p /var/log/myapp
        echo "$(date -Iseconds) hello from the otel collector" | sudo tee -a /var/log/myapp/test.log
        ```
      </Step>

      <Step title="Verify in Rootprint">
        Open Search, pick `otel-logs-v0_9` from the index selector, and query for
        `hello from the otel collector`. Records typically appear within 5–10 seconds. The OTLP path
        commits on Quickwit's normal cadence; there is no `commit=wait_for` knob.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Docker">
    Run the Collector as a container that bind-mounts the log files you want to tail. The config is
    the same one from the Bare Metal tab; the bind mount keeps the `/var/log/myapp/*.log` paths
    identical inside the container.

    <Steps>
      <Step title="Save otel-config.yaml">
        Save the **same config** shown in the Bare Metal tab as `otel-config.yaml` next to your
        `docker-compose.yml`. Replace `<your-rootprint>` and `<your-ingest-token>`, and point the
        `filelog` `include` at the log path you bind-mount in the next step.
      </Step>

      <Step title="Add the Collector to your compose file">
        ```yaml theme={"theme":"github-light"}
        services:
          rootprint-otelcol:
            image: otel/opentelemetry-collector-contrib:latest
            container_name: rootprint-otelcol
            restart: unless-stopped
            command: ["--config=/etc/otelcol/config.yaml"]
            volumes:
              - ./otel-config.yaml:/etc/otelcol/config.yaml:ro
              - /var/log/myapp:/var/log/myapp:ro
        ```

        The bind-mounted files keep their host ownership inside the container, so the Collector's
        container user may not be able to read them. If logs don't appear and the Collector reports
        `permission denied`, run the service as `user: root` or loosen host permissions with
        `sudo chmod a+r /var/log/myapp/*.log`.
      </Step>

      <Step title="Start the Collector">
        ```bash theme={"theme":"github-light"}
        docker compose up -d rootprint-otelcol
        ```
      </Step>

      <Step title="Send a test log line">
        Append a line to a file under the bind-mounted directory on the host:

        ```bash theme={"theme":"github-light"}
        sudo mkdir -p /var/log/myapp
        echo "$(date -Iseconds) hello from the otel collector" | sudo tee -a /var/log/myapp/test.log
        ```
      </Step>

      <Step title="Verify in Rootprint">
        Open Search, pick `otel-logs-v0_9` from the index selector, and query for
        `hello from the otel collector`. Records typically appear within 5–10 seconds.
      </Step>
    </Steps>
  </Tab>
</Tabs>

## What the config does

* **`filelog` receiver**: tails the files matched by `include` and emits one OTLP log record per
  line. The raw line lands in `body`, and the receiver populates `observedTimeUnixNano` with the
  time it read the line. To split structured fields out of the line, add the receiver's `operators`
  (a `json_parser` or `regex_parser`); without them the whole line is kept verbatim.
* **`logs_endpoint`**: the full logs URL, including `/v1/logs`. Set this rather than `endpoint`,
  which would append the path itself. The `otlphttp` exporter sends OTLP over HTTP as protobuf,
  which is the only content type Rootprint's OTLP endpoint accepts.
* **`compression: gzip`**: gzips each request body. Rootprint forwards the `Content-Encoding`
  header to Quickwit unchanged.
* **`batch` processor**: groups records before export to cut request count. Tune `send_batch_size`
  and `timeout` if you need tighter latency or larger batches.

## Troubleshooting

* **`401` from Rootprint**: the `Authorization` header is missing or malformed. Confirm the
  `headers.Authorization` value reads exactly `Bearer <token>`, with a single space.
* **`403` from Rootprint**: the API key's index scope does not match `otel-logs-v0_9`, or the key
  has been deleted. Mint a fresh ingest API key for `otel-logs-v0_9` in **Settings → API keys**.
* **`404 unrecognized receiver "filelog"`** on startup: you are running the core Collector, not
  Contrib. Install `otelcol-contrib`.
* **`413` from a proxy in front of Rootprint**: a single batch was too large on the wire. Rootprint
  sets no OTLP body-size limit, but a reverse proxy may. Lower the `batch` processor's
  `send_batch_size`.
* **`415` from Rootprint**: the request is not protobuf. Use the `otlphttp` exporter (not a custom
  HTTP exporter that sends JSON); `otlphttp` defaults to protobuf.
* **Collector starts cleanly but no logs appear**: `start_at: end` skipped the existing file
  content. Append a new line to trigger a read, or set `start_at: beginning` for a one-time replay.
* **`permission denied` reading `/var/log/...`**: the user the Collector runs as can't read the
  files. Either `sudo chmod a+r` the log files, or add that user to the group that owns them. (The
  Debian/RPM package runs as `otelcol-contrib`.)

## Related

* [Send logs from Docker](/send-logs/platforms/docker): collect **every** container's stdout via the Docker daemon, rather than tailing specific files.
* [Send logs from Kubernetes](/send-logs/platforms/kubernetes): run the Collector as a DaemonSet across a cluster.
* [OTLP reference](/send-logs/otlp): endpoint URL, response codes, body limits.
* [Indexes](/indexes): the `otel-logs-v0_9` schema, so you know what you can search.
* [Manage indexes](/configuration/manage-indexes): API key lifecycle and per-index permissions.
