Voyage AI Logs API

v1.0 ยท REST API

Send logs via REST API with automatic journey tracking and intelligent batching. Base URL: https://api.tracevoyage.com

๐Ÿ’ก

Recommended: Wrap API in a Logger Class

Instead of making raw API calls, create a logger class that handles authentication and batching automatically. Here's how:

โœ… What You Write (Simple)

from voyage_logger import VoyageLogger

# Initialize once
logger = VoyageLogger(
    app_name="my-app",
    environment="production"
)

# Use throughout your app
logger.info(
    message="User login successful",
    event_name="login_success",
    event_flow_name="authentication",
    app_user_id="user_123",
    app_username="john_doe",
    meta_data={
        "ip": "192.168.1.1"
    }
)

# Errors with auto-ticket
logger.critical(
    message="Payment failed",
    event_name="payment_error",
    app_user_id="user_123",
    add_ticket=True
)

โš™๏ธ What Happens Behind the Scenes

class VoyageLogger:
    def __init__(self, app_name, environment):
        # 1. Get access token
        self.token = self._authenticate()
        self.app_name = app_name
        self.environment = environment
        self._queue = []
    
    def _authenticate(self):
        response = requests.post(
            "https://api.tracevoyage.com/accounts/api/v1/auth/sign-in-keys",
            headers={
                "x-api-key": os.getenv("VOYAGE_API_KEY"),
                "x-api-secret": os.getenv("VOYAGE_API_SECRET")
            }
        )
        return response.json()["data"][0]["access_token"]
    
    def info(self, message, **kwargs):
        # 2. Build log entry
        log = {
            "message": message,
            "level": "info",
            "app_name": self.app_name,
            "environment": self.environment,
            **kwargs
        }
        
        # 3. Add to queue
        self._queue.append(log)
        
        # 4. Auto-flush when batch is full
        if len(self._queue) >= 100:
            self._flush()
    
    def _flush(self):
        # 5. Send batch to API
        requests.post(
            "https://api.tracevoyage.com/voyage/api/v1/logs/batch",
            headers={
                "Authorization": f"Bearer {self.token}",
                "Content-Type": "application/json"
            },
            json={"logs": self._queue}
        )
        self._queue = []

๐ŸŽฏ Key Benefits

โœ“
Auto Authentication
Token refresh handled automatically
โœ“
Smart Batching
Groups logs for better performance
โœ“
Clean Interface
Simple logger.info() calls

Implementation Patterns

Choose your preferred approach: use a wrapper class for convenience, or make direct API calls for full control.

โšก

Logger Class (Recommended)

Handles auth & batching automatically

  • โœ“ Simple logger.info() interface
  • โœ“ Auto token refresh
  • โœ“ Built-in batching
  • โœ“ Error retry logic
๐Ÿ”ง

Direct API Calls

Full control over requests

  • โœ“ Complete flexibility
  • โœ“ No dependencies
  • โœ“ Custom batching logic
  • โœ“ Fine-grained control

Full Implementation Examples

๐ŸPython - Complete VoyageLogger Class Implementation
import os
import requests
import time
import threading
from typing import List, Dict, Optional

