Skip to content

feat(loki): add the Grafana Loki drain adapter - #480

Merged
HugoRCD merged 1 commit into
mainfrom
feat/loki-adapter
Aug 1, 2026
Merged

feat(loki): add the Grafana Loki drain adapter#480
HugoRCD merged 1 commit into
mainfrom
feat/loki-adapter

Conversation

@HugoRCD

@HugoRCD HugoRCD commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Stacked on #473 — uses sendEncodedDrainRequest() and defineHttpDrain({ label }) introduced there. Merge #473 first; the base retargets to main automatically.

What

evlog/lokicreateLokiDrain() pushes wide events to Loki's push API.

// Self-hosted, single tenant
createLokiDrain({ endpoint: 'http://localhost:3100' })

// Grafana Cloud — instance ID + token sent as HTTP Basic
createLokiDrain({ endpoint: 'https://logs-prod-eu-west-0.grafana.net', user: '123456', apiKey: process.env.GRAFANA_API_KEY })

// Multi-tenant self-hosted
createLokiDrain({ endpoint: 'http://loki.internal:3100', tenantId: 'team-checkout' })

The design decision that matters: cardinality

Loki indexes and bills by label cardinality — one stream per unique label combination. Labelling requestId would create a stream per request and degrade or break an instance.

So the adapter labels only service, environment, level by default, and pushes the full wide event as a JSON log line. Everything else stays queryable at read time:

{service="checkout", environment="production"}
  | json
  | status >= 500
  • labelFields promotes extra low-cardinality fields (region, tier)
  • labels adds static deployment-wide labels (cluster: prod-eu)
  • object-valued fields are never promoted, even if listed — they would explode cardinality

The docs page carries an explicit warning about this.

Ordering

Loki rejects out-of-order entries within a stream. The adapter groups events by label set and sorts each stream's entries by timestamp, so batched pushes (via evlog/pipeline) are accepted.

Auth resolution

Config Header
user + apiKey Authorization: Basic base64(user:apiKey) — Grafana Cloud
apiKey only Authorization: Bearer <apiKey>
neither no auth header — unauthenticated instance
tenantId X-Scope-OrgID (independent of the above)

Touchpoints

All 8 from .agents/skills/create-adapter/SKILL.md, adjusted for the current docs layout (4.integrate/adapters/self-hosted/, not 4.adapters/):

  1. src/adapters/loki.ts
  2. tsdown.config.ts entry
  3. package.json exports + typesVersions
  4. test/adapters/loki.test.ts — 26 tests
  5. apps/docs/content/4.integrate/adapters/self-hosted/04.loki.md
  6. adapters overview — frontmatter links, card, .env block
  7. apps/docs/skills/review-logging-patterns/SKILL.md drain table + description
  8. CI scope loki added to both semantic-pull-request.yml and pull_request_template.md

Verification

  • pnpm run test — 1664/1664 pass (26 new)
  • pnpm run lint — 0 errors (2 pre-existing max-params warnings in nitro-v3/plugin.ts)
  • pnpm run typecheck — 26/26 tasks pass
  • pnpm run api:snapshot — adds the ./loki subpath only

Tests cover URL resolution (including an endpoint that already carries the push path), all four auth shapes, label promotion and the cardinality guard, stream grouping and timestamp ordering, empty batches, env resolution, missing-endpoint skip, and the drain never throwing on a failed push.

Summary by CodeRabbit

  • New Features
    • Added Grafana Loki integration for sending events with configurable authentication, labels, batching, retries, and timeouts.
    • Supports both self-hosted Loki and Grafana Cloud deployments.
  • Documentation
    • Added setup, configuration, authentication, querying, troubleshooting, and framework integration guidance.
    • Updated adapter listings and environment variable references.
  • Tests
    • Added end-to-end validation with a local Loki and Grafana sandbox, including event querying, labels, batching, and timestamp handling.

@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
evlog-docs Ready Ready Preview, v0 Aug 1, 2026 6:58pm
evlog-render-lab Ready Ready Preview Aug 1, 2026 6:58pm
evlog-telemetry Ready Ready Preview Aug 1, 2026 6:58pm
just-use-evlog Ready Ready Preview Aug 1, 2026 6:58pm

Request Review

@changeset-bot

changeset-bot Bot commented Aug 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d84c3f3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
evlog Minor
@evlog/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Added a Grafana Loki HTTP drain adapter with configurable authentication, labels, batching, retries, and timestamps. Added package exports, unit and end-to-end tests, a local Loki/Grafana sandbox, integration documentation, and release metadata.

