README.md

September 17, 2026 · View on GitHub

Jev Logs. Keep your logs. Spend on the signal.

Jev Logs

Score OpenTelemetry logs before expensive LLM analysis.
Diagnostic value, priority, and routing with TypeSafe's Jev. Every record stays in your archive.

npm version CI status MIT license Node.js 22 or later Status: preview

Website · Guide · llms.txt · npm · Feedback


What Jev Logs does

Health checks and cache hits still hit a reasoning model if you send every event. That costs money before you start investigating.

Jev Logs scores each log first: how useful it is, how urgent it is, and whether it should go to deeper analysis. It uses TypeSafe's Jev through the Vercel AI SDK, with a TypeScript API and an OpenTelemetry exporter wrapper.

LayerWhat you get
ScoreA 0-100 diagnostic-value score, priority, and actionable probability.
Keep the pipelineWrap your existing exporter. Resource, scope, timestamps, and trace context stay on the record.
Annotate firstAnnotation mode keeps every record and attaches jev.* attributes.
Route laterConfidently low-value events can skip a separate LLM-analysis branch.
Keep uncertain recordsErrors, protected records, ambiguity, and provider failures stay eligible for analysis.

Start a local OpenTelemetry receiver with one config

Requires Node.js 22+. Install npm install jevlogs, or use npx directly. Add jevlogs.config.json at your project root:

{
  "envFile": ".env",
  "port": 4318,
  "retainBelow": 0.1,
  "timeoutMs": 2000,
  "maxInputChars": 8000
}

Create .env beside it:

AI_GATEWAY_API_KEY=your-vercel-ai-gateway-key

Add .env to your .gitignore. Commit the JSON config, not your key. Create a key in your Vercel AI Gateway dashboard. This is your Gateway key, not an OpenAI key or a Jev Logs account. Provider usage is charged to your Gateway account. The AI SDK reads it server-side to authenticate Jev requests. Applications sending OTLP logs do not need this key. The website never receives it.

Run from that project root:

npx jevlogs@latest --live

The receiver listens at http://127.0.0.1:4318/v1/logs and stays running until Ctrl+C. GET /health checks the receiver, not model availability. It prints one JSON decision per record to stdout, with available trace/span IDs and timestamp. It does not print raw log bodies or store your logs. Keep your existing archive/export pipeline.

The default config is read from your current working directory. Use --config ./config/jevlogs.json for another location; envFile resolves relative to that config. Existing environment variables take precedence over .env. --port 4320 overrides the config port. All settings are optional; you can omit envFile when your shell or secret manager already supplies AI_GATEWAY_API_KEY. Unknown configuration keys fail clearly. Never put an API key directly in the JSON.

SettingDefaultMeaning
envFileNoneLocal dotenv file to load; explicit missing files fail startup
port4318Local HTTP receiver port
retainBelow0.1Actionable-probability threshold, from 0 through 0.5; low value and low priority are also required to retain
timeoutMs2000Per-record model timeout; failures remain eligible for analysis
maxInputChars8000Maximum serialized model input; oversized input remains eligible for analysis
forwardUrlNoneOTLP HTTP/JSON logs endpoint that receives the annotated batch, for example your Collector at http://127.0.0.1:4320/v1/logs
forwardModeannotateannotate forwards every record with jev.* attributes; analysis-only forwards only records routed to analysis
rules[]Regular expressions tested against the redacted body before any model call; first match wins
cacheSize1000Decisions kept in memory, keyed by a hash of the redacted model input; 0 disables the cache
cacheTtlMs300000How long a cached decision stays valid

Send logs from your application

Use OTLP HTTP/JSON, not gRPC or binary protobuf. With the JavaScript JSON exporter:

npm install @opentelemetry/sdk-logs@0.222.0 @opentelemetry/exporter-logs-otlp-http@0.222.0
import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';

