CLI

evlog map

RulesScoring
A static observability score for your app, Lighthouse-style. Scans every entry point in a Nuxt, Nitro, Next.js, or TanStack Start project, scores wide-event coverage, and names the entry points to fix first.

evlog map reads your project on disk and answers one question: if something goes wrong in production tonight, which parts of this app will be able to tell you why?

It finds every entry point — API handlers, pages that fetch, middleware, scheduled jobs, server actions — checks each one for wide-event coverage, scores it, and names the three worth fixing first. Nothing runs, nothing is instrumented, no traffic is needed: it is static analysis over your source.

evlog map·idle
scan
0/147
1api/auth/[...all].tsA
······
2…ment-declined.get.ts$
······
…owser-ingest.post.ts
······
3…i/auth/login.post.tsA
······
…st/wide-event.get.ts
······
··/100good
5 of 29 shown ▲ 76 → 86 by fixing the 3 above
Terminal
evlog map
Early days. The foundation is solid — AST-based, tested, with a versioned JSON contract — but the rule set is young and only four frameworks have adapters. A release that sharpens a rule can change a verdict on code you did not touch, so pin the CLI version if you gate a pull request on the score.

Raise my evlog map score

The report

This is a real run against the evlog playground, a Nuxt app with 29 entry points:

evlog map
▀▀█ █▀▀   score /100              evlog-playground · Nuxt
  █ █▀█   ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▱▱▱▱▱    29 entry points scanned
  ▀ ▀▀▀   good                    ▂▂▂▃▃▃▃▃▄▆▆▆▆▆███████████████

COVERAGE
● API handlers       ▰▰▰▰▰▰▰▰▱▱   79  13 of 28 have gaps
● Middleware & jobs  ▰▰▰▰▰▱▱▱▱▱   45  run without any logging
● Money & auth       ▰▰▰▰▰▰▰▱▱▱   71  missing audit trails

FIX FIRST
1. ANY    /api/auth/:all* A — touches auth and logs nothing
   server/api/auth/[...all].ts:1 · evlog.dev/learn/wide-events
2. GET    /api/test/catalog/payment-declined $ — moves money with no audit trail
   server/api/test/catalog/payment-declined.get.ts:11 · evlog.dev/use-cases/audit/overview
3. POST   /api/auth/login A — missing log.audit
   server/api/auth/login.post.ts:1 · evlog.dev/use-cases/audit/overview

THEN
· add useLogger + log.set to /api/test/browser-ingest +4
· add log.audit to /api/payment/process, /api/test/better-auth/whoami
· add log.set to /api/audit/deny
· add log.set + createError({ why, fix }) to /api/audit/with-audit
· add useLogger + log.set + createError({ why, fix }) to /api/test/h3-error
· add createError({ why, fix }) to /api/test/tail-sampling/error

✓ Already solid: /api/audit/catalog/invoice-refund +14
▲ 76 → 86 by fixing the 3 above

────────────────────────────
evlog.map.json updated · how this score works → evlog.dev/cli/scoring
▸ evlog map --all every entry point · evlog map <file> inspect one
  --min-score 80 CI gate

Read from the top down, each block answers a different question.

The score

The big digits are the global score out of 100: a weighted average of every entry point, where money and auth routes count double and pages count half. The word underneath is its grade — excellent, good, needs work, or at risk.

The row of blocks on the right is every entry point in the project, worst on the left, best on the right. A wall of tall blocks on the right with a few short ones on the left is a healthy app with a handful of blind spots. A flat low row is an app with no instrumentation at all. On a large project the row is sampled down to the width of your terminal, showing the worst entry point in each bucket.

The full calculation — per-rule weights, route weighting, and grade thresholds — is on Scoring.

Coverage

Where the score comes from, grouped the way you think about your app rather than by rule. Areas only appear when the project has them: a Nuxt app with no pages/ never shows a Pages row.

