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
Log Filtering and Search
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
- Error Handling - Comprehensive error management
- Connection Callbacks - Monitor connection state
- Storage - Debug storage operations
tip
In production, set log level to ERROR to minimize performance impact while still capturing critical issues.