Skip to content

Commit d3395e9

Browse files
committed
docs: clarify Nuxt usage, add client logging and security best practices
1 parent 99c93c5 commit d3395e9

6 files changed

Lines changed: 561 additions & 14 deletions

File tree

AGENTS.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,69 @@ When creating errors with `createError()`:
288288
- Document public APIs with JSDoc comments
289289
- **No HTML comments in Vue templates** - Never use `<!-- comment -->` in `<template>` blocks. The code should be self-explanatory.
290290

291+
### Security: Preventing Sensitive Data Leakage
292+
293+
Wide events capture comprehensive context, making it easy to accidentally log sensitive data. **Never log:**
294+
295+
| Category | Examples | Risk |
296+
|----------|----------|------|
297+
| Credentials | Passwords, API keys, tokens, secrets | Account compromise |
298+
| Payment data | Full card numbers, CVV, bank accounts | PCI compliance violation |
299+
| Personal data (PII) | SSN, passport numbers, emails (unmasked) | Privacy laws (GDPR, CCPA) |
300+
| Authentication | Session tokens, JWTs, refresh tokens | Session hijacking |
301+
302+
**Safe logging pattern** - explicitly select which fields to log:
303+
304+
```typescript
305+
// ❌ DANGEROUS - logs everything including password
306+
const body = await readBody(event)
307+
log.set({ user: body })
308+
309+
// ✅ SAFE - explicitly select fields
310+
log.set({
311+
user: {
312+
id: body.id,
313+
plan: body.plan,
314+
// password: body.password ← NEVER include
315+
},
316+
})
317+
```
318+
319+
**Sanitization helpers** - create utilities for masking data:
320+
321+
```typescript
322+
// server/utils/sanitize.ts
323+
export function maskEmail(email: string): string {
324+
const [local, domain] = email.split('@')
325+
if (!domain) return '***'
326+
return `${local[0]}***@${domain[0]}***.${domain.split('.')[1]}`
327+
}
328+
329+
export function maskCard(card: string): string {
330+
return `****${card.slice(-4)}`
331+
}
332+
```
333+
334+
**Production checklist**:
335+
336+
- [ ] No passwords or secrets in logs
337+
- [ ] No full credit card numbers (only last 4 digits)
338+
- [ ] No API keys or tokens
339+
- [ ] PII is masked or omitted
340+
- [ ] Request bodies are selectively logged (not `log.set({ body })`)
341+
342+
### Client-Side Logging
343+
344+
The `log` API also works on the client side (auto-imported in Nuxt):
345+
346+
```typescript
347+
// In a Vue component or composable
348+
log.info('checkout', 'User initiated checkout')
349+
log.error({ action: 'payment', error: 'validation_failed' })
350+
```
351+
352+
Client logs output to the browser console with colored tags in development. Use for debugging and development - for production analytics, use dedicated services.
353+
291354
## Publishing
292355

293356
```bash

apps/docs/content/1.getting-started/3.quick-start.md

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,52 @@ description: Get up and running with evlog in minutes.
55

66
This guide covers the core APIs you'll use most often with evlog.
77

8-
## useLogger (Nuxt/Nitro)
8+
::callout{icon="i-lucide-sparkles" color="info"}
9+
In Nuxt, all evlog functions (`useLogger`, `log`, `createError`, `parseError`) are **auto-imported**. No import statements needed.
10+
::
11+
12+
## useLogger (Server-Side)
913

10-
In API routes, use `useLogger(event)` to get a request-scoped logger:
14+
Use `useLogger(event)` in any Nuxt/Nitro API route to get a request-scoped logger:
1115

1216
::code-group
13-
```typescript [Code]
14-
// server/api/checkout.post.ts
17+
```typescript [server/api/checkout.post.ts]
1518
export default defineEventHandler(async (event) => {
19+
// Get the request-scoped logger (auto-imported in Nuxt)
1620
const log = useLogger(event)
1721

22+
// Accumulate context throughout the request
1823
log.set({ user: { id: 1, plan: 'pro' } })
1924
log.set({ cart: { items: 3, total: 9999 } })
2025

21-
// Auto-emits on request end
22-
return { success: true }
26+
// Process checkout...
27+
const order = await processCheckout()
28+
log.set({ orderId: order.id })
29+
30+
// Logger auto-emits when request ends - nothing else to do!
31+
return { success: true, orderId: order.id }
2332
})
2433
```
2534
```bash [Output (Pretty)]
2635
10:23:45.612 INFO [my-app] POST /api/checkout 200 in 234ms
2736
├─ user: id=1 plan=pro
28-
└─ cart: items=3 total=9999
37+
├─ cart: items=3 total=9999
38+
└─ orderId: ord_abc123
2939
```
3040
::
3141