AreaWhat it covers
API handlersServer route handlers
PagesPages that fetch data server-side
Middleware & jobsMiddleware, scheduled tasks, server actions
Money & authEvery entry point the sensitivity classifier flagged, wherever it lives

Money & auth deliberately overlaps the other rows. It is the group where a missing event costs the most, so it gets its own line and a marker when it scores below the app as a whole.

Fix first

The three entry points with the most to gain, sensitive ones first, worst score first. Each gets the method, the path, a sensitivity marker, a sentence naming the actual problem, and the file, the line, and a docs link for the rule that failed.

$ is money, A is auth, @ is PII. These come from the sensitivity classifier, and a flagged entry point is held to one extra requirement: an audit trail.

Then

Everything else with a gap, batched by the fix rather than listed one entry point per line. add useLogger + log.set to /api/test/browser-ingest +4 means five entry points need the same two lines, so you can work through them in one pass.

Going further

A separate section that only appears when the project already uses an evlog feature that an entry point is not benefiting from:

Excerpt
GOING FURTHER
you already use these — your app could get more out of them
+ Should these duplicated errors become catalog entries? — 2 entry points
   server/api/orders/[id].get.ts:4 · evlog.dev/learn/catalogs
Suggestions never change the score.

These are suggestions, not gaps. They are gated on evidence that the feature is already in use somewhere in the project, they are never counted as failures, and a --min-score gate can never fail because of one. See opportunities.

The last two lines

✓ Already solid names entry points with nothing left to fix, so a good app gets told so. ▲ 76 → 86 by fixing the 3 above is the score you would land on if you fixed exactly what is under FIX FIRST — the reason to start there rather than anywhere else.

Three views

The default report answers "how am I doing". Two flags answer the other two questions you will have.

Every entry point

--all prints the check matrix: one row per entry point, worst first, grouped by directory.

evlog map --all (trimmed)
evlog-playground · Nuxt · 76/100 · 29 entry points, worst first

                                            log   ctx   err   audit catch fetch
server/
├─ api/auth/[...all].ts   ▰▰▱▱▱▱▱▱▱▱  20 A  ●     ●     ·     ●     ·     ·
├─ …ment-declined.get.ts  ▰▰▱▱▱▱▱▱▱▱  20 $  ●     ●     ●     ●     ·     ·
├─ …test/h3-error.get.ts  ▰▰▰▱▱▱▱▱▱▱  25    ●     ●     ●     ·     ·     ·
├─ …owser-ingest.post.ts  ▰▰▰▰▰▱▱▱▱▱  45    ●     ●     ·     ·     ·     ·
├─ …t/with-audit.post.ts  ▰▰▰▰▰▰▰▱▱▱  65    ●     ●     ●     ·     ●     ·
├─ …i/auth/login.post.ts  ▰▰▰▰▰▰▰▰▱▱  75 A  ●     ●     ·     ●     ·     ·
├─ …ampling/error.get.ts  ▰▰▰▰▰▰▰▰▱▱  80    ●     ●     ●     ·     ·     ·
└─ …st/wide-event.get.ts  ▰▰▰▰▰▰▰▰▰▰ 100    ●     ●     ·     ·     ·     ·

● covered   ● gap   ·  not applicable   $ money   A auth   @ pii
what each column checks → evlog.dev/cli/rules

One column per rule, in the order they cost points. A dot means the rule does not apply here — a handler that throws nothing is not asked whether its errors carry why and fix, and that is a dot rather than a free pass. A hollow is a check you disabled with a comment. Every column is explained on Rules.

One entry point

Pass a route or a file path to get the full explanation of a single entry point:

Terminal
evlog map server/api/auth/login.post.ts
# or by route
evlog map /api/auth/login
evlog map server/api/auth/login.post.ts
POST   /api/auth/login A   ▰▰▰▰▰▰▰▰▱▱ 75/100
server/api/auth/login.post.ts · Nuxt

