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

# Sumo Logic

> Send agent logs to a Sumo Logic HTTP Source as newline-delimited JSON, with the collector code stored as a write-only credential

A `sumologic` drain posts to one Sumo Logic **HTTP Source**. Each record becomes one newline-delimited JSON message with `timestamp`, `level`, `message`, and the whole Lua record under `lua`.

<Info>
  Vendor destinations are opened per deployment. Where `sumologic` is not open yet, a create answers `422 DRAIN_TYPE_UNAVAILABLE` and the message names the types that are. **It may not be enabled on your deployment yet.**
</Info>

**Before you begin**

* A hosted collector with an **HTTP Logs & Metrics Source**. Sumo gives you one URL when you create it, ending in a long random collector code.

## The URL is the credential

<Warning>
  **A Sumo Logic HTTP Source has no token, no key, and no auth header. Anyone holding the URL can write to it.** Treat it exactly as you would a password: never paste it into a ticket, a chat, or a shell history.

  Lua treats it that way too. When the drain is created, the **collector code is split off the URL and stored in the encrypted credential store**, bound to your organization. What the drain document keeps is the prefix — `https://endpoint4.collection.eu.sumologic.com/receiver/v1/http/` — which is why `endpoint` is still safe to return from a read. The two halves are joined at send time and nowhere else.

  The code is in no read route, no audit entry, no delivery row, no response excerpt, and no error message, because none of them ever holds it.
</Warning>

## What to enter

| Field    | Flag               | Value                                                          |
| -------- | ------------------ | -------------------------------------------------------------- |
| Name     | `--name`           | 1–64 characters, unique in the organization                    |
| Type     | `--type sumologic` |                                                                |
| Endpoint | `--endpoint`       | The **full** HTTP Source URL, **ending in the collector code** |

There is no `--header` to set: the URL is the whole credential.

```bash theme={null}
lua drains create \
  --name "Sumo prod" \
  --type sumologic \
  --endpoint 'https://endpoint4.collection.eu.sumologic.com/receiver/v1/http/<collector code>' \
  --environments production \
  --min-severity info
```

A URL that stops short of the collector code is refused at create time with `422 DRAIN_VALIDATION_FAILED` and `field: "endpoint"` — there is nothing to authenticate with. The host must end in `.sumologic.com`; that is a **suffix** rule, so every regional collection endpoint is admitted and a lookalike domain is not.

The collector code may contain letters, digits, and `. _ ~ % = + -` only. A value outside that set is dropped rather than interpolated into the URL, and the request then `404`s at Sumo — a delivery row you can read, rather than a malformed request.

## Rotating it

There is no "reveal" and no rotate verb, because Lua cannot show you what it will not read back.

<Steps>
  <Step title="Create a new HTTP Source in Sumo Logic">
    Or regenerate the existing one. Copy its URL.
  </Step>

  <Step title="Patch the drain with the new URL">
    ```bash theme={null}
    lua drains update drn_1f60ad39c825e74b0a37bb21 \
      --endpoint 'https://endpoint4.collection.eu.sumologic.com/receiver/v1/http/<new code>'
    ```

    A `PATCH` carrying a new URL rewrites the stored code **even when the prefix is unchanged** — which is exactly what a source rotation looks like.
  </Step>

  <Step title="Re-verify">
    Changing the endpoint returns the drain to `pending_verification`; records buffer until you verify.

    ```bash theme={null}
    lua drains verify drn_1f60ad39c825e74b0a37bb21
    ```
  </Step>

  <Step title="Delete the old source in Sumo Logic">
    Only now. Until the new drain verifies, the old source is your delivery path.
  </Step>
</Steps>

A read of the drain shows the prefix and `endpointSecretLast4` — the last four characters of the stored code, enough to tell one from another after a rotation, and not enough to be one.

## How ownership verification works

<Warning>
  **A vendor preset is reachability-checked, not ownership-verified.** `lua drains verify` posts one batch holding a single `lua.drain.test` record, and any `2xx` from the HTTP Source is accepted. It proves the URL works. It does **not** prove you own the collector. Only a [generic HTTPS](/drains/generic-https) drain is ownership-verified by a token echo, and only an [object-storage](/drains/object-storage) drain proves ownership by writing into a bucket.
</Warning>

```bash theme={null}
lua drains verify drn_1f60ad39c825e74b0a37bb21
```

Sumo answers a successful upload with **`200`**. A `404` means the collector code is wrong or the source has been deleted. Verification is limited to 5 attempts per drain per hour.

Sumo Logic deliveries are **not signed**.

## What arrives

**One JSON object per line**, newline-delimited — not a JSON array. Sumo's HTTP Source splits an upload into messages on newlines, so an array would arrive as **one enormous message** and defeat every per-message search, facet, and monitor you would want to build on it.

