Skip to main content

Satellite Log Level Configuration

The DeployStack Satellite uses Pino logger with Fastify for high-performance, structured logging. This guide covers everything you need to know about configuring and using log levels effectively in the satellite service.

Overview

The Satellite logging system is identical to the backend implementation, built on industry best practices:
  • Pino Logger: Ultra-fast JSON logger for Node.js
  • Fastify Integration: Native logging support with request correlation
  • Environment-based Configuration: Automatic log level adjustment based on NODE_ENV
  • Structured Logging: JSON output for production, pretty-printed for development

Available Log Levels

Log levels are ordered by severity (lowest to highest):
LevelNumeric ValueDescriptionWhen to Use
trace10Very detailed debuggingMCP server process tracing, detailed communication flows
debug20Debugging informationDevelopment debugging, MCP server lifecycle events
info30General informationSatellite startup, team operations, successful MCP calls
warn40Warning messagesResource limits approached, MCP server restarts
error50Error conditionsMCP server failures, team isolation violations
fatal60Fatal errorsSatellite crashes, critical system failures

Configuration

Environment Variables

Set the log level using the LOG_LEVEL environment variable in your .env file:
# Development - show debug information
LOG_LEVEL=debug npm run dev

# Production - show info and above
LOG_LEVEL=info npm run start

# Troubleshooting - show everything
LOG_LEVEL=trace npm run dev

# Quiet mode - only errors and fatal
LOG_LEVEL=error npm run start

Default Behavior

The logger automatically adjusts based on your environment:
// From src/fastify/config/logger.ts
export const loggerConfig: FastifyServerOptions['logger'] = {
  level: process.env.LOG_LEVEL || (process.env.NODE_ENV === 'production' ? 'info' : 'debug'),
  transport: process.env.NODE_ENV !== 'production'
    ? {
        target: 'pino-pretty',
        options: {
          colorize: true,
          translateTime: 'SYS:standard',
          ignore: 'pid,hostname'
        }
      }
    : undefined
}
Default Levels:
  • Development: debug (shows debug, info, warn, error, fatal)
  • Production: info (shows info, warn, error, fatal)

Log Output Formats

Development Format (Pretty-printed)

[2025-09-09 13:34:50.836 +0200] INFO: 🚀 DeployStack Satellite running on http://0.0.0.0:3001
[2025-09-09 13:34:50.836 +0200] DEBUG: 🔄 Starting MCP server process manager...
[2025-09-09 13:35:13.499 +0200] INFO: Hello world endpoint accessed
    operation: "hello_world"
    endpoint: "/health/hello"

Production Format (JSON)

{"level":30,"time":"2025-09-09T11:34:50.836Z","pid":1234,"hostname":"satellite-01","msg":"DeployStack Satellite running on http://0.0.0.0:3001"}
{"level":20,"time":"2025-09-09T11:34:50.836Z","pid":1234,"hostname":"satellite-01","msg":"Starting MCP server process manager..."}
{"level":30,"time":"2025-09-09T11:35:13.499Z","pid":1234,"hostname":"satellite-01","operation":"hello_world","endpoint":"/health/hello","msg":"Hello world endpoint accessed"}

Satellite-Specific Logging Patterns

MCP Server Management

// MCP server lifecycle
server.log.debug({
  operation: 'mcp_server_spawn',
  serverId: 'filesystem-server',
  teamId: 'team-123',
  command: 'npx @modelcontextprotocol/server-filesystem'
}, 'Spawning MCP server process');

server.log.info({
  operation: 'mcp_server_ready',
  serverId: 'filesystem-server',
  teamId: 'team-123',
  pid: 5678,
  startupTime: '2.3s'
}, 'MCP server process ready');

server.log.warn({
  operation: 'mcp_server_restart',
  serverId: 'filesystem-server',
  teamId: 'team-123',
  reason: 'health_check_failed',
  restartCount: 2
}, 'Restarting MCP server process');

Team Isolation Operations

// Team resource management
server.log.debug({
  operation: 'team_isolation_setup',
  teamId: 'team-123',
  namespace: 'satellite-team-123',
  cpuLimit: '0.1',
  memoryLimit: '100MB'
}, 'Setting up team resource isolation');

server.log.warn({
  operation: 'resource_limit_approached',
  teamId: 'team-123',
  resourceType: 'memory',
  currentUsage: '85MB',
  limit: '100MB'
}, 'Team approaching resource limit');