const provider = new LoggerProvider({
  processors: [new BatchLogRecordProcessor({
    exporter: new OTLPLogExporter({
      url: 'http://127.0.0.1:4318/v1/logs',
    }),
    maxExportBatchSize: 16,
    exportTimeoutMillis: 15000,
  })],
});
provider.getLogger('my-app').emit({
  body: 'GET /health returned 200',
  severityNumber: 9,
});
await provider.shutdown(); // Flush once when your application exits.

For SDKs that support HTTP/JSON configuration through environment variables:

OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://127.0.0.1:4318/v1/logs
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/json

Environment variables configure an installed exporter; they do not instrument your application automatically. Check your language SDK supports this protocol. Keep batches at 16 records for the default timeouts. The receiver accepts uncompressed JSON only, up to 1 MiB and 100 records per request, with four evaluations in flight. Concurrent batches receive HTTP 503 with Retry-After; let a retry-capable exporter handle backpressure. It binds to loopback only. This preview is a local development receiver, not a remote hosted collector.

Embed the same receiver in an npm application

import { startJevLogsServer, loadJevConfig } from 'jevlogs/server';

const server = await startJevLogsServer({
  ...await loadJevConfig(),
  async onLog({ resource, scope, logRecord, decision }) {
    // Original OTLP fields are preserved. Connect your own durable sink here.
    // decision.route tells you whether deeper LLM analysis is recommended.
    console.log(JSON.stringify({ decision, traceId: logRecord.traceId }));
  },
});
console.log(server.url);
// During application shutdown: await server.close();

onLog is called for every record, including those marked retain. Redaction applies to model input; the callback receives the original record, so apply your own storage policy. HTTP success acknowledges callback completion, not durable storage. Callback failures return OTLP partial-success counts; compliant clients do not retry rejected records in a partial-success response. Persist within your callback if delivery matters. Retried requests are not deduplicated.

Only the body and severity go into model input, after the SDK's redaction. Errors and jev.protected=true records bypass inference. Resource attributes, scope and trace IDs remain available to your callback. Default redaction is a starting point, not a complete sensitive-data policy.

For a one-time model demonstration instead of starting the receiver, run npx jevlogs --live --sample. File and stdin modes still work as finite batches.

Forward annotated logs to your collector

Point your application at Jev Logs and Jev Logs at the collector you already run. Every record continues on with jev.* attributes attached; nothing is stored in between.

{
  "envFile": ".env",
  "forwardUrl": "http://127.0.0.1:4320/v1/logs",
  "forwardMode": "annotate",
  "rules": [
    { "name": "health", "match": "^GET /health", "route": "retain" }
  ]
}
your app ──OTLP JSON──▶ jevlogs :4318 ──annotated OTLP JSON──▶ collector :4320

Forwarding happens before the local decision output, so an upstream failure returns HTTP 503 with Retry-After and your exporter resends the batch. Authentication headers for the upstream come from OTEL_EXPORTER_OTLP_LOGS_HEADERS or OTEL_EXPORTER_OTLP_HEADERS in the receiver's environment, using the standard key=value,key=value syntax. analysis-only mode forwards just the records routed to analysis, which is how you feed a separate LLM-analysis pipeline without touching your archive.

Rules run after protection and redaction and before the cache or the model, so known noise costs nothing. A retain rule produces value: 0, priority: low; an analyze rule produces the conservative fallback. ERROR/FATAL and jev.protected records are never affected by rules. Identical redacted inputs share one model call and are then served from an in-memory cache, marked cached: true. GET /stats reports requests, records, forwarded batches, cache hits, rule hits, model latency and reported input tokens.

What you can build today

Use caseHow to use Jev LogsWhat stays in your application
Triage a batch of application logsRun the live CLI on a text file or JSONL snapshot.Log collection, retention, and human investigation.
Prioritize an incident-analysis queueCall triage() before enqueueing expensive reasoning work.The queue, retries, and downstream analysis model.
Explore signal quality in an OTel backendWrap your exporter in annotation mode and inspect jev.* attributes.Your exporter, backend queries, alerts, and dashboards.
Filter only the LLM analysis branchPreserve an archive processor and add a separate analysis-only processor.Archive delivery and analysis-queue delivery.
Keep audit events eligible for analysisSet protected: true in standalone/CLI input or jev.protected: true in OTel attributes.Your policy for deciding which records are protected.
Plan an analysis budgetUse estimateSavings() with measured volume and your model prices.Actual token metering and billing verification.