class VoyageLogger:
    """
    Voyage AI Logger - Wraps REST API with simple interface
    
    Usage:
        logger = VoyageLogger(app_name="my-app", environment="production")
        logger.info("User logged in", app_user_id="user_123")
    """
    
    def __init__(
        self,
        app_name: str,
        environment: str = "production",
        batch_size: int = 100,
        flush_interval: float = 5.0
    ):
        self.app_name = app_name
        self.environment = environment
        self.batch_size = batch_size
        self.flush_interval = flush_interval
        
        # API configuration
        self.base_url = os.getenv("VOYAGE_LOGS_URL", "https://api.tracevoyage.com")
        self.api_key = os.getenv("VOYAGE_LOGS_KEY")
        self.api_secret = os.getenv("VOYAGE_LOGS_SECRET")
        
        # State
        self._access_token = None
        self._token_expiry = 0
        self._queue: List[Dict] = []
        self._lock = threading.Lock()
        
        # Authenticate on initialization
        self._authenticate()
        
        # Start background flusher
        self._start_auto_flush()
    
    def _authenticate(self):
        """Get access token from API"""
        response = requests.post(
            f"{self.base_url}/accounts/api/v1/auth/sign-in-keys",
            headers={
                "x-api-key": self.api_key,
                "x-api-secret": self.api_secret
            }
        )
        response.raise_for_status()
        
        data = response.json()
        self._access_token = data["data"][0]["access_token"]
        self._token_expiry = time.time() + 3600  # 1 hour
    
    def _refresh_token_if_needed(self):
        """Refresh token if expiring soon (within 5 minutes)"""
        if time.time() >= self._token_expiry - 300:
            self._authenticate()
    
    def _send_batch(self, logs: List[Dict]) -> bool:
        """Send batch of logs to API"""
        if not logs:
            return True
        
        try:
            self._refresh_token_if_needed()
            
            response = requests.post(
                f"{self.base_url}/voyage/api/v1/logs/batch",
                headers={
                    "Authorization": f"Bearer {self._access_token}",
                    "Content-Type": "application/json"
                },
                json={"logs": logs},
                timeout=10
            )
            
            return response.status_code in [200, 201]
        except Exception as e:
            print(f"Failed to send logs: {e}")
            return False
    
    def _flush(self):
        """Flush queued logs"""
        with self._lock:
            if not self._queue:
                return
            
            logs = self._queue.copy()
            self._queue.clear()
        
        self._send_batch(logs)
    
    def _start_auto_flush(self):
        """Start background thread for auto-flushing"""
        def flush_worker():
            while True:
                time.sleep(self.flush_interval)
                self._flush()
        
        thread = threading.Thread(target=flush_worker, daemon=True)
        thread.start()
    
    def log(self, level: str, message: str, **kwargs):
        """Base log method"""
        log_entry = {
            "message": message,
            "level": level,
            "app_name": self.app_name,
            "environment": self.environment,
            **kwargs
        }
        
        with self._lock:
            self._queue.append(log_entry)
            
            # Auto-flush if batch is full
            if len(self._queue) >= self.batch_size:
                self._flush()
    
    def debug(self, message: str, **kwargs):
        """Log debug message"""
        self.log("debug", message, **kwargs)
    
    def info(self, message: str, **kwargs):
        """Log info message"""
        self.log("info", message, **kwargs)
    
    def warn(self, message: str, **kwargs):
        """Log warning message"""
        self.log("warn", message, **kwargs)
    
    def error(self, message: str, **kwargs):
        """Log error message"""
        self.log("error", message, **kwargs)
    
    def critical(self, message: str, **kwargs):
        """Log critical message"""
        self.log("critical", message, **kwargs)
    
    def flush(self):
        """Manually flush all queued logs"""
        self._flush()


# Example Usage
if __name__ == "__main__":
    # Initialize logger
    logger = VoyageLogger(
        app_name="my-application",
        environment="production"
    )
    
    # Log events
    logger.info(
        message="User login successful",
        event_name="login_success",
        event_flow_name="authentication",
        app_user_id="user_123",
        app_username="john_doe",
        meta_data={"ip": "192.168.1.1"}
    )
    
    # Log errors with ticket creation
    logger.critical(
        message="Payment gateway timeout",
        event_name="payment_error",
        event_flow_name="checkout",
        app_user_id="user_123",
        add_ticket=True,
        meta_data={"gateway": "stripe", "amount": 99.99}
    )
    
    # Ensure logs are sent before exit
    logger.flush()
โšกJavaScript/Node.js - Complete VoyageLogger Class Implementation
const axios = require('axios');

/**
 * Voyage AI Logger - Wraps REST API with simple interface
 * 
 * Usage:
 *   const logger = new VoyageLogger({ appName: 'my-app', environment: 'production' });
 *   logger.info('User logged in', { appUserId: 'user_123' });
 */
class VoyageLogger {
  constructor({
    appName,
    environment = 'production',
    batchSize = 100,
    flushInterval = 5000
  }) {
    this.appName = appName;
    this.environment = environment;
    this.batchSize = batchSize;
    this.flushInterval = flushInterval;
    
    // API configuration
    this.baseUrl = process.env.VOYAGE_LOGS_URL || 'https://api.tracevoyage.com';
    this.apiKey = process.env.VOYAGE_LOGS_KEY;
    this.apiSecret = process.env.VOYAGE_LOGS_SECRET;
    
    // State
    this.accessToken = null;
    this.tokenExpiry = 0;
    this.queue = [];
    
    // Initialize
    this.authenticate().then(() => {
      this.startAutoFlush();
    });
  }
  