Backend Communication

// Backend polling and communication
server.log.debug({
  operation: 'backend_poll',
  backendUrl: 'https://api.deploystack.io',
  satelliteId: 'satellite-01',
  responseTime: '150ms'
}, 'Backend polling completed');

server.log.info({
  operation: 'configuration_update',
  satelliteId: 'satellite-01',
  configVersion: 'v1.2.3',
  changedKeys: ['teams', 'mcpServers']
}, 'Configuration updated from backend');

HTTP Proxy Operations

// MCP client requests
server.log.debug({
  operation: 'mcp_request_proxy',
  clientId: 'vscode-client',
  teamId: 'team-123',
  mcpServer: 'filesystem-server',
  method: 'tools/list',
  responseTime: '45ms'
}, 'MCP request proxied successfully');

server.log.error({
  operation: 'mcp_request_failed',
  clientId: 'vscode-client',
  teamId: 'team-123',
  mcpServer: 'filesystem-server',
  method: 'tools/call',
  error: 'Server process not responding',
  statusCode: 503
}, 'MCP request failed');

Logger Parameter Injection Pattern

The Satellite follows the same logger injection pattern as the backend:

✅ DO: Pass Logger as Parameter to Services

// ✅ Good - MCP server manager accepts logger
class McpServerManager {
  static async spawnServer(config: McpServerConfig, logger: FastifyBaseLogger): Promise<McpServerProcess> {
    logger.debug({
      operation: 'mcp_server_spawn',
      serverId: config.id,
      teamId: config.teamId,
      command: config.command
    }, 'Spawning MCP server process');
    
    try {
      const process = await this.createProcess(config);
      
      logger.info({
        operation: 'mcp_server_spawned',
        serverId: config.id,
        teamId: config.teamId,
        pid: process.pid
      }, 'MCP server process spawned successfully');
      
      return process;
    } catch (error) {
      logger.error({
        operation: 'mcp_server_spawn_failed',
        serverId: config.id,
        teamId: config.teamId,
        error
      }, 'Failed to spawn MCP server process');
      throw error;
    }
  }
}

// ✅ Good - Team isolation service accepts logger
export async function setupTeamIsolation(teamId: string, logger: FastifyBaseLogger): Promise<boolean> {
  logger.info({
    operation: 'team_isolation_setup',
    teamId
  }, 'Setting up team isolation');
  
  try {
    // ... isolation setup logic
    logger.info({
      operation: 'team_isolation_ready',
      teamId
    }, 'Team isolation setup completed');
    return true;
  } catch (error) {
    logger.error({
      operation: 'team_isolation_failed',
      teamId,
      error
    }, 'Failed to setup team isolation');
    return false;
  }
}

✅ DO: Use Child Loggers for Persistent Context

// ✅ Good - Create child logger with satellite context
class SatelliteManager {
  private logger: FastifyBaseLogger;
  
  constructor(baseLogger: FastifyBaseLogger, satelliteId: string) {
    this.logger = baseLogger.child({ 
      satelliteId, 
      component: 'SatelliteManager' 
    });
  }
  
  async processTeamCommand(teamId: string, command: string) {
    const teamLogger = this.logger.child({ teamId });
    
    teamLogger.debug('Processing team command', { command });
    teamLogger.info('Team command completed');
  }
}

Satellite-Specific Context Objects

Always include relevant context that helps identify satellite operations:
// ✅ Good - Satellite-specific structured logging
server.log.info({
  satelliteId: 'satellite-01',
  satelliteType: 'global', // or 'team'
  operation: 'satellite_startup',
  port: 3001,
  version: '0.1.0'
}, 'Satellite service started');

// ✅ Good - Team-aware logging
server.log.debug({
  satelliteId: 'satellite-01',
  teamId: 'team-123',
  operation: 'mcp_tool_call',
  toolName: 'read_file',
  serverId: 'filesystem-server',
  userId: 'user-456',
  duration: '120ms'
}, 'MCP tool call completed');

// ✅ Good - Resource monitoring
server.log.warn({
  satelliteId: 'satellite-01',
  teamId: 'team-123',
  operation: 'resource_monitoring',
  cpuUsage: '0.08',
  memoryUsage: '78MB',
  processCount: 3,
  activeConnections: 12
}, 'Team resource usage update');
Best Practices for Satellite Context Objects:
  • Always include satelliteId: Identifies which satellite instance
  • Include teamId for team-specific operations
  • Add operation: Consistent field describing the operation
  • Include serverId for MCP server operations
  • Add performance metrics: Duration, resource usage, counts
  • Use consistent naming: camelCase and standard field names

