Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,69 @@ When creating errors with `createError()`:
- Document public APIs with JSDoc comments
- **No HTML comments in Vue templates** - Never use `<!-- comment -->` in `<template>` blocks. The code should be self-explanatory.

### Security: Preventing Sensitive Data Leakage

Wide events capture comprehensive context, making it easy to accidentally log sensitive data. **Never log:**

| Category | Examples | Risk |
|----------|----------|------|
| Credentials | Passwords, API keys, tokens, secrets | Account compromise |
| Payment data | Full card numbers, CVV, bank accounts | PCI compliance violation |
| Personal data (PII) | SSN, passport numbers, emails (unmasked) | Privacy laws (GDPR, CCPA) |
| Authentication | Session tokens, JWTs, refresh tokens | Session hijacking |

**Safe logging pattern** - explicitly select which fields to log:

```typescript
// ❌ DANGEROUS - logs everything including password
const body = await readBody(event)
log.set({ user: body })

// ✅ SAFE - explicitly select fields
log.set({
user: {
id: body.id,
plan: body.plan,
// password: body.password ← NEVER include
},
})
```

**Sanitization helpers** - create utilities for masking data:

```typescript
// server/utils/sanitize.ts
export function maskEmail(email: string): string {
const [local, domain] = email.split('@')
if (!domain) return '***'
return `${local[0]}***@${domain[0]}***.${domain.split('.')[1]}`
}

export function maskCard(card: string): string {
return `****${card.slice(-4)}`
}
```

**Production checklist**:

- [ ] No passwords or secrets in logs
- [ ] No full credit card numbers (only last 4 digits)
- [ ] No API keys or tokens
- [ ] PII is masked or omitted
- [ ] Request bodies are selectively logged (not `log.set({ body })`)

### Client-Side Logging

The `log` API also works on the client side (auto-imported in Nuxt):

```typescript
// In a Vue component or composable
log.info('checkout', 'User initiated checkout')
log.error({ action: 'payment', error: 'validation_failed' })
```

Client logs output to the browser console with colored tags in development. Use for debugging and development - for production analytics, use dedicated services.

## Publishing

```bash
Expand Down
80 changes: 70 additions & 10 deletions apps/docs/content/1.getting-started/3.quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,52 @@ description: Get up and running with evlog in minutes.

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

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

## useLogger (Server-Side)

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

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

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

// Auto-emits on request end
return { success: true }
// Process checkout...
const order = await processCheckout()
log.set({ orderId: order.id })

// Logger auto-emits when request ends - nothing else to do!
return { success: true, orderId: order.id }
})
```
```bash [Output (Pretty)]
10:23:45.612 INFO [my-app] POST /api/checkout 200 in 234ms
├─ user: id=1 plan=pro
└─ cart: items=3 total=9999
├─ cart: items=3 total=9999
└─ orderId: ord_abc123
```
::

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

### When to use useLogger vs log

| Use `useLogger(event)` | Use `log` |
|------------------------|-----------|
| API routes, middleware, server plugins | One-off events outside request context |
| When you need to accumulate context | Quick debugging messages |
| For wide events (one log per request) | Client-side logging |

## createError (Structured Errors)

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

::code-group
```typescript [Code]
```typescript [Server]
// server/utils/auth.ts
import { log } from 'evlog'

log.info('auth', 'User logged in')
log.error({ action: 'payment', error: 'card_declined' })
log.warn('cache', 'Cache miss')
Expand All @@ -128,6 +144,49 @@ log.warn('cache', 'Cache miss')
Prefer wide events (`useLogger`) over simple logs when possible. Use `log` for truly one-off events that don't belong to a request.
::

## log (Client-Side)

The same `log` API works on the client side, outputting to the browser console:

::code-group
```vue [components/CheckoutButton.vue]
<script setup lang="ts">
async function handleCheckout() {
log.info('checkout', 'User initiated checkout')

try {
await $fetch('/api/checkout', { method: 'POST' })
log.info({ action: 'checkout', status: 'success' })
} catch (err) {
log.error({ action: 'checkout', error: 'failed' })
}
}
</script>
```
```typescript [composables/useAnalytics.ts]
export function useAnalytics() {
function trackEvent(event: string, data?: Record<string, unknown>) {
log.info('analytics', `Event: ${event}`)
if (data) {
log.debug({ event, ...data })
}
}

return { trackEvent }
}
```
::

In pretty mode (development), client logs appear with colored tags in the browser console:

```
[my-app] info { action: 'checkout', status: 'success' }
```

::callout{icon="i-lucide-info" color="info"}
Client-side `log` is designed for debugging and development. For production analytics, use dedicated services like Plausible, PostHog, or Mixpanel.
::

## Wide Event Fields

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

- [Wide Events](/core-concepts/wide-events) - Learn how to design effective wide events
- [Structured Errors](/core-concepts/structured-errors) - Master error handling with evlog
- [Best Practices](/core-concepts/best-practices) - Security guidelines and production tips
7 changes: 7 additions & 0 deletions apps/docs/content/2.core-concepts/.navigation.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
title: Core Concepts
icon: i-lucide-book-open
items:
- title: Wide Events
path: /core-concepts/wide-events
- title: Structured Errors
path: /core-concepts/structured-errors
- title: Best Practices
path: /core-concepts/best-practices
Loading
Loading