Skip to main content

Debug Logging

The Moitribe SDK includes a comprehensive logging system to help you debug issues and monitor SDK operations.

Logger Overview

The SDK provides three log levels:

  • DEBUG (1) - Detailed debugging information
  • INFO (2) - General information messages
  • ERROR (3) - Error messages only

Basic Usage

Import and Configure

import { Logger, LogLevel } from '@veniso/moitribe-js';

// Set log level (default is INFO)
Logger.setLogLevel(LogLevel.DEBUG);

// Get current log level
const currentLevel = Logger.getLogLevel();
console.log('Current log level:', currentLevel);

Logging Messages

// Debug messages (only shown when log level is DEBUG)
Logger.debug('Initializing SDK...');
Logger.debug('API call parameters:', { gameId: 'my-game', method: 'getprofile' });
Logger.debug('Response received:', result);

// Info messages (shown at INFO level and above)
Logger.info('SDK initialized successfully');
Logger.info('Player authenticated:', playerName);
Logger.info('Score submitted to leaderboard:', leaderboardId);

// Error messages (always shown)
Logger.error('Failed to load profile:', errorMessage);
Logger.error('Network connection lost:', errorCode);
Logger.error('Invalid parameters provided:', validationErrors);

Log Level Configuration

Development vs Production

// Development - enable all logging
if (process.env.NODE_ENV === 'development') {
Logger.setLogLevel(LogLevel.DEBUG);
} else {
// Production - only show errors
Logger.setLogLevel(LogLevel.ERROR);
}

// Browser-based development detection
if (typeof window !== 'undefined' && (window as any).__DEV__) {
Logger.setLogLevel(LogLevel.DEBUG);
}

Dynamic Log Level Control

class LogManager {
private static isDebugMode = false;

static enableDebugMode(): void {
Logger.setLogLevel(LogLevel.DEBUG);
this.isDebugMode = true;
console.log('Debug mode enabled');
}

static disableDebugMode(): void {
Logger.setLogLevel(LogLevel.INFO);
this.isDebugMode = false;
console.log('Debug mode disabled');
}

static toggleDebugMode(): void {
if (this.isDebugMode) {
this.disableDebugMode();
} else {
this.enableDebugMode();
}
}

static isDebugEnabled(): boolean {
return this.isDebugMode;
}
}

// Usage
LogManager.enableDebugMode();
LogManager.toggleDebugMode();

Advanced Logging Patterns

Contextual Logging

class GameLogger {
private static context: string = 'Game';

static setContext(context: string): void {
this.context = context;
}

static debug(message: string, data?: any): void {
const formattedMessage = `[${this.context}] ${message}`;
if (data) {
Logger.debug(formattedMessage, data);
} else {
Logger.debug(formattedMessage);
}
}

static info(message: string, data?: any): void {
const formattedMessage = `[${this.context}] ${message}`;
if (data) {
Logger.info(formattedMessage, data);
} else {
Logger.info(formattedMessage);
}
}

static error(message: string, error?: any): void {
const formattedMessage = `[${this.context}] ${message}`;
if (error) {
Logger.error(formattedMessage, error);
} else {
Logger.error(formattedMessage);
}
}
}

// Usage
GameLogger.setContext('ProfileManager');
GameLogger.debug('Loading player profile...');
GameLogger.info('Profile loaded successfully', { playerId: '123', name: 'Alex' });
GameLogger.error('Failed to load profile', { errorCode: 13003 });

Performance Logging

class PerformanceLogger {
private static timers: Map<string, number> = new Map();

static startTimer(operation: string): void {
this.timers.set(operation, performance.now());
Logger.debug(`Timer started: ${operation}`);
}

static endTimer(operation: string): number {
const startTime = this.timers.get(operation);
if (!startTime) {
Logger.error(`Timer not found: ${operation}`);
return 0;
}

const duration = performance.now() - startTime;
this.timers.delete(operation);

Logger.info(`Operation completed: ${operation}`, {
duration: `${duration.toFixed(2)}ms`
});

return duration;
}

static measureAsync<T>(operation: string, asyncFunction: () => Promise<T>): Promise<T> {
this.startTimer(operation);

return asyncFunction()
.then(result => {
this.endTimer(operation);
return result;
})
.catch(error => {
this.endTimer(operation);
Logger.error(`Operation failed: ${operation}`, error);
throw error;
});
}
}