  async authenticate() {
    try {
      const response = await axios.post(
        `${this.baseUrl}/accounts/api/v1/auth/sign-in-keys`,
        {},
        {
          headers: {
            'x-api-key': this.apiKey,
            'x-api-secret': this.apiSecret
          }
        }
      );
      
      this.accessToken = response.data.data[0].access_token;
      this.tokenExpiry = Date.now() + 3600000; // 1 hour
    } catch (error) {
      console.error('Authentication failed:', error.message);
      throw error;
    }
  }
  
  async refreshTokenIfNeeded() {
    // Refresh if expiring within 5 minutes
    if (Date.now() >= this.tokenExpiry - 300000) {
      await this.authenticate();
    }
  }
  
  async sendBatch(logs) {
    if (!logs || logs.length === 0) return true;
    
    try {
      await this.refreshTokenIfNeeded();
      
      const response = await axios.post(
        `${this.baseUrl}/voyage/api/v1/logs/batch`,
        { logs },
        {
          headers: {
            'Authorization': `Bearer ${this.accessToken}`,
            'Content-Type': 'application/json'
          },
          timeout: 10000
        }
      );
      
      return [200, 201].includes(response.status);
    } catch (error) {
      console.error('Failed to send logs:', error.message);
      return false;
    }
  }
  
  async flush() {
    if (this.queue.length === 0) return;
    
    const logs = [...this.queue];
    this.queue = [];
    
    await this.sendBatch(logs);
  }
  
  startAutoFlush() {
    setInterval(() => {
      this.flush();
    }, this.flushInterval);
  }
  
  log(level, message, params = {}) {
    const logEntry = {
      message,
      level,
      app_name: this.appName,
      environment: this.environment,
      ...params
    };
    
    this.queue.push(logEntry);
    
    // Auto-flush if batch is full
    if (this.queue.length >= this.batchSize) {
      this.flush();
    }
  }
  
  debug(message, params) {
    this.log('debug', message, params);
  }
  
  info(message, params) {
    this.log('info', message, params);
  }
  
  warn(message, params) {
    this.log('warn', message, params);
  }
  
  error(message, params) {
    this.log('error', message, params);
  }
  
  critical(message, params) {
    this.log('critical', message, params);
  }
}

// Example Usage
async function main() {
  // Initialize logger
  const logger = new VoyageLogger({
    appName: 'my-application',
    environment: 'production'
  });
  
  // Wait for authentication
  await new Promise(resolve => setTimeout(resolve, 1000));
  
  // Log events
  logger.info('User login successful', {
    event_name: 'login_success',
    event_flow_name: 'authentication',
    app_user_id: 'user_123',
    app_username: 'john_doe',
    meta_data: { ip: '192.168.1.1' }
  });
  
  // Log errors with ticket creation
  logger.critical('Payment gateway timeout', {
    event_name: 'payment_error',
    event_flow_name: 'checkout',
    app_user_id: 'user_123',
    add_ticket: true,
    meta_data: { gateway: 'stripe', amount: 99.99 }
  });
  
  // Ensure logs are sent before exit
  await logger.flush();
}

module.exports = VoyageLogger;
๐Ÿ’™TypeScript - Type-Safe VoyageLogger Implementation
import axios from 'axios';

type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'critical';

interface LogEntry {
  message: string;
  level: LogLevel;
  app_name: string;
  environment: string;
  event_name?: string;
  event_flow_name?: string;
  app_user_id?: string;
  app_username?: string;
  app_useremail?: string;
  session_id?: string;
  method_name?: string;
  tags?: string[];
  meta_data?: Record<string, any>;
  exception_info?: string;
  add_ticket?: boolean;
  event_timeline?: Array<{ step: string; timestamp: number }>;
}

interface VoyageLoggerConfig {
  appName: string;
  environment?: string;
  batchSize?: number;
  flushInterval?: number;
}

interface LogParams {
  event_name?: string;
  event_flow_name?: string;
  app_user_id?: string;
  app_username?: string;
  app_useremail?: string;
  session_id?: string;
  method_name?: string;
  tags?: string[];
  meta_data?: Record<string, any>;
  exception_info?: string;
  add_ticket?: boolean;
  event_timeline?: Array<{ step: string; timestamp: number }>;
}

/**
 * Voyage AI Logger - Type-safe wrapper for REST API
 * 
 * @example
 * const logger = new VoyageLogger({ appName: 'my-app', environment: 'production' });
 * logger.info('User logged in', { appUserId: 'user_123' });
 */