Read the capability and integration guide

The guide includes the full CLI reference, JSONL schema, decision fields, configuration defaults, an archive-plus-analysis pipeline, redaction examples, cost calculation, and troubleshooting. Read the same guide on GitHub.

Try it in one command

npx jevlogs

No setup. No API key. A clearly labeled offline demo walks through four sample logs with fixed answers:

   0 / 100  low      RETAIN   GET /health returned 200 in 2ms
  25 / 100  low      RETAIN   Cache hit for product:482
 100 / 100  critical ANALYZE  Payment capture failed after three retries
  75 / 100  high     ANALYZE  Database connection pool at 94% capacity

4 logs preserved · 2 selected for analysis · 2 may skip deeper analysis.

Illustrative output. The default demo makes no network requests and does not run Jev inference.

Try real Jev

Set AI_GATEWAY_API_KEY in your environment, then choose your input:

# Evaluate the included sample logs with Jev
npx jevlogs --live --sample

# Evaluate a local log file
npx jevlogs --live --file ./app.log --limit 20

# Pipe JSONL in; get machine-readable decisions out
cat app.jsonl | npx jevlogs --live --stdin --json

# Follow a live stream; each line is evaluated as it arrives
tail -f app.log | npx jevlogs --live --stdin --follow --json

Live mode sends redacted log bodies to Vercel AI Gateway / TypeSafe and incurs provider charges. Your files remain unchanged. Get access through AI Gateway.

CLI input, limits, and exit codes
  • Accepts plain text or JSONL with body/message, severityNumber, severityText/level, and protected.
  • Processes 20 records by default, up to 100 with --limit; total input is capped at 1 MiB.
  • --follow streams stdin line by line with no record limit, four evaluations in flight, and ends at EOF.
  • --json emits one decision per line without raw bodies. Headers and summaries go to stderr.
  • Provider failures conservatively keep records for analysis and exit with code 2.
  • Usage and input errors exit with code 1.
  • Run npx jevlogs --help for the complete command reference.

Add it to your application

npm install jevlogs
import { createJevLogs } from 'jevlogs';

const jev = createJevLogs();
const decision = await jev.triage({
  body: 'Database connection pool at 94% capacity',
  severityText: 'WARN',
});

console.log(decision);
// value · priority · route · actionableProbability · reason

Requires Node.js 22+ and a server-side AI_GATEWAY_API_KEY for live evaluation. The standalone API and CLI do not require OpenTelemetry at runtime. TypeScript projects checking dependency declarations may also need the OTel peer because the package exports its exporter types. Importing the library does not run the CLI.

Already using OpenTelemetry?

npm install jevlogs @opentelemetry/sdk-logs@0.222.0

Wrap the exporter you already use:

import {
  LoggerProvider,
  BatchLogRecordProcessor,
  ConsoleLogRecordExporter,
} from '@opentelemetry/sdk-logs';
import { JevLogExporter } from 'jevlogs';

const provider = new LoggerProvider({
  processors: [
    new BatchLogRecordProcessor({
      exporter: new JevLogExporter({
        exporter: new ConsoleLogRecordExporter(), // or your OTLP exporter
        mode: 'annotate', // keeps every log
      }),
      maxExportBatchSize: 16,
      exportTimeoutMillis: 15_000,
    }),
  ],
});

provider.getLogger('app').emit({
  body: 'GET /health returned 200',
  severityNumber: 9,
});

// Flush during application shutdown, not after each log.
await provider.shutdown();

Annotate first. Route when you are ready. Keep your original archive processor, then add a separate analysis-queue exporter with mode: 'analysis-only'. Annotation alone does not reduce LLM billing; the downstream analysis pipeline must act on the routing decision.

Read the integration guide → · Open the runnable example →

What gets analyzed?

                           ┌─ Existing archive → all logs