// Usage
PerformanceLogger.startTimer('profile_load');
MoitribeSDK('my-game', 'getprofile', {}, (result) => {
PerformanceLogger.endTimer('profile_load');
console.log('Profile loaded:', result.success);
});

// With async operations
const profile = await PerformanceLogger.measureAsync('async_profile_load', async () => {
return SDKPromise.wrap('my-game', 'getprofile');
});

SDK Operation Logging

class SDKOperationLogger {
static logAPICall(gameId: string, method: string, params: any): void {
Logger.debug('SDK API Call', {
gameId,
method,
params: this.sanitizeParams(params),
timestamp: new Date().toISOString()
});
}

static logAPIResponse(gameId: string, method: string, response: any, duration: number): void {
const logLevel = response.success ? 'info' : 'error';

Logger[logLevel]('SDK API Response', {
gameId,
method,
success: response.success,
duration: `${duration.toFixed(2)}ms`,
timestamp: new Date().toISOString(),
...(response.success ? {} : { error: response.message, code: response.code })
});
}

private static sanitizeParams(params: any): any {
// Remove sensitive data from logs
const sanitized = { ...params };

// Remove password fields
if (sanitized.password) {
sanitized.password = '[REDACTED]';
}

// Remove OTP fields
if (sanitized.otp) {
sanitized.otp = '[REDACTED]';
}

return sanitized;
}
}

// Wrap SDK calls with logging
function loggedSDKCall<T>(gameId: string, method: string, params: any, callback: (result: T) => void): void {
const startTime = performance.now();

SDKOperationLogger.logAPICall(gameId, method, params);

MoitribeSDK(gameId, method, params, (result: T) => {
const duration = performance.now() - startTime;
SDKOperationLogger.logAPIResponse(gameId, method, result, duration);
callback(result);
});
}

Remote Logging

Server-Side Logging

interface RemoteLogEntry {
level: 'debug' | 'info' | 'error';
message: string;
timestamp: string;
context?: string;
data?: any;
userAgent?: string;
gameId?: string;
}

class RemoteLogger {
private static endpoint: string = 'https://api.example.com/logs';
private static isEnabled: boolean = false;
private static logQueue: RemoteLogEntry[] = [];
private static maxQueueSize: number = 100;

static enable(endpoint: string): void {
this.endpoint = endpoint;
this.isEnabled = true;
Logger.info('Remote logging enabled', { endpoint });
}

static disable(): void {
this.isEnabled = false;
Logger.info('Remote logging disabled');
}

static log(level: 'debug' | 'info' | 'error', message: string, data?: any): void {
if (!this.isEnabled) return;

const entry: RemoteLogEntry = {
level,
message,
timestamp: new Date().toISOString(),
data,
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'Node.js'
};

this.logQueue.push(entry);

if (this.logQueue.length >= this.maxQueueSize) {
this.flush();
}
}

static async flush(): Promise<void> {
if (this.logQueue.length === 0) return;

const logs = [...this.logQueue];
this.logQueue = [];

try {
await fetch(this.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ logs })
});

Logger.debug('Remote logs sent successfully', { count: logs.length });
} catch (error) {
Logger.error('Failed to send remote logs', error);
// Re-add logs to queue for retry
this.logQueue.unshift(...logs);
}
}
}

// Hook into SDK logger
const originalDebug = Logger.debug.bind(Logger);
const originalInfo = Logger.info.bind(Logger);
const originalError = Logger.error.bind(Logger);

Logger.debug = (...args: any[]) => {
originalDebug(...args);
RemoteLogger.log('debug', args[0], args[1]);
};

Logger.info = (...args: any[]) => {
originalInfo(...args);
RemoteLogger.log('info', args[0], args[1]);
};

Logger.error = (...args: any[]) => {
originalError(...args);
RemoteLogger.log('error', args[0], args[1]);
};

// Flush logs periodically
setInterval(() => {
RemoteLogger.flush();
}, 30000); // Every 30 seconds

Client-Side Log Management

