README.md
September 17, 2026 · View on GitHub
Jev Logs
Score OpenTelemetry logs before expensive LLM analysis.
Diagnostic value, priority, and routing with TypeSafe's Jev. Every record stays in your archive.
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.
| Layer | What you get |
|---|---|
| Score | A 0-100 diagnostic-value score, priority, and actionable probability. |
| Keep the pipeline | Wrap your existing exporter. Resource, scope, timestamps, and trace context stay on the record. |
| Annotate first | Annotation mode keeps every record and attaches jev.* attributes. |
| Route later | Confidently low-value events can skip a separate LLM-analysis branch. |
| Keep uncertain records | Errors, 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.
| Setting | Default | Meaning |
|---|---|---|
envFile | None | Local dotenv file to load; explicit missing files fail startup |
port | 4318 | Local HTTP receiver port |
retainBelow | 0.1 | Actionable-probability threshold, from 0 through 0.5; low value and low priority are also required to retain |
timeoutMs | 2000 | Per-record model timeout; failures remain eligible for analysis |
maxInputChars | 8000 | Maximum serialized model input; oversized input remains eligible for analysis |
forwardUrl | None | OTLP HTTP/JSON logs endpoint that receives the annotated batch, for example your Collector at http://127.0.0.1:4320/v1/logs |
forwardMode | annotate | annotate 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 |
cacheSize | 1000 | Decisions kept in memory, keyed by a hash of the redacted model input; 0 disables the cache |
cacheTtlMs | 300000 | How 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 case | How to use Jev Logs | What stays in your application |
|---|---|---|
| Triage a batch of application logs | Run the live CLI on a text file or JSONL snapshot. | Log collection, retention, and human investigation. |
| Prioritize an incident-analysis queue | Call triage() before enqueueing expensive reasoning work. | The queue, retries, and downstream analysis model. |
| Explore signal quality in an OTel backend | Wrap your exporter in annotation mode and inspect jev.* attributes. | Your exporter, backend queries, alerts, and dashboards. |
| Filter only the LLM analysis branch | Preserve an archive processor and add a separate analysis-only processor. | Archive delivery and analysis-queue delivery. |
| Keep audit events eligible for analysis | Set protected: true in standalone/CLI input or jev.protected: true in OTel attributes. | Your policy for deciding which records are protected. |
| Plan an analysis budget | Use 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, andprotected. - Processes 20 records by default, up to 100 with
--limit; total input is capped at 1 MiB. --followstreams stdin line by line with no record limit, four evaluations in flight, and ends at EOF.--jsonemits 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 --helpfor 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
| Field | Meaning |
|---|---|
value | Five-level diagnostic rubric mapped to 0–100. Not money or confidence. |
priority | critical, high, normal, or low. |
route | analyze or retain. |
actionableProbability | Jev's boolean estimate; null when no model decision is available. |
reason | model, protected, uncertain, unavailable, or rule. |
cached | true when served from the local decision cache instead of a new model call. |
rule | Name 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.1 | Jev triage | Downstream analysis | Combined monthly cost | Reduction vs. $1,000 |
|---|---|---|---|---|
| 100% | $29.40 | $1,000.00 | $1,029.40 | −2.94% |
| 50% | $29.40 | $500.00 | $529.40 | 47.06% |
| 25% | $29.40 | $250.00 | $279.40 | 72.06% |
| 10% | $29.40 | $100.00 | $129.40 | 87.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.
$\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:
- TypeSafe Jev launch pricing: $0.042/M input, free output. Structured choices, rubric scores, and probabilities make it suitable for routing without generating a paragraph.
- Vercel GPT-4.1 pricing: $2/M input, $8/M output.
- Vercel GPT-4.1 mini pricing: $0.40/M input, $1.60/M output.
- Vercel Jev integration: experimental evaluation API in AI SDK 7.0.105 onward.
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
| Component | Status |
|---|---|
npm library and npx jevlogs CLI | Available in jevlogs@0.3.0 |
| OpenTelemetry Logs integration | Annotation, analysis-branch routing, local OTLP receiver with forwarding |
| Astro website and guide | Deployed on Cloudflare |
| Automated checks | Live CI status |
| Live Jev accuracy and production savings | Not yet independently validated for this project |
jevlogs.com | Domain 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