OpenTelemetry logs ────────┤
                           └─ Jev Logs → value + priority + probability

                               ┌───────────┴────────────┐
                               ▼                        ▼
                         Deeper analysis         Retain in archive
                         Useful / uncertain      Confidently low-value

A log may skip the analysis branch only when all three conditions hold:

  • Priority is low.
  • Diagnostic value is 25 or below.
  • Actionable probability is below 0.1, the default threshold.

ERROR/FATAL records and records marked jev.protected: true always remain eligible for analysis. So do invalid outputs, timeouts, and provider failures. Nothing in this SDK deletes your archive.

Decision fields and tuning
FieldMeaning
valueFive-level diagnostic rubric mapped to 0–100. Not money or confidence.
prioritycritical, high, normal, or low.
routeanalyze or retain.
actionableProbabilityJev's boolean estimate; null when no model decision is available.
reasonmodel, protected, uncertain, unavailable, or rule.
cachedtrue when served from the local decision cache instead of a new model call.
ruleName of the matching configured rule when reason is rule.
const jev = createJevLogs({
  retainBelow: 0.1, // 0–0.5; 0 disables analysis bypass
  timeoutMs: 2000,
  maxInputChars: 8000,
  rules: [{ name: 'health', match: '^GET /health', route: 'retain' }],
  cache: { maxEntries: 1000, ttlMs: 300_000 }, // or false
});
jev.stats(); // decisions, model calls, cache hits, rule hits, latency, input tokens

The exporter defaults to four concurrent requests, configurable from 1–32. Use OTel batches of 16; larger batches may exceed export deadlines. Overlapping exports bypass scoring and forward all records unchanged. Consumers should analyze records with missing decisions.

Fallback value 100 means "conservatively keep", not model certainty. Protected severity means severityNumber >= 17 or severity text ERROR, FATAL, or CRITICAL. Reserve the jev.* attribute prefix for SDK annotations. Downstream exporter errors propagate through OTel callbacks; this package does not provide a durable queue.

Cost estimate

A $1,000 monthly analysis bill can model as $129.40 when 10% of logs still need deeper analysis. That is 87.06% lower modeled LLM spend, including Jev triage. It is not a measured production result.

The mechanism is simple: pay Jev for a small structured decision, then pay your analysis model only for the selected records. Your existing archive still keeps every log. Annotation alone does not save analysis cost; connect an analysis branch and enable routing to reduce calls.

Logs still sent to GPT-4.1Jev triageDownstream analysisCombined monthly costReduction vs. $1,000
100%$29.40$1,000.00$1,029.40−2.94%
50%$29.40$500.00$529.4047.06%
25%$29.40$250.00$279.4072.06%
10%$29.40$100.00$129.4087.06%

Illustrative assumptions: 1M logs/month, 300 input and 50 output tokens per analyzed log; GPT-4.1 at $2/$8 per million input/output tokens; Jev at $0.042/M input with free output; 400 assumed question/context tokens per record. We conservatively charge Jev for every record, even though protected errors bypass evaluation. The routing percentages and token counts are assumptions, not measured Jev Logs accuracy or usage.

A cheaper downstream model changes the economics: at GPT-4.1 mini's published $0.40/$1.60 rates, the same 10% scenario falls from $200 to $49.40 (75.3%). These examples compare routing costs, not model quality. Filtering must be evaluated against incident recall on your own logs.

Try the savings calculator

$\text{text} \text{Baseline} = \text{logs} \times (\text{input} \text{tokens} \times \text{input} \text{rate} + \text{output} \text{tokens} \times \text{output} \text{rate}) / 1\text{M} \text{Triage} = \text{logs} \times (\text{input} \text{tokens} + \text{question} \text{tokens}) \times \text{Jev} \text{input} \text{rate} / 1\text{M} \text{With} \text{Jev} = \text{triage} + \text{baseline} \times \text{fraction} \text{still} \text{analyzed} $

Use the same calculation in code with estimateSavings(). Excludes storage, ingestion, hosting, retries, discounts, caching, and additional analysis prompt overhead. Savings can be negative when too many records still need analysis. This reduces analysis spend, not archive storage charges.

