TypeScript logging library focused on wide events and structured error handling. pnpm monorepo (managed with Corepack).
pnpm install # install deps
pnpm run dev:prepare # generate types (required before lint/typecheck after a fresh install)
pnpm run dev # start playground
pnpm run build:package # build the package
pnpm run test # run tests (vitest)
pnpm run lint # lint all packages
pnpm run typecheck # type-check all packages
pnpm run docs # start docs site (port 3000)
cd packages/evlog && pnpm run release # publishUse
corepack enableonce so thepackageManagerfield inpackage.jsonpins the right pnpm version automatically.After a clean
pnpm install, runpnpm run dev:preparebeforepnpm run lint/typecheck. Packages like@evlog/nuxthubextend generated.nuxt/tsconfig.jsonfiles; without prepare, turbo lint fails on missing extends.
apps/playground/ Dev environment for testing
apps/docs/ Docus documentation site
packages/evlog/ Main package
src/nuxt/ Nuxt module
src/nitro/ Nitro plugin (v2 + v3)
src/vite/ Vite plugin (evlog/vite)
src/shared/ Toolkit — exposed as evlog/toolkit (NOT evlog/shared)
src/ai/ AI SDK integration (evlog/ai)
src/adapters/ Drain adapters (Axiom, OTLP, HyperDX, PostHog, Sentry, Better Stack, Datadog)
src/enrichers/ Built-in enrichers (UserAgent, Geo, RequestSize, TraceContext)
src/runtime/ Runtime code (client/, server/, utils/)
test/ Tests
.agents/skills/ Internal skills for creating adapters, enrichers, and framework integrations
- All code in TypeScript. Follow existing patterns in
packages/evlog/src/. - JSDoc on all public APIs.
- No HTML comments (
<!-- -->) in Vue templates. README.mdat root is a symlink topackages/evlog/README.md— edit the source directly.evlog/toolkitis the public entrypoint forsrc/shared/. Never useevlog/shared.evlog/browseris deprecated — useevlog/httpinstead.- Hono does not export
useLogger(). Logger access isc.get('log'). - New export? Update both
packages/evlog/package.jsonexports andpackages/evlog/tsdown.config.ts. - Creating a new adapter, enricher, or framework integration? Read the matching skill at
.agents/skills/before starting:.agents/skills/create-adapter/SKILL.md.agents/skills/create-enricher/SKILL.md.agents/skills/create-framework-integration/SKILL.md
Every user-facing change must include a changeset. Before opening a PR for features, bug fixes, or breaking changes, run pnpm changeset and commit the generated .changeset/*.md file alongside the code.
- When to add a changeset: any change that affects the public API, adds a feature, fixes a bug, or introduces a breaking change. If a consumer of evlog would notice the difference, it needs a changeset.
- When you can skip: internal-only changes (CI config, docs typos, test refactors, devDeps bumps) that don't touch the published package.
- Bump type:
patchfor fixes,minorfor features,majorfor breaking changes. - Description: write from the consumer's perspective — what changed and how to use it. See existing changesets in
.changeset/for tone and level of detail.
A PR without a changeset for a user-facing change will not be merged.
PR titles and commits follow Conventional Commits. The CI source of truth is .github/workflows/semantic-pull-request.yml (lints PR titles via amannn/action-semantic-pull-request); .github/pull_request_template.md mirrors the same lists for contributors.
- Subject must not start with an uppercase letter.
feat: add stream server✓ —feat: Add stream server✗. - Omit the scope when the change is cross-cutting (touches multiple subsystems, or is repo-wide). Don't use
evlogas a scope: the whole monorepo is evlog, so a no-scope title already means "evlog itself". - Use a scope only to point at one subsystem. Adapters get their own scope (one per entrypoint, e.g.
axiom,datadog,fs); framework integrations get the framework's name (nuxt,next,hono, ...); core internals (logger, pipeline, error, redact, catalog) go undercore. - When you add a new subsystem (adapter, integration, top-level entrypoint), add its scope to both the workflow and the template in the same PR. Keep both lists alphabetically sorted.
MDC animation components (e.g. EnricherChain, DrainFanOut, StreamBus) follow a strict set of rules:
- Fixed outer size, always. The component must occupy the same height and width from t=0 to the end of the loop. Layout below the animation must not shift while the user reads the page.
- Pre-allocate every slot. Lines, rows, frames, buffer cells must all exist in the DOM from the start. Animate
opacity,color,transform— nevermax-height: 0 → N, never conditionalv-ifon structural elements. - Use
useTimedSequencefrom~/composables/useTimedSequencefor the timeline. Honorprefers-reduced-motionby snapping to the final state. - Wrap in
<Motion>frommotion-vwithnot-prose my-8and anIntersectionObserverso the animation starts when scrolled into view. - Header bar with status pill + play/pause + restart buttons (mirror
DrainFanOut.vue). - Compact by default:
text-[10px]for body,text-[9px]for footers/labels,leading-tightorleading-snug,py-1.5/py-2headers/footers,space-y-0.5or none,gap-1.5or smaller. The doc page width (sidebar + TOC) is narrow; aim for a final height under ~280px. - Use
<div>(not<ol>/<li>) for repeating slots — list elements collide with grid layout in Docus. - No viewport-dependent layout shift. Stick to a single column at any width or use
sm:for the optional split — neverlg:(the doc content area never reaches thelg:breakpoint).
Tests live in packages/evlog/test/ (mirrors src/) and use Vitest. Read packages/evlog/test/README.md before writing or editing tests — it has the file layout, the framework runtime fidelity matrix, and the helper decision table.
pnpm run test # full suite (~1.5s)
pnpm --filter evlog exec vitest run test/path/to/file # single test file
pnpm test:coverage # with thresholds; :open for HTML
pnpm api:snapshot # diff public API surface; :update to accept
pnpm mutate # Stryker (slow; weekly cron in CI)
pnpm test:e2e # adapters vs real endpointsRules:
- Every change has a matching test. Bug fixes require a failing regression test before the fix.
- Always import real source helpers, never re-implement them in tests.
- Use the helpers in
test/helpers/(drain spies, fake timers, fetch mock, framework matrix). The full decision table is intest/README.md. - Framework tests must use the framework's real request driver (supertest,
app.inject,app.handle,Test.createTestingModule, ...) — see the fidelity matrix intest/README.md.
A task is complete when all of the following pass:
pnpm run lint,pnpm run typecheck,pnpm run testexit 0- The change has a matching test (bug fix → failing regression first, then the fix)
pnpm test:coveragestays above the configured thresholds; if you changed a public export, thepnpm api:snapshotdiff was reviewed- New public APIs have JSDoc
- New exports are registered in
package.json#exports,package.json#typesVersions, andtsdown.config.ts - If adapter/enricher/integration: the matching
.agents/skills/create-*/SKILL.mdwas followed - A changeset is included for any user-facing change (
pnpm changeset)
Always do:
- Run lint, typecheck, and test before reporting done
- Follow existing code patterns — read neighboring files before writing new ones
- Use the skills at
.agents/skills/for new adapters, enrichers, or integrations - Add a changeset (
pnpm changeset) for every user-facing change — features, bug fixes, breaking changes
Ask first:
- Adding new dependencies
- Changing package exports or build config
- Architectural decisions that affect multiple packages
Never:
- Commit secrets,
.envfiles, or API keys - Skip tests or lint to "fix later"
- Ship a feature, bug fix, or refactor without a matching test
- Add HTML comments in Vue
<template>blocks - Modify
node_modules/or generated files - Open a PR for a user-facing change without a changeset
Default: anything that stays on the local clone is fine, anything that touches the remote or GitHub requires an explicit instruction in the task at hand. Never act on assumption — if the maintainer didn't ask for a push or a PR, prepare the branch locally and stop there.
OK (local-only, no ask needed):
git branch,git checkout,git switch,git checkout -b— create and move between branches freelygit add,git commit— staging and local commits are finegit status,git diff,git log,git show,git stash,git restore,git reset(local only) — read and rearrange the working treegh pr view,gh pr list,gh pr diff,gh issue view,gh run view— read-only GitHub queries
OK when the maintainer explicitly asks (in the current task):
git push -u origin <feature-branch>— push a feature branch you just preparedgit push --force-with-lease origin <feature-branch>— only on a feature branch you authored, after a clean rebasegh pr create --base main --head <feature-branch>— open a PR- Write a PR title (Conventional Commits, see above) and a PR body — keep the body factual, mirror the changeset, reference the issue (
Closes #X); no marketing copy
Never (no exceptions, even when asked):
- Push directly to
main(ormaster) — protected, always goes through a PR git push --forcewithout--with-lease,git push --tagsgh pr merge,gh pr close,gh pr review,gh issue create,gh issue edit,gh release create- Write a changelog entry, release note, or commit message body with multi-paragraph narrative — the changeset is the source of truth; commit subjects stay single-line, PR bodies stay short
- Add a
Co-authored-by,Signed-off-by, "Generated with…", "🤖", or any signature/attribution that names an agent, model, or tool — the work is the maintainer's, full stop
- Unsure about architecture → read the relevant SKILL.md or ask
- Unclear requirements → ask a clarifying question before making large speculative changes
You are expected to submit feedback. Report DX friction, documentation gaps, or anything that made your task harder than it should be. When in doubt, report it — no feedback being submitted is itself a problem.
Submit feedback mid-work. Don't wait until the task is done or for the user to ask.
Update this file when you encounter:
- A recurring mistake or easy-to-get-wrong pattern
- Explicit guidance from the maintainer
- A new convention that should be applied consistently