class VoyageLogger {
  private appName: string;
  private environment: string;
  private batchSize: number;
  private flushInterval: number;
  private baseUrl: string;
  private apiKey: string;
  private apiSecret: string;
  private accessToken: string | null = null;
  private tokenExpiry: number = 0;
  private queue: LogEntry[] = [];
  private flushTimer?: NodeJS.Timeout;

  constructor({
    appName,
    environment = 'production',
    batchSize = 100,
    flushInterval = 5000
  }: VoyageLoggerConfig) {
    this.appName = appName;
    this.environment = environment;
    this.batchSize = batchSize;
    this.flushInterval = flushInterval;
    
    this.baseUrl = process.env.VOYAGE_LOGS_URL || 'https://api.tracevoyage.com';
    this.apiKey = process.env.VOYAGE_LOGS_KEY!;
    this.apiSecret = process.env.VOYAGE_LOGS_SECRET!;
    
    this.initialize();
  }

  private async initialize(): Promise<void> {
    await this.authenticate();
    this.startAutoFlush();
  }

  private async authenticate(): Promise<void> {
    try {
      const response = await axios.post(
        `${this.baseUrl}/accounts/api/v1/auth/sign-in-keys`,
        {},
        {
          headers: {
            'x-api-key': this.apiKey,
            'x-api-secret': this.apiSecret
          }
        }
      );
      
      this.accessToken = response.data.data[0].access_token;
      this.tokenExpiry = Date.now() + 3600000;
    } catch (error) {
      console.error('Authentication failed:', error);
      throw error;
    }
  }

  private async refreshTokenIfNeeded(): Promise<void> {
    if (Date.now() >= this.tokenExpiry - 300000) {
      await this.authenticate();
    }
  }

  private async sendBatch(logs: LogEntry[]): Promise<boolean> {
    if (!logs || logs.length === 0) return true;
    
    try {
      await this.refreshTokenIfNeeded();
      
      const response = await axios.post(
        `${this.baseUrl}/voyage/api/v1/logs/batch`,
        { logs },
        {
          headers: {
            'Authorization': `Bearer ${this.accessToken}`,
            'Content-Type': 'application/json'
          },
          timeout: 10000
        }
      );
      
      return [200, 201].includes(response.status);
    } catch (error) {
      console.error('Failed to send logs:', error);
      return false;
    }
  }

  async flush(): Promise<void> {
    if (this.queue.length === 0) return;
    
    const logs = [...this.queue];
    this.queue = [];
    
    await this.sendBatch(logs);
  }

  private startAutoFlush(): void {
    this.flushTimer = setInterval(() => {
      this.flush();
    }, this.flushInterval);
  }

  private log(level: LogLevel, message: string, params: LogParams = {}): void {
    const logEntry: LogEntry = {
      message,
      level,
      app_name: this.appName,
      environment: this.environment,
      ...params
    };
    
    this.queue.push(logEntry);
    
    if (this.queue.length >= this.batchSize) {
      this.flush();
    }
  }

  debug(message: string, params?: LogParams): void {
    this.log('debug', message, params);
  }

  info(message: string, params?: LogParams): void {
    this.log('info', message, params);
  }

  warn(message: string, params?: LogParams): void {
    this.log('warn', message, params);
  }

  error(message: string, params?: LogParams): void {
    this.log('error', message, params);
  }

  critical(message: string, params?: LogParams): void {
    this.log('critical', message, params);
  }

  async close(): Promise<void> {
    if (this.flushTimer) {
      clearInterval(this.flushTimer);
    }
    await this.flush();
  }
}

export default VoyageLogger;

API Endpoints Reference

POST/accounts/api/v1/auth/sign-in-keys

Get an access token using your API credentials

Parameters

curl -X POST https://api.tracevoyage.com/accounts/api/v1/auth/sign-in-keys \
  -H "x-api-key: YOUR_API_KEY" \
  -H "x-api-secret: YOUR_API_SECRET"

Response

200 Success
{
  "data": [
    {
      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
      "token_type": "bearer",
      "expires_in": 3600
    }
  ]
}
400/401 Error
{
  "error": "Invalid credentials",
  "message": "API key or secret is incorrect",
  "status": 401
}
๐Ÿ’ก

Quick Tips

  • โ€ข Tokens expire after 1 hour
  • โ€ข Store tokens securely
  • โ€ข Never commit API keys to git

Log Levels

debug

Detailed info for debugging

info

General informational messages

warn

Warning, non-critical issues

error

Error events, recoverable

critical

Severe errors, needs attention

Need Help?

Check our comprehensive guides or reach out to our support team