Environment-Specific Configuration

Development Environment

# .env file for development
NODE_ENV=development
LOG_LEVEL=debug
PORT=3001
Features:
  • Pretty-printed, colorized output
  • Shows debug and trace information
  • Includes timestamps and context
  • Easier to read during development

Production Environment

# Production environment variables
NODE_ENV=production
LOG_LEVEL=info
PORT=3001
Features:
  • Structured JSON output
  • Optimized for log aggregation
  • Excludes debug information
  • Better performance

Testing Environment

# Testing environment
NODE_ENV=test
LOG_LEVEL=error
PORT=3002
Features:
  • Minimal log output during tests
  • Only shows errors and fatal messages
  • Reduces test noise

Common Satellite Logging Patterns

Satellite Lifecycle

// Satellite startup
server.log.info({
  operation: 'satellite_startup',
  satelliteId: 'satellite-01',
  port: 3001,
  version: '0.1.0'
}, '🚀 DeployStack Satellite starting');

server.log.info({
  operation: 'satellite_ready',
  satelliteId: 'satellite-01',
  endpoints: ['/api/health/hello', '/documentation']
}, '✅ Satellite service ready');

MCP Server Operations

// MCP server management
server.log.debug({
  operation: 'mcp_servers_discovery',
  teamId: 'team-123',
  availableServers: ['filesystem', 'web-search', 'calculator']
}, 'Discovering available MCP servers');

server.log.info({
  operation: 'mcp_server_health_check',
  serverId: 'filesystem-server',
  teamId: 'team-123',
  status: 'healthy',
  responseTime: '25ms'
}, 'MCP server health check passed');

Team Management

// Team operations
server.log.info({
  operation: 'team_registration',
  teamId: 'team-123',
  teamName: 'Engineering Team',
  memberCount: 5
}, 'Team registered with satellite');

server.log.warn({
  operation: 'team_quota_exceeded',
  teamId: 'team-123',
  quotaType: 'mcp_requests',
  currentUsage: 1050,
  limit: 1000
}, 'Team exceeded MCP request quota');

Troubleshooting

Debug Mode Not Working

If debug logs aren’t showing:
  1. Check LOG_LEVEL: Ensure it’s set to debug or trace in .env
  2. Check NODE_ENV: Development mode enables debug by default
  3. Restart Satellite: Environment changes require restart
# Force debug mode
LOG_LEVEL=debug npm run dev

Performance Issues

If logging is impacting satellite performance:
  1. Increase Log Level: Use info or warn in production
  2. Remove Excessive Debug Logs: Clean up verbose debug statements
  3. Use Async Logging: Pino handles this automatically

Log Aggregation

For production satellite monitoring:
// Add correlation IDs for request tracking
server.addHook('onRequest', async (request) => {
  request.log = request.log.child({ 
    requestId: request.id,
    satelliteId: process.env.SATELLITE_ID || 'unknown',
    userAgent: request.headers['user-agent']
  });
});

Migration from Console.log

Important: Replace all console.log statements with proper Pino logger calls to ensure consistent formatting and log level filtering.

Problem: Inconsistent Log Output

// ❌ Problem - Mixed logging approaches
console.log('✅ [McpServerManager] Server spawned');  // No timestamp or context
server.log.info('✅ MCP server ready');               // With timestamp and context

Solution: Use Proper Logger

// ✅ Solution - Consistent logging
class McpServerManager {
  private static logger = server.log.child({ component: 'McpServerManager' });
  
  static async spawnServer(config: McpServerConfig) {
    this.logger.debug('Spawning MCP server', { serverId: config.id });
    this.logger.info('MCP server spawned successfully', { serverId: config.id });
  }
}

Summary

  • Use proper log levels for satellite operations
  • Include satellite-specific context (satelliteId, teamId, serverId)
  • Follow backend logging patterns for consistency
  • Configure LOG_LEVEL via environment variables
  • Use child loggers for persistent context
  • Avoid console.log statements in favor of Pino logger
With proper log level configuration, the satellite service will have production-ready logging that scales from development to enterprise deployments, providing the observability needed for managing MCP servers and team isolation.
I