3242
::callout{icon="i-lucide-check" color="success"}
3343
The logger automatically emits when the request ends. No manual `emit()` call needed.
3444
::
3545

46+
### When to use useLogger vs log
47+
48+
| Use `useLogger(event)` | Use `log` |
49+
|------------------------|-----------|
50+
| API routes, middleware, server plugins | One-off events outside request context |
51+
| When you need to accumulate context | Quick debugging messages |
52+
| For wide events (one log per request) | Client-side logging |
53+
3654
## createError (Structured Errors)
3755

3856
Use `createError()` to throw errors with actionable context:
@@ -109,10 +127,8 @@ export async function checkout(cart: Cart) {
109127
For quick one-off logs anywhere in your code:
110128

111129
::code-group
112-
```typescript [Code]
130+
```typescript [Server]
113131
// server/utils/auth.ts
114-
import { log } from 'evlog'
115-
116132
log.info('auth', 'User logged in')
117133
log.error({ action: 'payment', error: 'card_declined' })
118134
log.warn('cache', 'Cache miss')
@@ -128,6 +144,49 @@ log.warn('cache', 'Cache miss')
128144
Prefer wide events (`useLogger`) over simple logs when possible. Use `log` for truly one-off events that don't belong to a request.
129145
::
130146

147+
## log (Client-Side)
148+
149+
The same `log` API works on the client side, outputting to the browser console:
150+
151+
::code-group
152+
```vue [components/CheckoutButton.vue]
153+
<script setup lang="ts">
154+
async function handleCheckout() {
155+
log.info('checkout', 'User initiated checkout')
156+
157+
try {
158+
await $fetch('/api/checkout', { method: 'POST' })
159+
log.info({ action: 'checkout', status: 'success' })
160+
} catch (err) {
161+
log.error({ action: 'checkout', error: 'failed' })
162+
}
163+
}
164+
</script>
165+
```
166+
```typescript [composables/useAnalytics.ts]
167+
export function useAnalytics() {
168+
function trackEvent(event: string, data?: Record<string, unknown>) {
169+
log.info('analytics', `Event: ${event}`)
170+
if (data) {
171+
log.debug({ event, ...data })
172+
}
173+
}
174+
175+
return { trackEvent }
176+
}
177+
```
178+
::
179+
180+
In pretty mode (development), client logs appear with colored tags in the browser console:
181+
182+
```
183+
[my-app] info { action: 'checkout', status: 'success' }
184+
```
185+
186+
::callout{icon="i-lucide-info" color="info"}
187+
Client-side `log` is designed for debugging and development. For production analytics, use dedicated services like Plausible, PostHog, or Mixpanel.
188+
::
189+
131190
## Wide Event Fields
132191

133192
Every wide event should include context from different layers:
@@ -168,3 +227,4 @@ log.set({ status: 200, duration: 234 })
168227

169228
- [Wide Events](/core-concepts/wide-events) - Learn how to design effective wide events
170229
- [Structured Errors](/core-concepts/structured-errors) - Master error handling with evlog
230+
- [Best Practices](/core-concepts/best-practices) - Security guidelines and production tips
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
title: Core Concepts
22
icon: i-lucide-book-open
3+
items:
4+
- title: Wide Events
5+
path: /core-concepts/wide-events
6+
- title: Structured Errors
7+
path: /core-concepts/structured-errors
8+
- title: Best Practices
9+
path: /core-concepts/best-practices

0 commit comments

Comments
 (0)