class LogFilter {
private static logs: Array<{
level: string;
message: string;
timestamp: number;
data?: any;
}> = [];

static capture(level: string, message: string, data?: any): void {
this.logs.push({
level,
message,
timestamp: Date.now(),
data
});

// Keep only last 1000 logs
if (this.logs.length > 1000) {
this.logs = this.logs.slice(-1000);
}
}

static filterByLevel(level: string): typeof this.logs {
return this.logs.filter(log => log.level === level);
}

static searchByMessage(searchTerm: string): typeof this.logs {
const term = searchTerm.toLowerCase();
return this.logs.filter(log =>
log.message.toLowerCase().includes(term)
);
}

static filterByTimeRange(startTime: number, endTime: number): typeof this.logs {
return this.logs.filter(log =>
log.timestamp >= startTime && log.timestamp <= endTime
);
}

static getRecentLogs(count: number = 50): typeof this.logs {
return this.logs.slice(-count);
}

static exportLogs(): string {
return JSON.stringify(this.logs, null, 2);
}

static clearLogs(): void {
this.logs = [];
}
}

// Hook into logger to capture all logs
const originalMethods = {
debug: Logger.debug.bind(Logger),
info: Logger.info.bind(Logger),
error: Logger.error.bind(Logger)
};

Logger.debug = (...args: any[]) => {
originalMethods.debug(...args);
LogFilter.capture('debug', args[0], args[1]);
};

Logger.info = (...args: any[]) => {
originalMethods.info(...args);
LogFilter.capture('info', args[0], args[1]);
};

Logger.error = (...args: any[]) => {
originalMethods.error(...args);
LogFilter.capture('error', args[0], args[1]);
};

Browser Console Integration

Enhanced Console Output

class ConsoleEnhancer {
static enhance(): void {
// Add styling to console output
const originalDebug = console.debug.bind(console);
const originalInfo = console.info.bind(console);
const originalError = console.error.bind(console);

console.debug = (...args: any[]) => {
originalDebug('%c[DEBUG]', 'color: #888; font-weight: bold', ...args);
};

console.info = (...args: any[]) => {
originalInfo('%c[INFO]', 'color: #007acc; font-weight: bold', ...args);
};

console.error = (...args: any[]) => {
originalError('%c[ERROR]', 'color: #ff4444; font-weight: bold', ...args);
};
}

static createLogGroup(title: string, logs: Array<{ level: string; message: string; data?: any }>): void {
console.group(`%c${title}`, 'color: #333; font-weight: bold; font-size: 14px');

logs.forEach(log => {
const style = this.getLevelStyle(log.level);
console.log(`%c[${log.level.toUpperCase()}]`, style, log.message, log.data || '');
});

console.groupEnd();
}

private static getLevelStyle(level: string): string {
switch (level) {
case 'debug': return 'color: #888; font-weight: bold';
case 'info': return 'color: #007acc; font-weight: bold';
case 'error': return 'color: #ff4444; font-weight: bold';
default: return 'color: #333; font-weight: bold';
}
}
}

// Enable console enhancements in browser
if (typeof window !== 'undefined') {
ConsoleEnhancer.enhance();
}

Best Practices

1. Use Appropriate Log Levels

// ✓ Good - appropriate levels
Logger.debug('Processing request with parameters:', params); // Detailed info
Logger.info('User logged in successfully'); // Important events
Logger.error('Database connection failed', error); // Problems

// ✗ Bad - wrong levels
Logger.error('User logged in successfully'); // Not an error
Logger.debug('Database connection failed', error); // Should be error

2. Include Context

// ✓ Good - with context
Logger.error('Failed to load profile', {
playerId: '123',
errorCode: 13003,
timestamp: Date.now()
});

// ✗ Bad - no context
Logger.error('Profile load failed');

3. Avoid Sensitive Data

// ✓ Good - sanitized
Logger.debug('Authentication attempt', {
username: 'player123',
password: '[REDACTED]'
});

// ✗ Bad - sensitive data exposed
Logger.debug('Authentication attempt', {
username: 'player123',
password: 'secret123'
});

4. Performance Considerations

// ✓ Good - conditional expensive operations
if (Logger.getLogLevel() <= LogLevel.DEBUG) {
Logger.debug('Large object:', JSON.stringify(hugeObject));
}

// ✗ Bad - always expensive
Logger.debug('Large object:', JSON.stringify(hugeObject));

Next Steps

tip

In production, set log level to ERROR to minimize performance impact while still capturing critical issues.