Changes

Grafana Loki adapter

Layer / File(s) Summary
Loki configuration and payload construction
packages/evlog/src/adapters/loki.ts
Added LokiConfig, environment resolution, URL and timestamp helpers, label extraction, stream grouping, JSON serialization, and timestamp ordering.
Drain delivery and package wiring
packages/evlog/src/adapters/loki.ts, packages/evlog/package.json, packages/evlog/tsdown.config.ts
Added Basic and Bearer authentication, tenant headers, request encoding, drain creation, direct send helpers, build configuration, and the public evlog/loki export.
Adapter behavior validation
packages/evlog/test/adapters/loki.test.ts
Added unit coverage for configuration, labels, payloads, authentication, batching, errors, and failure handling.
Sandbox and round-trip E2E coverage
packages/evlog/test/e2e/*, package.json
Added Loki and Grafana containers, datasource provisioning, seeded request events, lifecycle scripts, polling queries, and round-trip assertions.
Documentation and release metadata
apps/docs/content/4.integrate/adapters/*, apps/docs/skills/review-logging-patterns/SKILL.md, .changeset/loki-adapter.md
Added Loki navigation, setup and deployment guidance, configuration references, querying examples, troubleshooting, skill coverage, and a minor-release changeset.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Application
  participant LokiDrain
  participant LokiPushAPI
  Application->>LokiDrain: Emit event or batch
  LokiDrain->>LokiDrain: Group labels and order timestamps
  LokiDrain->>LokiPushAPI: Send authenticated JSON payload
  LokiPushAPI-->>LokiDrain: Return HTTP response
  LokiDrain-->>Application: Complete or report failure
Loading

Possibly related PRs

  • HugoRCD/evlog#351: Adds another evlog drain adapter with package exports, documentation, and adapter tests.

Suggested labels: feature, documentation

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: adding the Grafana Loki drain adapter.
Description check ✅ Passed The description explains the adapter, design decisions, supported deployments, testing, documentation, and linked stacked PR.
Docstring Coverage ✅ Passed Docstring coverage is 84.62% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/loki-adapter

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Trivy (0.72.0)

Trivy execution failed: 2026-08-01T19:02:02Z FATAL Fatal error run error: fs scan error: scan error: scan failed: failed analysis: post analysis error: post analysis error: helm scan error: fs filter error: fs filter error: walk error range error: stat .nuxt/manifest/meta/eb87d1cf-97be-423d-aab2-3fea338616b7.json: no such file or directory: range error: stat .nuxt/manifest/meta/eb87d1cf-97be-423d-aab2-3fea338616b7.json: no such file or directory


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Thank you for following the naming conventions! 🙏

@HugoRCD

HugoRCD commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@pkg-pr-new

pkg-pr-new Bot commented Aug 1, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/@evlog/cli@480
npm i https://pkg.pr.new/evlog@480
npm i https://pkg.pr.new/@evlog/nuxthub@480
npm i https://pkg.pr.new/@evlog/telemetry@480

commit: d84c3f3

HugoRCD added a commit that referenced this pull request Aug 1, 2026
`semantic-pull-request.yml` validates a PR title against the scope list as
it exists on the base branch, so a PR that introduces a new subsystem
cannot register its own scope — the check reads main's copy and rejects the
title. #480 and #481 both fail on exactly that.

Registering the two scopes ahead of the adapters unblocks them. Worth
noting in `.agents/skills/create-adapter/SKILL.md`, which currently tells
you to add the scope "in the same PR".
@HugoRCD
HugoRCD force-pushed the feat/loki-adapter branch from 5e5e23e to 746ec44 Compare August 1, 2026 17:14
@HugoRCD

HugoRCD commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/docs/content/4.integrate/adapters/self-hosted/04.loki.md`:
- Line 255: Update the “Missing endpoint” documentation to state that
createLokiDrain() logs an error and does not block the request when
LOKI_ENDPOINT and endpoint are both absent; remove the inaccurate claim that the
drain skips silently.

In `@packages/evlog/test/adapters/loki.test.ts`:
- Around line 1-49: Update the Loki adapter tests to import and use the shared
mockFetch, getFetchJson, and getFetchHeaders helpers. Replace the local fetchSpy
setup in beforeEach and remove the bodyOf and headersOf helper functions,
adapting each test assertion to the shared helper APIs while preserving existing
request-body and header coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 80b969b7-c9b1-4621-9bf5-b89a1476ba7e

📥 Commits

Reviewing files that changed from the base of the PR and between 4b021d7 and 746ec44.

⛔ Files ignored due to path filters (1)
  • packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (8)
  • .changeset/loki-adapter.md
  • apps/docs/content/4.integrate/adapters/01.overview.md
  • apps/docs/content/4.integrate/adapters/self-hosted/04.loki.md
  • apps/docs/skills/review-logging-patterns/SKILL.md
  • packages/evlog/package.json
  • packages/evlog/src/adapters/loki.ts
  • packages/evlog/test/adapters/loki.test.ts
  • packages/evlog/tsdown.config.ts

Comment thread apps/docs/content/4.integrate/adapters/self-hosted/04.loki.md Outdated
Comment thread packages/evlog/test/adapters/loki.test.ts
@HugoRCD

HugoRCD commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/evlog/test/adapters/loki.test.ts (1)

25-49: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use the shared fetch test helpers instead of local mocking.

This test file still defines and uses local fetchSpy, bodyOf, and headersOf instead of the shared helpers in packages/evlog/test/helpers/fetch. A prior review already flagged this and requested importing mockFetch, getFetchJson, and getFetchHeaders from that module and removing the local re-implementations. That request is still unaddressed in this version of the file.

Replace fetchSpy setup in beforeEach, and replace all bodyOf(fetchSpy, ...) / headersOf(fetchSpy, ...) calls throughout the file with the shared helpers.

Based on path instructions: "Use the helpers in packages/evlog/test/helpers/, including drain spies, fake timers, fetch mocks, and framework-matrix helpers" and "Import real source helpers in tests; never re-implement those helpers in test code."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/evlog/test/adapters/loki.test.ts` around lines 25 - 49, Update the
Loki adapter tests to import and use mockFetch, getFetchJson, and
getFetchHeaders from the shared fetch helpers module. Remove the local bodyOf,
headersOf, and fetchSpy implementations, replace the beforeEach fetch setup with
mockFetch, and update all bodyOf(fetchSpy, ...) and headersOf(fetchSpy, ...)
usages to the shared helper calls.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/docs/content/4.integrate/adapters/self-hosted/04.loki.md`:
- Line 36: Update the setup prompt wording in the Grafana Loki drain adapter
documentation to use “evlog-wide events” instead of “evlog wide events,”
preserving the rest of the sentence.

In `@packages/evlog/src/adapters/loki.ts`:
- Around line 133-146: Replace the btoa-based Basic auth encoding in
buildHeaders with Node’s Unicode-safe Buffer.from(...).toString('base64')
approach, preserving the existing user/apiKey credential format and
Authorization header behavior. Confirm the package targets a Node/Nitro runtime
and check nearby adapters for btoa usage before making the focused change.

---

Duplicate comments:
In `@packages/evlog/test/adapters/loki.test.ts`:
- Around line 25-49: Update the Loki adapter tests to import and use mockFetch,
getFetchJson, and getFetchHeaders from the shared fetch helpers module. Remove
the local bodyOf, headersOf, and fetchSpy implementations, replace the
beforeEach fetch setup with mockFetch, and update all bodyOf(fetchSpy, ...) and
headersOf(fetchSpy, ...) usages to the shared helper calls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c49f700d-2859-4bb1-aff4-fdfee671f791

📥 Commits

Reviewing files that changed from the base of the PR and between 746ec44 and 921b1a4.

⛔ Files ignored due to path filters (1)
  • packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (8)
  • .changeset/loki-adapter.md
  • apps/docs/content/4.integrate/adapters/01.overview.md
  • apps/docs/content/4.integrate/adapters/self-hosted/04.loki.md
  • apps/docs/skills/review-logging-patterns/SKILL.md
  • packages/evlog/package.json
  • packages/evlog/src/adapters/loki.ts
  • packages/evlog/test/adapters/loki.test.ts
  • packages/evlog/tsdown.config.ts

Comment thread apps/docs/content/4.integrate/adapters/hybrid/01.loki.md
Comment thread packages/evlog/src/adapters/loki.ts
@HugoRCD HugoRCD self-assigned this Aug 1, 2026
@HugoRCD

HugoRCD commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

Both comments reviewed.

1. evlog wide eventsevlog-wide events (04.loki.md:36) — not applied. "Wide events" is evlog's core domain term (see Wide Events); the sentence means "wide events, produced by evlog". Hyphenating it to "evlog-wide" would change the meaning to "across the whole of evlog", which is wrong. LanguageTool's compound-modifier heuristic misfires on the domain vocabulary here, and the same phrasing is used across the existing adapter pages.

2. Buffer.from(...).toString('base64') instead of btoa (loki.ts:146) — not applied as suggested, but the underlying issue is fixed. Buffer is unavailable on Cloudflare Workers and other edge runtimes without nodejs_compat, and evlog adapters are expected to run there — switching would be a regression. btoa is a web standard available on Node 16+, Deno, Bun, Workers and browsers.

The real defect behind the comment was still there though: btoa throws above U+00FF, so a password containing an accent would have failed at drain time. Credentials are now UTF-8 encoded before btoa via a small toBasicCredentials() helper, with a comment explaining why Buffer is off the table, plus a regression test asserting té:pässwörd round-trips.

`createLokiDrain()` pushes wide events to Loki's push API and covers the
three deployment shapes: self-hosted single-tenant (endpoint only),
multi-tenant (`X-Scope-OrgID`), and Grafana Cloud (instance ID + token sent
as HTTP Basic).

Loki bills and indexes by label cardinality, so the adapter labels only
`service`, `environment` and `level` by default and pushes the full wide
event as a JSON log line — everything else stays queryable with `| json`
without inflating the index. `labelFields` promotes extra low-cardinality
fields; `labels` adds static deployment-wide ones. Object-valued fields are
never promoted.

Events sharing a label set are grouped into one stream and their entries
sorted by timestamp, since Loki rejects out-of-order pushes within a
stream.

Built on `defineHttpDrain` + `sendEncodedDrainRequest`, so both the drain
and `sendBatchToLoki` share one encoder. Tests use the shared
`test/helpers/fetch` mocks rather than a local fetch spy.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/docs/content/4.integrate/adapters/01.overview.md`:
- Around line 389-394: Update the Grafana Loki zero-config environment example
to include the LOKI_TENANT_ID variable alongside LOKI_ENDPOINT and the Grafana
Cloud credentials, documenting the tenant ID required for multi-tenant
deployments.

In `@packages/evlog/test/e2e/docker-compose.yml`:
- Around line 17-18: Update the Docker Compose port mappings for the Grafana and
Loki services to bind explicitly to the loopback interface, changing the host
bindings for ports 3100 and 3001 while preserving their existing container
ports.

In `@packages/evlog/test/e2e/loki.e2e.ts`:
- Around line 43-46: Export a Loki adapter-owned header helper from the module
containing buildHeaders and toBasicCredentials, then update the E2E setup to
import and call that helper instead of constructing Authorization and
X-Scope-OrgID headers locally. Preserve the existing Basic, Bearer, and
tenant-header behavior while removing the duplicated raw btoa logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bc47033b-c749-4aeb-a3cd-4fb94f09ac18

📥 Commits

Reviewing files that changed from the base of the PR and between 921b1a4 and d84c3f3.

⛔ Files ignored due to path filters (1)
  • packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (15)
  • .changeset/loki-adapter.md
  • apps/docs/content/4.integrate/adapters/01.overview.md
  • apps/docs/content/4.integrate/adapters/hybrid/.navigation.yml
  • apps/docs/content/4.integrate/adapters/hybrid/01.loki.md
  • apps/docs/skills/review-logging-patterns/SKILL.md
  • package.json
  • packages/evlog/package.json
  • packages/evlog/src/adapters/loki.ts
  • packages/evlog/test/adapters/loki.test.ts
  • packages/evlog/test/e2e/README.md
  • packages/evlog/test/e2e/docker-compose.yml
  • packages/evlog/test/e2e/grafana/datasources.yml
  • packages/evlog/test/e2e/loki.e2e.ts
  • packages/evlog/test/e2e/seed.mjs
  • packages/evlog/tsdown.config.ts

Comment thread apps/docs/content/4.integrate/adapters/01.overview.md
Comment thread packages/evlog/test/e2e/docker-compose.yml
Comment thread packages/evlog/test/e2e/loki.e2e.ts
@HugoRCD
HugoRCD merged commit 1b0edb8 into main Aug 1, 2026
19 checks passed
@HugoRCD
HugoRCD deleted the feat/loki-adapter branch August 1, 2026 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant