Skip to content

Commit 5489c0d

Browse files
authored
feat(library): add evlog observability library (#110)
* feat(library): add evlog observability library Adds evlog (wide-event structured logging) as a new per-app library option under a new Observability category. Supports Next.js, Hono, TanStack Start, and Node stacks. - META entry with evlog ^2.11.0 and nitro devDep scoped to tanstack-start - Next.js: lib/evlog.ts (createEvlog + createInstrumentation), instrumentation.ts (defineNodeInstrumentation), proxy.ts rewrites return value through evlogMiddleware when selected - Hono: initLogger + evlog() middleware wired into src/app.ts, replaces hono/logger and adds EvlogVariables typing - TanStack Start: nitro.config.ts module registration with experimental.asyncContext, __root.tsx wires evlogErrorHandler via server middleware - Node: initLogger + createLogger wire-up in src/index.ts - Composes cleanly with better-auth in Next.js proxy - Full integration test covering all four stacks, turborepo multi-app, and no-evlog regression * fix(library): improve evlog Hono error handler and simplify Node wire-up - Hono: app.onError uses c.get('log').error(err) + parseError for structured error responses instead of raw console.error - Node: remove demo createLogger/set/emit, keep only initLogger + log.info for minimal wire-up consistent with other stacks - Add test for Hono structured error handling * fix(library): remove captureOutput from evlog instrumentation * fix(library): move instrumentation.ts into src/ for Next.js convention Next.js looks for instrumentation.ts inside src/ when using the src/ directory convention. Fixes double output caused by incorrect file placement. * docs(modules): add evlog observability documentation page Adds Observability category to module docs sidebar and evlog.mdx covering per-stack wire-up: Next.js (factory + instrumentation + proxy middleware), Hono (initLogger + evlog middleware + parseError), TanStack Start (nitro module + error handler), and Node (initLogger).
1 parent 9fb0206 commit 5489c0d

File tree

13 files changed

+515
-3
lines changed

13 files changed

+515
-3
lines changed

apps/cli/src/__meta__.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,20 @@ export const META: Meta = {
324324
support: { stacks: ['hono'] },
325325
packageJson: {},
326326
},
327+
evlog: {
328+
label: 'evlog',
329+
hint: 'Wide-event structured logging with drains and sampling',
330+
category: 'Observability',
331+
support: { stacks: ['nextjs', 'hono', 'tanstack-start', 'node'] },
332+
packageJson: {
333+
dependencies: {
334+
evlog: '^2.11.0',
335+
},
336+
devDependencies: {
337+
nitro: $when({ stack: 'tanstack-start' }, '^3.0.260311-beta'),
338+
},
339+
},
340+
},
327341
},
328342

329343
project: {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { defineConfig } from 'nitro';
2+
import evlog from 'evlog/nitro/v3';
3+
4+
export default defineConfig({
5+
experimental: {
6+
asyncContext: true,
7+
},
8+
modules: [
9+
evlog({
10+
env: { service: '{{appName}}' },
11+
}),
12+
],
13+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { defineNodeInstrumentation } from 'evlog/next/instrumentation';
2+
3+
export const { register, onRequestError } = defineNodeInstrumentation(() => import('./lib/evlog'));
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { createEvlog } from 'evlog/next';
2+
import { createInstrumentation } from 'evlog/next/instrumentation';
3+
4+
export const { withEvlog, useLogger, log, createError } = createEvlog({
5+
service: '{{appName}}',
6+
});
7+
8+
export const { register, onRequestError } = createInstrumentation({
9+
service: '{{appName}}',
10+
});

apps/cli/templates/stack/hono/src/app.ts.hbs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
import { Hono } from 'hono';
22
import { cors } from 'hono/cors';
3+
{{#unless (hasLibrary "evlog")}}
34
import { logger } from 'hono/logger';
5+
{{/unless}}
46
import { timeout } from 'hono/timeout';
7+
{{#if (hasLibrary "evlog")}}
8+
import { initLogger, parseError } from 'evlog';
9+
import { evlog, type EvlogVariables } from 'evlog/hono';
10+
import type { ContentfulStatusCode } from 'hono/utils/http-status';
511

6-
const app = new Hono();
12+
initLogger({
13+
env: { service: '{{appName}}' },
14+
});
15+
{{/if}}
16+
17+
const app = new Hono{{#if (hasLibrary "evlog")}}<EvlogVariables>{{/if}}();
718

19+
{{#if (hasLibrary "evlog")}}
20+
app.use(evlog());
21+
{{else}}
822
app.use(logger());
23+
{{/if}}
924
app.use(timeout(15000));
1025

1126
app.use(
@@ -42,6 +57,19 @@ app.get('/health/:id', (c) => {
4257
});
4358

4459
app.onError((err, c) => {
60+
{{#if (hasLibrary "evlog")}}
61+
c.get('log').error(err);
62+
const parsed = parseError(err);
63+
return c.json(
64+
{
65+
ok: false,
66+
message: parsed.message,
67+
why: parsed.why,
68+
fix: parsed.fix,
69+
},
70+
(parsed.status || 500) as ContentfulStatusCode,
71+
);
72+
{{else}}
4573
console.error('Error:', err);
4674
return c.json(
4775
{
@@ -51,6 +79,7 @@ app.onError((err, c) => {
5179
},
5280
500,
5381
);
82+
{{/if}}
5483
});
5584

5685
export default app;

apps/cli/templates/stack/nextjs/src/proxy.ts.hbs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
{{#if (hasLibrary "evlog")}}
2+
import type { NextRequest } from 'next/server';
3+
import { evlogMiddleware } from 'evlog/next';
4+
{{else}}
15
import { type NextRequest, NextResponse } from 'next/server';
6+
{{/if}}
27
{{#if (hasLibrary "better-auth")}}
38
import { headers } from 'next/headers';
49
{{#if (isMono)}}
@@ -8,6 +13,10 @@ import { auth } from '@/lib/auth/auth';
813
{{/if}}
914
{{/if}}
1015

16+
{{#if (hasLibrary "evlog")}}
17+
const withEvlog = evlogMiddleware();
18+
{{/if}}
19+
1120
export default async function proxy(request: NextRequest) {
1221
const { pathname } = request.nextUrl;
1322

@@ -21,7 +30,11 @@ export default async function proxy(request: NextRequest) {
2130
console.log(`PROXY "${pathname}"`);
2231
{{/if}}
2332

33+
{{#if (hasLibrary "evlog")}}
34+
return await withEvlog(request);
35+
{{else}}
2436
return NextResponse.next();
37+
{{/if}}
2538
}
2639

2740
export const config = {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,11 @@
1+
{{#if (hasLibrary "evlog")}}
2+
import { initLogger, log } from 'evlog';
3+
4+
initLogger({
5+
env: { service: '{{appName}}' },
6+
});
7+
8+
log.info({ action: 'startup' });
9+
{{else}}
110
console.log('Hello from {{appName}}');
11+
{{/if}}

apps/cli/templates/stack/tanstack-start/src/routes/___root.tsx.hbs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
/// <reference types="vite/client" />
22
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
33
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
4+
{{#if (hasLibrary "evlog")}}
5+
import { createMiddleware } from '@tanstack/react-start'
6+
import { evlogErrorHandler } from 'evlog/nitro/v3'
7+
{{/if}}
48

59
import appCss from '../styles.css?url'
610

@@ -25,6 +29,11 @@ export const Route = createRootRoute({
2529
},
2630
],
2731
}),
32+
{{#if (hasLibrary "evlog")}}
33+
server: {
34+
middleware: [createMiddleware().server(evlogErrorHandler)],
35+
},
36+
{{/if}}
2837
shellComponent: RootDocument,
2938
})
3039

0 commit comments

Comments
 (0)