Vision
Features

Live Logs

Stream and filter logs in real-time

Live Logs

Vision captures and displays all your application logs in real-time, with powerful filtering and correlation with traces.

What are Live Logs?

The Logs page shows:

  • All console output - console.log(), console.error(), etc.
  • Real-time streaming - Logs appear instantly
  • Trace correlation - Link logs to their requests
  • Filtering - Search and filter by level, service, message
  • Timestamps - Precise timing for each log

Log Levels

Vision captures all standard log levels:

console.log('Info message')        // INFO
console.info('Info message')       // INFO
console.warn('Warning message')    // WARN
console.error('Error message')     // ERROR
console.debug('Debug message')     // DEBUG

In the Dashboard:

[INFO]  2024-01-20 10:30:45  Info message
[WARN]  2024-01-20 10:30:46  Warning message
[ERROR] 2024-01-20 10:30:47  Error message
[DEBUG] 2024-01-20 10:30:48  Debug message

Automatic Log Capture

With Vision Server

Logs are automatically captured:

import { Vision } from '@getvision/server'

const app = new Vision({ service: { name: 'My API' } })

app.service('users')
  .endpoint('POST', '/users', schema, async (data, c) => {
    console.log('Creating user:', data.email)
    
    const user = c.span('db.insert', {}, () => {
      return db.insert(users).values(data).returning().get()
    })
    
    console.log('User created:', user.id)
    return user
  })

Dashboard shows:

[INFO] 10:30:45  Creating user: [email protected]
[INFO] 10:30:45  User created: user_123

With Vision Adapters

Same automatic capture:

import { Hono } from 'hono'
import { visionAdapter } from '@getvision/adapter-hono'

const app = new Hono()
app.use('*', visionAdapter())

app.post('/users', async (c) => {
  console.log('Creating user')
  // Automatically captured
  return c.json({ success: true })
})

Both Vision Server and Adapters automatically capture all console output.

Trace Correlation

Logs are automatically linked to their traces:

app.service('users')
  .endpoint('GET', '/users/:id', schema, async ({ id }, c) => {
    console.log('Fetching user:', id)
    
    const user = c.span('db.select', {}, () => {
      console.log('Executing query')
      return db.select().from(users).where(eq(users.id, id)).get()
    })
    
    if (!user) {
      console.error('User not found:', id)
      throw new Error('User not found')
    }
    
    console.log('User found:', user.email)
    return user
  })

In the Dashboard:

Click on a trace to see its logs:

Trace: GET /users/123 [200] 45ms
├─ Logs:
│  ├─ [INFO]  Fetching user: 123
│  ├─ [INFO]  Executing query
│  └─ [INFO]  User found: [email protected]
└─ Spans:
   └─ db.select (12ms)

Click on a log to see its trace:

Log: [INFO] Fetching user: 123
└─ Trace: GET /users/123 [200] 45ms

Filtering Logs

By Level

[Filter: ERROR]
[ERROR] 10:30:47  Database connection failed
[ERROR] 10:31:22  User not found: 456

By Search Term

[Search: "user"]
[INFO]  10:30:45  Creating user: [email protected]
[INFO]  10:30:45  User created: user_123
[ERROR] 10:31:22  User not found: 456

By Context Keys

Search for logs containing specific context data, even if it's not in the message text.

[Search: "orderId=ord_123"]
[INFO]  10:30:45  Creating order
[INFO]  10:30:45  Order created successfully

By Service

[Service: Users]
[INFO]  10:30:45  Creating user
[INFO]  10:30:46  User created
[INFO]  10:31:00  Fetching user

By Time Range

[Last 5 minutes]
[INFO]  10:30:45  Recent log 1
[INFO]  10:31:00  Recent log 2

Structured Logging

Log objects for better inspection:

console.log('User created', {
  userId: user.id,
  email: user.email,
  timestamp: new Date().toISOString()
})

Dashboard shows:

[INFO] 10:30:45  User created
{
  "userId": "user_123",
  "email": "[email protected]",
  "timestamp": "2024-01-20T10:30:45.123Z"
}

Error Logging

Capture errors with stack traces:

try {
  const result = riskyOperation()
} catch (error) {
  console.error('Operation failed:', error)
  // Stack trace automatically captured
}

Dashboard shows:

[ERROR] 10:30:47  Operation failed: Error: Database timeout
Stack trace:
  at riskyOperation (handler.ts:45)
  at async handler (handler.ts:32)
  at async dispatch (hono.ts:123)

Performance Logging

Log with timing information:

app.service('users')
  .endpoint('GET', '/users', schema, async (_, c) => {
    const start = Date.now()
    
    const users = c.span('db.select', {}, () => {
      return db.select().from(users).all()
    })
    
    const duration = Date.now() - start
    console.log(`Fetched ${users.length} users in ${duration}ms`)
    
    return { users }
  })

Dashboard shows:

[INFO] 10:30:45  Fetched 150 users in 45ms

Smart Visuals

Vision automatically cleans up your logs to make them easier to read:

  1. Inline Badges - Key-value pairs like code=200 or duration=150ms are automatically extracted from the message and displayed as stylish inline badges.
  2. Deduplication - The extracted keys are removed from the text message to avoid repetition.
  3. Context Integration - Trace context and metadata are seamlessly integrated into these badges.

Before (Raw):

INF request completed code=200 duration=131ms path=/users/1

After (Vision UI):

INF request completed  [code=200] [duration=131ms] [path=/users/1]

Log Context (Wide Events)

Vision follows the Wide Events philosophy: add context once, see it everywhere.

The Problem

Without wide events, you'd do this:

app.post('/orders', async (c) => {
  const orderId = generateId()
  const userId = c.get('userId')

  console.log('Creating order', { orderId, userId })  // Add context manually
  await validateOrder(data)
  console.log('Order validated', { orderId, userId }) // Repeat context
  await chargePayment(data)
  console.log('Payment charged', { orderId, userId }) // Repeat again
  await createOrder(data)
  console.log('Order created', { orderId, userId })   // And again...
})

The Solution: c.addContext()

Add context once at the start. All logs in that request automatically include it:

// Vision Server - c.addContext() is built in
app.service('orders')
  .endpoint('POST', '/orders', schema, async (data, c) => {
    const orderId = generateId()

    // Add context ONCE - built into context!
    c.addContext({
      orderId,
      userId: data.userId,
      'order.type': 'subscription'
    })

    console.log('Creating order')        // Has orderId, userId automatically
    await validateOrder(data)
    console.log('Order validated')       // Has orderId, userId automatically
    await chargePayment(data)
    console.log('Payment charged')       // Has orderId, userId automatically
    await createOrder(data)
    console.log('Order created')         // Has orderId, userId automatically

    return order
  })

With Adapters (Hono/Express/Fastify), use getVisionContext():

import { getVisionContext } from '@getvision/adapter-hono'

app.post('/orders', async (c) => {
  const { vision } = getVisionContext()
  vision.addContext({ orderId, userId })
  // ...
})

Two Places to See Your Logs

1. Logs Tab (Global Search)

The Logs tab shows ALL logs from your application. You can search by:

  • Log message text
  • Log level (error, warn, info, debug)
  • Any context field - search userId=user_123 to find all logs for that user
[Search: "orderId=ord_456"]

Results:
10:30:45  Creating order       [orderId=ord_456] [userId=user_123]
10:30:46  Order validated      [orderId=ord_456] [userId=user_123]
10:30:47  Payment charged      [orderId=ord_456] [userId=user_123]
10:30:48  Order created        [orderId=ord_456] [userId=user_123]

2. Trace Details (Request-Scoped)

When you click on a trace, you see logs only from that specific request:

Trace: POST /orders [201] 320ms