Published pricing and model context

Sources checked September 16, 2026:

Check actual billing and token usage before budgeting. Provider speed and cost benchmarks are not Jev Logs benchmarks. Typed outputs can still be wrong.

Data stays under your control

Only the log body and severity enter the model request; arbitrary OTel attributes are not sent. The SDK redacts common labeled secrets, Bearer tokens, and email addresses before transmission. Supply a domain-specific redact(text) hook for your own data policy. The default is not comprehensive PII detection.

Keep Gateway credentials on the server. Mark audit, security, and compliance records as protected. Evaluate incident recall on labeled logs before enabling analysis filtering, and periodically review a sample of bypassed events. Typed outputs can still contain incorrect decisions.

Current scope and limits

This release handles Node.js log records and OTLP HTTP/JSON from any language through the local receiver. It does not include a hosted dashboard, log storage, a Collector plugin, trace/metric sampling, automatic logger instrumentation, a durable queue, or a downstream reasoning-model client. It does not explain root causes or automatically remediate incidents.

The file/stdin CLI modes process finite input after EOF, up to 1 MiB and 100 selected records. Plain text and simple JSONL are supported in those modes. --live alone runs the local OTLP HTTP/JSON receiver documented above. --stdin --follow evaluates a live stream line by line. There is no protobuf/gRPC receiver. Numeric logger levels in file inputs require normalization to OTel severity.

The default redactor transforms the model-bound copy, not the original record sent to your exporter. Zero-data-retention is requested through Gateway, while your archive policies remain your responsibility. Identical redacted inputs share one model call and are cached in memory for five minutes by default; there are no automatic model retries.

estimateSavings().retainedFraction is the fraction still sent to the downstream LLM, including protected and uncertain records; it is not your archive retention rate. Start with annotation, measure incident recall and costs, then choose whether to enable filtering.

Project status

Public preview · MIT licensed · TypeScript first

ComponentStatus
npm library and npx jevlogs CLIAvailable in jevlogs@0.3.0
OpenTelemetry Logs integrationAnnotation, analysis-branch routing, local OTLP receiver with forwarding
Astro website and guideDeployed on Cloudflare
Automated checksLive CI status
Live Jev accuracy and production savingsNot yet independently validated for this project
jevlogs.comDomain connection pending

The AI SDK's experimental_evaluate API is pinned and experimental. Jev is a hosted model; this repository makes the integration SDK open source. This is an independent project, not an official GitHub, TypeSafe, Vercel, or OpenTelemetry product.

Launch artwork

Download the compact "Introducing Jev Logs" image. Both the launch card and README banner carry a jevlogs.com signature.

Agent skill

Coding agents can learn this workflow from the jevlogs skill in this repository. It covers choosing an entry point, reading decisions, wiring the archive and analysis branches, handling real logs safely, measuring recall and cost before filtering, and troubleshooting, with runnable examples under skills/jevlogs/examples/.

Install it with the skills CLI into Claude Code, Cursor, Codex, or any supported agent:

npx skills add reachjalil/jevlogs --skill jevlogs

Then ask your agent things like "use Jev to prioritize these logs", "add Jev Logs to my OpenTelemetry pipeline", or "estimate what routing would save us". Run npx skills add reachjalil/jevlogs --list to see it before installing. Copying the skills/jevlogs folder into your agent's skills directory works too.

Build with us

Small improvements welcome: integration examples, clearer docs, reproducible bugs, and evaluations on synthetic or sanitized logs. Please don't attach credentials or sensitive production logs to issues.

git clone https://github.com/reachjalil/jevlogs.git
cd jevlogs
pnpm install --frozen-lockfile
pnpm test
pnpm check:examples
pnpm site:build

For the website, run pnpm --filter jevlogs-site dev. To inspect the publishable library, run pnpm pack.

Report a bug · Release guide · Deployment guide · Design provenance


Keep every log. Spend analysis on the records that need it.
MIT licensed. License