WHY THIS FILE IS SCANNED
▍ POST /api/auth/login — server handler

FLAGGED SENSITIVE BECAUSE
▍ auth: path says "auth"

CHECKS
✓ useLogger  wide event emitted per request
✓ log.set    context attached with log.set()
✗ log.audit  sensitive action with no audit trail  evlog.dev/use-cases/audit/overview

SUGGESTED SHAPE — Nuxt
│ export default defineEventHandler(async (event) => {
│   log.audit({
│     action: 'auth.login',
│     actor: { type: 'user', id: user.id },
│   })
│ })

▲ fixing this entry point: 75 → 100

Four things worth noting:

  • Why this file is scanned says what the CLI thinks this file is. If that is wrong, everything below it is wrong, and this is where you see it.
  • Flagged sensitive because shows the exact reason, so a false positive is one line to spot instead of a mystery.
  • Suggested shape is composed from the rules that actually failed, in your framework's idiom. The audit action is read off the route — /api/auth/login suggests auth.login, not a placeholder.
  • When every requirement passes but there are suggestions left, the verdict splits in two: nothing to fix, and n things left to gain.

What counts as an entry point

Detection is per framework, from the file layout. --framework overrides it when detection guesses wrong.

server/api/**        → API handler, path prefixed /api, method from the filename suffix
server/routes/**     → API handler, path as written
pages/**/*.vue       → page
server/middleware/** → middleware
server/tasks/**      → scheduled job

Both app/ and src/app/ are supported for Next.js, and src/middleware.ts alongside middleware.ts. A route.ts with no HTTP method exports is listed once with the method ANY.

Kinds show up in the report as ANY, PAGE, MID, ACT (server action), CRON, and WS, or as the HTTP method when the entry point has one.

evlog's own client-log ingest endpoints are exempt. They are plumbing, not app code, so every rule reports n/a for them rather than failing.

When a verdict is wrong

A check you disagree with should cost you one comment, not your CI gate:

server/api/health.get.ts
// evlog-map-disable-next-line wide-event, context -- liveness probe, deliberately silent
export default defineEventHandler(() => ({ ok: true }))

The check becomes n/a with your reason attached, so it stops costing score — and the report says how many checks the project has disabled, so a high score never hides an app that logs nothing. Full syntax on Rules.

Flags

FlagDefaultWhat it does
<entry>Inspect one entry point, by route path or file path
--alloffEvery entry point as a check matrix
--min-score <n>offExit 1 when the global score is below n
--framework <name>detectedForce nuxt, nitro, next, or tanstack-start
--no-writewritesSkip writing evlog.map.json
--verboseoffShow per-file parse warnings
--cwd <dir>currentScan another directory
--jsonoffThe full map as JSON on stdout

evlog.map.json

Every run writes evlog.map.json to the project root: the score, the framework, and every entry point with its checks, its suggestions, its sensitivity, and its own score. It is the same data --json prints.

Use --no-write when you do not want the file — CI runs, or a quick look at somebody else's project. The file is a build artifact, so gitignore it unless you have a reason to track it.

What it will not tell you

Static analysis has limits, and knowing them is the difference between trusting the score and being annoyed by it.

  • It reads code, not traffic. An entry point can score 100 and still emit useless events at runtime if the context it attaches is wrong. The score says the shape is there.
  • It follows imports one hop. Helpers re-exported through a local module are resolved — import { useLogger } from '@/lib/evlog' counts — but a logger passed through three layers of your own abstraction may not be recognised.
  • Four frameworks. Nuxt, Nitro, Next.js App Router, and TanStack Start have adapters. Other frameworks that evlog integrates with are not scanned yet.
  • Sensitivity is a heuristic. It reads imports and route paths. evlog map <file> always shows the reason, so a wrong call is visible rather than silent.

Next

  • Rules — every check, what satisfies it, and the exact fix
  • Scoring — weights, grades, and the sensitivity classifier
  • CI — gate a pull request on the score