Request Body:
  { "items": [...], "total": 99.99 }

Logs:
  10:30:45  Creating order       [orderId=ord_456]
  10:30:46  Order validated      [orderId=ord_456]
  10:30:47  Payment charged      [orderId=ord_456]
  10:30:48  Order created        [orderId=ord_456]

Spans:
  ├── validate.order (15ms)
  ├── payment.charge (180ms)
  └── db.insert.orders (45ms)

This is the power of wide events: You see the complete picture of what happened in ONE request - the HTTP data, the logs, the spans, all correlated together.

What to Add to Context

vision.addContext({
  // User info
  'user.id': user.id,
  'user.email': user.email,
  'user.plan': user.plan,

  // Request info
  'request.id': requestId,
  'request.type': 'api',

  // Business context
  orderId: order.id,
  'cart.items': cart.items.length,

  // Feature flags
  'feature.newCheckout': isEnabled('new-checkout'),

  // Environment
  'env': process.env.NODE_ENV
})

When to Use c.addContext()

// Vision Server - add context in middleware
app.use('*', async (c, next) => {
  // Add user context from auth
  const user = c.get('user')
  if (user) {
    c.addContext({
      'user.id': user.id,
      'user.plan': user.plan
    })
  }

  // Add request ID
  c.addContext({
    'request.id': c.req.header('X-Request-ID') || nanoid()
  })

  await next()
})

// Add business context in handler
app.service('orders')
  .endpoint('POST', '/orders', schema, async (data, c) => {
    const orderId = generateId()

    c.addContext({ orderId })

    // Now all logs have orderId
    console.log('Processing order')

    return order
  })

Log Retention

Configure how many logs to keep:

const app = new Vision({
  service: { name: 'My API' },
  vision: {
    maxLogs: 10000  // Keep last 10,000 logs (default)
  }
})

When the limit is reached, oldest logs are removed automatically.

Best Practices

1. Use Appropriate Log Levels

// ✅ Good - Right level for the message
console.log('User logged in')           // INFO
console.warn('Rate limit approaching')  // WARN
console.error('Payment failed')         // ERROR
console.debug('Cache hit')              // DEBUG

// ❌ Bad - Wrong levels
console.error('User logged in')         // Too severe
console.log('Payment failed')           // Too casual

2. Add Context

// ✅ Good - Rich context
console.log('Order created:', {
  orderId: order.id,
  userId: order.userId,
  total: order.total,
  items: order.items.length
})

// ❌ Bad - No context
console.log('Order created')

3. Don't Log Sensitive Data

// ✅ Good - Safe logging
console.log('User authenticated:', { userId: user.id })

// ❌ Bad - Leaking secrets
console.log('User authenticated:', {
  userId: user.id,
  password: user.password,  // Never log passwords!
  apiKey: user.apiKey       // Never log API keys!
})

4. Use Structured Logging

// ✅ Good - Structured
console.log('Payment processed', {
  amount: 99.99,
  currency: 'USD',
  provider: 'stripe'
})

// ❌ Bad - String concatenation
console.log('Payment processed: $99.99 USD via stripe')

5. Log Important Events

// ✅ Good - Track important events
console.log('User registered:', user.email)
console.log('Order placed:', order.id)
console.error('Payment failed:', error.message)

// ❌ Bad - Too verbose
console.log('Variable x assigned')
console.log('If statement executed')
console.log('Loop iteration 1')

Debugging Workflow

  1. See error in logs - Filter by ERROR level
  2. Find related trace - Click log to see trace
  3. Inspect request - View request body, headers
  4. Check spans - See which operation failed
  5. Review timeline - Understand what happened when

Export Logs

Coming soon: Export logs as JSON, CSV, or text files

Future features:

  • Export to file - Download logs for offline analysis
  • Log aggregation - Group similar logs
  • Alerting - Get notified of errors
  • Log forwarding - Send to external services

Next Steps