```text theme={null}
{"timestamp":"2026-09-12T12:59:57.644Z","level":"error","message":"Ticket lookup failed: upstream timeout","lua":{"id":"1789217997644-jtoulxxxq","eventName":"lua.skill.error","severityText":"ERROR","attributes":{"lua.source":"skill","gen_ai.tool.name":"lookup_tickets"}}}
{"timestamp":"2026-09-12T13:00:01.112Z","level":"info","message":"Ticket 88213 resolved","lua":{"id":"1789218001112-bq2x0nnvs","eventName":"lua.skill.complete","severityText":"INFO","attributes":{"lua.source":"skill"}}}
```

| Sumo field  | Comes from                                                                                      |
| ----------- | ----------------------------------------------------------------------------------------------- |
| `timestamp` | The record's `timestamp`, RFC 3339 in UTC                                                       |
| `level`     | The severity, lower-cased: `debug`, `info`, `warn`, `error`                                     |
| `message`   | The record's `body`, or its `eventName` when there is no body                                   |
| `lua`       | The **whole record**, unflattened, exactly as [Event schema](/drains/event-schema) describes it |

Two headers ride every delivery, so the records are categorised without configuring anything in Sumo:

| Header            | Value            |
| ----------------- | ---------------- |
| `X-Sumo-Name`     | `lua-log-drain`  |
| `X-Sumo-Category` | `lua/agent-logs` |

`X-Sumo-Host` is deliberately **not** sent: it would duplicate `lua.agent.id`, which is already on every record.

### Useful queries

```text theme={null}
_sourceCategory=lua/agent-logs | json field=_raw "lua.eventName" as eventName | where eventName = "lua.skill.error"

_sourceCategory=lua/agent-logs | json field=_raw "level" | count by level

_sourceCategory=lua/agent-logs | json field=_raw "lua.attributes.gen_ai.tool.name" as tool | count by tool
```

### Caps

| Cap                            | Value                                       |
| ------------------------------ | ------------------------------------------- |
| Records per request            | 500                                         |
| Uncompressed bytes per request | 448 KiB (Sumo's own request limit is 1 MiB) |
| Bytes per message              | 256 KiB                                     |
| Compression                    | `gzip`, always                              |
| Retried                        | `408`, `429`, and every `5xx`               |
| Terminal                       | Every `3xx`, and every other `4xx`          |

A record over 256 KiB has its `body` cut and suffixed `... [truncated by lua]` rather than being dropped.

<Note>
  The 448 KiB batch cap is about half what the other presets use, for the same reason it is on [Axiom](/drains/axiom#caps): a Sumo message carries the body **twice** — once as `message`, once inside the nested record — so a full batch encodes to roughly 1.9× the bytes the cap was checked against.
</Note>

## Test the URL with curl

```bash theme={null}
SUMO_URL='https://endpoint4.collection.eu.sumologic.com/receiver/v1/http/<collector code>'

printf '%s\n' \
  '{"timestamp":"2026-09-12T12:59:57.644Z","level":"info","message":"Lua drain connectivity test","lua":{"id":"probe-0001","eventName":"lua.drain.test","severityText":"INFO"}}' \
  | curl -sS -i -X POST "$SUMO_URL" \
      -H 'Content-Type: application/x-ndjson' \
      -H 'X-Sumo-Name: lua-log-drain' \
      -H 'X-Sumo-Category: lua/agent-logs' \
      --data-binary @-
```

`200` means accepted; search `_sourceCategory=lua/agent-logs` in Sumo within a few seconds. `404` means the collector code is wrong.

Then do the same through the real delivery path:

```bash theme={null}
lua drains test drn_1f60ad39c825e74b0a37bb21
```

## If it isn't working

<AccordionGroup>
  <Accordion title="404 on every delivery">
    The collector code is wrong, or the HTTP Source was deleted. There is no way to read the stored code back — rotate: create a new source and `PATCH` the drain with its URL.
  </Accordion>

  <Accordion title="422 DRAIN_VALIDATION_FAILED on the endpoint">
    The URL stops short of the collector code. It must be the full HTTP Source URL Sumo gave you, ending in the code.
  </Accordion>

  <Accordion title="Everything arrives as one giant message">
    That is what an array upload looks like. Lua sends newline-delimited JSON, so this should not happen through a drain — check for a proxy between Lua and Sumo that re-wraps the body.
  </Accordion>

  <Accordion title="I need to see the collector code">
    Lua will not show it. `endpointSecretLast4` is the most a read ever carries. Recover it from Sumo Logic itself, or rotate.
  </Accordion>
</AccordionGroup>

## Next steps

<Columns cols={2}>
  <Card title="Event schema" href="/drains/event-schema">What every field under `lua` means.</Card>
  <Card title="Protecting your destination" href="/drains/protecting-your-destination">How destination credentials are stored and what verification proves.</Card>
  <Card title="Delivery guarantees" href="/drains/delivery-guarantees">Retry, drops, heartbeat, and health states.</Card>
  <Card title="lua drains" href="/reference/cli/drains">Every verb and flag.</Card>
</Columns>
