evlog 0.0.0-reserved → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,421 @@
1
+ # evlog
2
+
3
+ [![npm version](https://img.shields.io/npm/v/evlog?color=black)](https://npmjs.com/package/evlog)
4
+ [![npm downloads](https://img.shields.io/npm/dm/evlog?color=black)](https://npm.chart.dev/evlog)
5
+ [![CI](https://img.shields.io/github/actions/workflow/status/HugoRCD/evlog/ci.yml?branch=main&color=black)](https://github.com/HugoRCD/evlog/actions/workflows/ci.yml)
6
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/evlog?color=black&label=size)](https://bundlephobia.com/package/evlog)
7
+ [![Nuxt](https://img.shields.io/badge/Nuxt-black?logo=nuxt&logoColor=white)](https://nuxt.com/)
8
+ [![license](https://img.shields.io/github/license/HugoRCD/evlog?color=black)](https://github.com/HugoRCD/evlog/blob/main/LICENSE)
9
+
10
+ **Your logs are lying to you.**
11
+
12
+ A single request generates 10+ log lines. When production breaks at 3am, you're grep-ing through noise, praying you'll find signal. Your errors say "Something went wrong" – thanks, very helpful.
13
+
14
+ **evlog fixes this.** One log per request. All context included. Errors that explain themselves.
15
+
16
+ ## Why evlog?
17
+
18
+ ### The Problem
19
+
20
+ ```typescript
21
+ // server/api/checkout.post.ts
22
+
23
+ // ❌ Scattered logs - impossible to debug
24
+ console.log('Request received')
25
+ console.log('User:', user.id)
26
+ console.log('Cart loaded')
27
+ console.log('Payment failed') // Good luck finding this at 3am
28
+
29
+ throw new Error('Something went wrong') // 🤷‍♂️
30
+ ```
31
+
32
+ ### The Solution
33
+
34
+ ```typescript
35
+ // server/api/checkout.post.ts
36
+ import { useLogger } from 'evlog'
37
+
38
+ // ✅ One comprehensive event per request
39
+ export default defineEventHandler(async (event) => {
40
+ const log = useLogger(event) // Auto-injected by evlog
41
+
42
+ log.set({ user: { id: user.id, plan: 'premium' } })
43
+ log.set({ cart: { items: 3, total: 9999 } })
44
+ log.error(error, { step: 'payment' })
45
+
46
+ // Emits ONE event with ALL context + duration (automatic)
47
+ })
48
+ ```
49
+
50
+ Output:
51
+
52
+ ```json
53
+ {
54
+ "timestamp": "2025-01-24T10:23:45.612Z",
55
+ "level": "error",
56
+ "service": "my-app",
57
+ "method": "POST",
58
+ "path": "/api/checkout",
59
+ "duration": "1.2s",
60
+ "user": { "id": "123", "plan": "premium" },
61
+ "cart": { "items": 3, "total": 9999 },
62
+ "error": { "message": "Card declined", "step": "payment" }
63
+ }
64
+ ```
65
+
66
+ ### Built for AI-Assisted Development
67
+
68
+ We're in the age of AI agents writing and debugging code. When an agent encounters an error, it needs **clear, structured context** to understand what happened and how to fix it.
69
+
70
+ Traditional logs force agents to grep through noise. evlog gives them:
71
+ - **One event per request** with all context in one place
72
+ - **Self-documenting errors** with `why` and `fix` fields
73
+ - **Structured JSON** that's easy to parse and reason about
74
+
75
+ Your AI copilot will thank you.
76
+
77
+ ---
78
+
79
+ ## Installation
80
+
81
+ ```bash
82
+ npm install evlog
83
+ ```
84
+
85
+ ## Nuxt Integration
86
+
87
+ The recommended way to use evlog. Zero config, everything just works.
88
+
89
+ ```typescript
90
+ // nuxt.config.ts
91
+ export default defineNuxtConfig({
92
+ modules: ['evlog/nuxt'],
93
+
94
+ evlog: {
95
+ env: {
96
+ service: 'my-app',
97
+ environment: process.env.NODE_ENV,
98
+ },
99
+ },
100
+ })
101
+ ```
102
+
103
+ That's it. Now use `useLogger(event)` in any API route:
104
+
105
+ ```typescript
106
+ // server/api/checkout.post.ts
107
+ import { useLogger, defineError } from 'evlog'
108
+
109
+ export default defineEventHandler(async (event) => {
110
+ const log = useLogger(event)
111
+
112
+ // Authenticate user and add to wide event
113
+ const user = await requireAuth(event)
114
+ log.set({ user: { id: user.id, plan: user.plan } })
115
+
116
+ // Load cart and add to wide event
117
+ const cart = await getCart(user.id)
118
+ log.set({ cart: { items: cart.items.length, total: cart.total } })
119
+
120
+ // Process payment
121
+ try {
122
+ const payment = await processPayment(cart, user)
123
+ log.set({ payment: { id: payment.id, method: payment.method } })
124
+ } catch (error) {
125
+ log.error(error, { step: 'payment' })
126
+
127
+ throw defineError({
128
+ message: 'Payment failed',
129
+ why: error.message,
130
+ fix: 'Try a different payment method or contact your bank',
131
+ })
132
+ }
133
+
134
+ // Create order
135
+ const order = await createOrder(cart, user)
136
+ log.set({ order: { id: order.id, status: order.status } })
137
+
138
+ return order
139
+ // log.emit() called automatically at request end
140
+ })
141
+ ```
142
+
143
+ The wide event emitted at the end contains **everything**:
144
+
145
+ ```json
146
+ {
147
+ "timestamp": "2026-01-24T10:23:45.612Z",
148
+ "level": "info",
149
+ "service": "my-app",
150
+ "method": "POST",
151
+ "path": "/api/checkout",
152
+ "duration": "1.2s",
153
+ "user": { "id": "user_123", "plan": "premium" },
154
+ "cart": { "items": 3, "total": 9999 },
155
+ "payment": { "id": "pay_xyz", "method": "card" },
156
+ "order": { "id": "order_abc", "status": "created" },
157
+ "status": 200
158
+ }
159
+ ```
160
+
161
+ ## Nitro Integration
162
+
163
+ Works with **any framework powered by Nitro**: Nuxt, Analog, Vinxi, SolidStart, TanStack Start, and more.
164
+
165
+ ```typescript
166
+ // nitro.config.ts
167
+ export default defineNitroConfig({
168
+ plugins: ['evlog/nitro'],
169
+ })
170
+ ```
171
+
172
+ Same API, same wide events:
173
+
174
+ ```typescript
175
+ // routes/api/documents/[id]/export.post.ts
176
+ import { useLogger, defineError } from 'evlog'
177
+
178
+ export default defineEventHandler(async (event) => {
179
+ const log = useLogger(event)
180
+
181
+ // Get document ID from route params
182
+ const documentId = getRouterParam(event, 'id')
183
+ log.set({ document: { id: documentId } })
184
+
185
+ // Parse request body for export options
186
+ const body = await readBody(event)
187
+ log.set({ export: { format: body.format, includeComments: body.includeComments } })
188
+
189
+ // Load document from database
190
+ const document = await db.documents.findUnique({ where: { id: documentId } })
191
+ if (!document) {
192
+ throw defineError({
193
+ message: 'Document not found',
194
+ why: `No document with ID "${documentId}" exists`,
195
+ fix: 'Check the document ID and try again',
196
+ })
197
+ }
198
+ log.set({ document: { id: documentId, title: document.title, pages: document.pages.length } })
199
+
200
+ // Generate export
201
+ try {
202
+ const exportResult = await generateExport(document, body.format)
203
+ log.set({ export: { format: body.format, size: exportResult.size, pages: exportResult.pages } })
204
+
205
+ return { url: exportResult.url, expiresAt: exportResult.expiresAt }
206
+ } catch (error) {
207
+ log.error(error, { step: 'export-generation' })
208
+
209
+ throw defineError({
210
+ message: 'Export failed',
211
+ why: `Failed to generate ${body.format} export: ${error.message}`,
212
+ fix: 'Try a different format or contact support',
213
+ })
214
+ }
215
+ // log.emit() called automatically - outputs one comprehensive wide event
216
+ })
217
+ ```
218
+
219
+ Output when the export completes:
220
+
221
+ ```json
222
+ {
223
+ "timestamp": "2025-01-24T14:32:10.123Z",
224
+ "level": "info",
225
+ "service": "document-api",
226
+ "method": "POST",
227
+ "path": "/api/documents/doc_123/export",
228
+ "duration": "2.4s",
229
+ "document": { "id": "doc_123", "title": "Q4 Report", "pages": 24 },
230
+ "export": { "format": "pdf", "size": 1240000, "pages": 24 },
231
+ "status": 200
232
+ }
233
+ ```
234
+
235
+ ## Structured Errors
236
+
237
+ Errors should tell you **what** happened, **why**, and **how to fix it**.
238
+
239
+ ```typescript
240
+ // server/api/repos/sync.post.ts
241
+ import { useLogger, defineError } from 'evlog'
242
+
243
+ export default defineEventHandler(async (event) => {
244
+ const log = useLogger(event)
245
+
246
+ log.set({ repo: { owner: 'acme', name: 'my-project' } })
247
+
248
+ try {
249
+ const result = await syncWithGitHub()
250
+ log.set({ sync: { commits: result.commits, files: result.files } })
251
+ return result
252
+ } catch (error) {
253
+ log.error(error, { step: 'github-sync' })
254
+
255
+ throw defineError({
256
+ message: 'Failed to sync repository',
257
+ why: 'GitHub API rate limit exceeded',
258
+ fix: 'Wait 1 hour or use a different token',
259
+ link: 'https://docs.github.com/en/rest/rate-limit',
260
+ cause: error,
261
+ })
262
+ }
263
+ })
264
+ ```
265
+
266
+ Console output (development):
267
+
268
+ ```
269
+ Error: Failed to sync repository
270
+ Why: GitHub API rate limit exceeded
271
+ Fix: Wait 1 hour or use a different token
272
+ More info: https://docs.github.com/en/rest/rate-limit
273
+ ```
274
+
275
+ ## Standalone TypeScript
276
+
277
+ For scripts, workers, or any TypeScript project:
278
+
279
+ ```typescript
280
+ // scripts/migrate.ts
281
+ import { initLogger, log, createRequestLogger } from 'evlog'
282
+
283
+ // Initialize once at script start
284
+ initLogger({
285
+ env: {
286
+ service: 'migration-script',
287
+ environment: 'production',
288
+ },
289
+ })
290
+
291
+ // Simple logging
292
+ log.info('migration', 'Starting database migration')
293
+ log.info({ action: 'migration', tables: ['users', 'orders'] })
294
+
295
+ // Or use request logger for a logical operation
296
+ const migrationLog = createRequestLogger({ action: 'full-migration' })
297
+
298
+ migrationLog.set({ tables: ['users', 'orders', 'products'] })
299
+ migrationLog.set({ rowsProcessed: 15000 })
300
+ migrationLog.emit()
301
+ ```
302
+
303
+ ```typescript
304
+ // workers/sync-job.ts
305
+ import { initLogger, createRequestLogger, defineError } from 'evlog'
306
+
307
+ initLogger({
308
+ env: {
309
+ service: 'sync-worker',
310
+ environment: process.env.NODE_ENV,
311
+ },
312
+ })
313
+
314
+ async function processSyncJob(job: Job) {
315
+ const log = createRequestLogger({ jobId: job.id, type: 'sync' })
316
+
317
+ try {
318
+ log.set({ source: job.source, target: job.target })
319
+
320
+ const result = await performSync(job)
321
+ log.set({ recordsSynced: result.count })
322
+
323
+ return result
324
+ } catch (error) {
325
+ log.error(error, { step: 'sync' })
326
+ throw error
327
+ } finally {
328
+ log.emit()
329
+ }
330
+ }
331
+ ```
332
+
333
+ ## API Reference
334
+
335
+ ### `initLogger(config)`
336
+
337
+ Initialize the logger. Required for standalone usage, automatic with Nuxt/Nitro plugins.
338
+
339
+ ```typescript
340
+ initLogger({
341
+ env: {
342
+ service: string // Service name
343
+ environment: string // 'production' | 'development' | 'test'
344
+ version?: string // App version
345
+ commitHash?: string // Git commit
346
+ region?: string // Deployment region
347
+ },
348
+ pretty?: boolean // Pretty print (default: true in dev)
349
+ })
350
+ ```
351
+
352
+ ### `log`
353
+
354
+ Simple logging API.
355
+
356
+ ```typescript
357
+ log.info('tag', 'message') // Tagged log
358
+ log.info({ key: 'value' }) // Wide event
359
+ log.error('tag', 'message')
360
+ log.warn('tag', 'message')
361
+ log.debug('tag', 'message')
362
+ ```
363
+
364
+ ### `createRequestLogger(options)`
365
+
366
+ Create a request-scoped logger for wide events.
367
+
368
+ ```typescript
369
+ const log = createRequestLogger({
370
+ method: 'POST',
371
+ path: '/checkout',
372
+ requestId: 'req_123',
373
+ })
374
+
375
+ log.set({ user: { id: '123' } }) // Add context
376
+ log.error(error, { step: 'x' }) // Log error with context
377
+ log.emit() // Emit final event
378
+ log.getContext() // Get current context
379
+ ```
380
+
381
+ ### `defineError(options)`
382
+
383
+ Create a structured error.
384
+
385
+ ```typescript
386
+ defineError({
387
+ message: string // What happened
388
+ why?: string // Why it happened
389
+ fix?: string // How to fix it
390
+ link?: string // Documentation URL
391
+ cause?: Error // Original error
392
+ })
393
+ ```
394
+
395
+ ## Framework Support
396
+
397
+ evlog works with any framework powered by [Nitro](https://nitro.unjs.io/):
398
+
399
+ | Framework | Integration |
400
+ |-----------|-------------|
401
+ | **Nuxt** | `modules: ['evlog/nuxt']` |
402
+ | **Analog** | `plugins: ['evlog/nitro']` |
403
+ | **Vinxi** | `plugins: ['evlog/nitro']` |
404
+ | **SolidStart** | `plugins: ['evlog/nitro']` |
405
+ | **TanStack Start** | `plugins: ['evlog/nitro']` |
406
+ | **Standalone Nitro** | `plugins: ['evlog/nitro']` |
407
+
408
+ ## Philosophy
409
+
410
+ Inspired by [Logging Sucks](https://loggingsucks.com/) by [Boris Tane](https://github.com/boristane).
411
+
412
+ 1. **Wide Events**: One log per request with all context
413
+ 2. **Structured Errors**: Errors that explain themselves
414
+ 3. **Request Scoping**: Accumulate context, emit once
415
+ 4. **Pretty for Dev, JSON for Prod**: Human-readable locally, machine-parseable in production
416
+
417
+ ## License
418
+
419
+ [MIT](./LICENSE)
420
+
421
+ Made by [@HugoRCD](https://github.com/HugoRCD)
@@ -0,0 +1,47 @@
1
+ import { ErrorOptions } from './types.mjs';
2
+
3
+ /**
4
+ * Structured error with context for better debugging
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * throw new EvlogError({
9
+ * message: 'Failed to sync repository',
10
+ * why: 'GitHub API rate limit exceeded',
11
+ * fix: 'Wait 1 hour or use a different token',
12
+ * link: 'https://docs.github.com/en/rest/rate-limit',
13
+ * cause: originalError,
14
+ * })
15
+ * ```
16
+ */
17
+ declare class EvlogError extends Error {
18
+ readonly why?: string;
19
+ readonly fix?: string;
20
+ readonly link?: string;
21
+ constructor(options: ErrorOptions | string);
22
+ /**
23
+ * Format error for console output with colors
24
+ */
25
+ toString(): string;
26
+ /**
27
+ * Convert to plain object for JSON serialization
28
+ */
29
+ toJSON(): Record<string, unknown>;
30
+ }
31
+ /**
32
+ * Create an EvlogError (functional alternative to `new EvlogError()`)
33
+ *
34
+ * Named `defineError` to avoid conflict with Nuxt's built-in `createError`
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * throw defineError({
39
+ * message: 'Payment failed',
40
+ * why: 'Card declined by issuer',
41
+ * fix: 'Try a different payment method',
42
+ * })
43
+ * ```
44
+ */
45
+ declare function defineError(options: ErrorOptions | string): EvlogError;
46
+
47
+ export { EvlogError, defineError };
@@ -0,0 +1,47 @@
1
+ import { ErrorOptions } from './types.js';
2
+
3
+ /**
4
+ * Structured error with context for better debugging
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * throw new EvlogError({
9
+ * message: 'Failed to sync repository',
10
+ * why: 'GitHub API rate limit exceeded',
11
+ * fix: 'Wait 1 hour or use a different token',
12
+ * link: 'https://docs.github.com/en/rest/rate-limit',
13
+ * cause: originalError,
14
+ * })
15
+ * ```
16
+ */
17
+ declare class EvlogError extends Error {
18
+ readonly why?: string;
19
+ readonly fix?: string;
20
+ readonly link?: string;
21
+ constructor(options: ErrorOptions | string);
22
+ /**
23
+ * Format error for console output with colors
24
+ */
25
+ toString(): string;
26
+ /**
27
+ * Convert to plain object for JSON serialization
28
+ */
29
+ toJSON(): Record<string, unknown>;
30
+ }
31
+ /**
32
+ * Create an EvlogError (functional alternative to `new EvlogError()`)
33
+ *
34
+ * Named `defineError` to avoid conflict with Nuxt's built-in `createError`
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * throw defineError({
39
+ * message: 'Payment failed',
40
+ * why: 'Card declined by issuer',
41
+ * fix: 'Try a different payment method',
42
+ * })
43
+ * ```
44
+ */
45
+ declare function defineError(options: ErrorOptions | string): EvlogError;
46
+
47
+ export { EvlogError, defineError };
package/dist/error.mjs ADDED
@@ -0,0 +1,64 @@
1
+ import { colors, isServer } from './utils.mjs';
2
+
3
+ class EvlogError extends Error {
4
+ why;
5
+ fix;
6
+ link;
7
+ constructor(options) {
8
+ const opts = typeof options === "string" ? { message: options } : options;
9
+ super(opts.message, { cause: opts.cause });
10
+ this.name = "EvlogError";
11
+ this.why = opts.why;
12
+ this.fix = opts.fix;
13
+ this.link = opts.link;
14
+ if (Error.captureStackTrace) {
15
+ Error.captureStackTrace(this, EvlogError);
16
+ }
17
+ }
18
+ /**
19
+ * Format error for console output with colors
20
+ */
21
+ toString() {
22
+ const useColors = isServer();
23
+ const red = useColors ? colors.red : "";
24
+ const yellow = useColors ? colors.yellow : "";
25
+ const cyan = useColors ? colors.cyan : "";
26
+ const dim = useColors ? colors.dim : "";
27
+ const reset = useColors ? colors.reset : "";
28
+ const bold = useColors ? colors.bold : "";
29
+ const lines = [];
30
+ lines.push(`${red}${bold}Error:${reset} ${this.message}`);
31
+ if (this.why) {
32
+ lines.push(`${yellow}Why:${reset} ${this.why}`);
33
+ }
34
+ if (this.fix) {
35
+ lines.push(`${cyan}Fix:${reset} ${this.fix}`);
36
+ }
37
+ if (this.link) {
38
+ lines.push(`${dim}More info:${reset} ${this.link}`);
39
+ }
40
+ if (this.cause) {
41
+ lines.push(`${dim}Caused by:${reset} ${this.cause.message}`);
42
+ }
43
+ return lines.join("\n");
44
+ }
45
+ /**
46
+ * Convert to plain object for JSON serialization
47
+ */
48
+ toJSON() {
49
+ return {
50
+ name: this.name,
51
+ message: this.message,
52
+ why: this.why,
53
+ fix: this.fix,
54
+ link: this.link,
55
+ cause: this.cause instanceof Error ? { name: this.cause.name, message: this.cause.message } : void 0,
56
+ stack: this.stack
57
+ };
58
+ }
59
+ }
60
+ function defineError(options) {
61
+ return new EvlogError(options);
62
+ }
63
+
64
+ export { EvlogError, defineError };
@@ -0,0 +1,5 @@
1
+ export { EvlogError, defineError } from './error.mjs';
2
+ export { createRequestLogger, getEnvironment, initLogger, log } from './logger.mjs';
3
+ export { useLogger } from './runtime/composables/useLogger.mjs';
4
+ export { BaseWideEvent, EnvironmentContext, ErrorOptions, EvlogEventContext, Log, LogLevel, LoggerConfig, RequestLogger, WideEvent } from './types.mjs';
5
+ import 'h3';
@@ -0,0 +1,5 @@
1
+ export { EvlogError, defineError } from './error.js';
2
+ export { createRequestLogger, getEnvironment, initLogger, log } from './logger.js';
3
+ export { useLogger } from './runtime/composables/useLogger.js';
4
+ export { BaseWideEvent, EnvironmentContext, ErrorOptions, EvlogEventContext, Log, LogLevel, LoggerConfig, RequestLogger, WideEvent } from './types.js';
5
+ import 'h3';
package/dist/index.mjs ADDED
@@ -0,0 +1,4 @@
1
+ export { EvlogError, defineError } from './error.mjs';
2
+ export { createRequestLogger, getEnvironment, initLogger, log } from './logger.mjs';
3
+ export { useLogger } from './runtime/composables/useLogger.mjs';
4
+ import './utils.mjs';
@@ -0,0 +1,40 @@
1
+ import { RequestLogger, EnvironmentContext, LoggerConfig, Log } from './types.mjs';
2
+
3
+ /**
4
+ * Initialize the logger with configuration.
5
+ * Call this once at application startup.
6
+ */
7
+ declare function initLogger(config?: LoggerConfig): void;
8
+ /**
9
+ * Simple logging API - as easy as console.log
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * log.info('auth', 'User logged in')
14
+ * log.error({ action: 'payment', error: 'failed' })
15
+ * ```
16
+ */
17
+ declare const log: Log;
18
+ interface RequestLoggerOptions {
19
+ method?: string;
20
+ path?: string;
21
+ requestId?: string;
22
+ }
23
+ /**
24
+ * Create a request-scoped logger for building wide events.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const log = createRequestLogger({ method: 'POST', path: '/checkout' })
29
+ * log.set({ user: { id: '123' } })
30
+ * log.set({ cart: { items: 3 } })
31
+ * log.emit()
32
+ * ```
33
+ */
34
+ declare function createRequestLogger(options?: RequestLoggerOptions): RequestLogger;
35
+ /**
36
+ * Get the current environment context.
37
+ */
38
+ declare function getEnvironment(): EnvironmentContext;
39
+
40
+ export { createRequestLogger, getEnvironment, initLogger, log };
@@ -0,0 +1,40 @@
1
+ import { RequestLogger, EnvironmentContext, LoggerConfig, Log } from './types.js';
2
+
3
+ /**
4
+ * Initialize the logger with configuration.
5
+ * Call this once at application startup.
6
+ */
7
+ declare function initLogger(config?: LoggerConfig): void;
8
+ /**
9
+ * Simple logging API - as easy as console.log
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * log.info('auth', 'User logged in')
14
+ * log.error({ action: 'payment', error: 'failed' })
15
+ * ```
16
+ */
17
+ declare const log: Log;
18
+ interface RequestLoggerOptions {
19
+ method?: string;
20
+ path?: string;
21
+ requestId?: string;
22
+ }
23
+ /**
24
+ * Create a request-scoped logger for building wide events.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const log = createRequestLogger({ method: 'POST', path: '/checkout' })
29
+ * log.set({ user: { id: '123' } })
30
+ * log.set({ cart: { items: 3 } })
31
+ * log.emit()
32
+ * ```
33
+ */
34
+ declare function createRequestLogger(options?: RequestLoggerOptions): RequestLogger;
35
+ /**
36
+ * Get the current environment context.
37
+ */
38
+ declare function getEnvironment(): EnvironmentContext;
39
+
40
+ export { createRequestLogger, getEnvironment, initLogger, log };