wingman-monitor
Version:
Runtime error monitoring package with React provider that automatically reports errors to webhook endpoints
375 lines • 15.4 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WingmanMonitor = void 0;
const axios_1 = __importDefault(require("axios"));
const config_1 = require("./config");
class WingmanMonitor {
constructor(projectPath) {
this.config = null;
this.isStarted = false;
this.originalErrorHandler = null;
this.originalUnhandledRejectionHandler = null;
this.recentLogs = [];
this.maxLogs = 50;
this.configManager = new config_1.ConfigManager(projectPath);
this.originalConsoleError = console.error;
this.originalConsoleWarn = console.warn;
this.originalConsoleLog = console.log;
}
captureConsoleLog(type, args) {
const logEntry = {
type,
message: args.map(arg => {
try {
return typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg);
}
catch {
return '[Circular Object]';
}
}).join(' '),
timestamp: Date.now(),
args
};
this.recentLogs.push(logEntry);
if (this.recentLogs.length > this.maxLogs) {
this.recentLogs.shift(); // Remove oldest log
}
}
getRelevantLogs(errorTimestamp, windowMs = 5000) {
// Get logs from 5 seconds before the error
const startTime = errorTimestamp - windowMs;
const relevantLogs = this.recentLogs.filter(log => log.timestamp >= startTime && log.timestamp <= errorTimestamp);
if (relevantLogs.length === 0)
return '';
let logContext = '\n\n--- Recent Console Activity ---\n';
relevantLogs.forEach(log => {
const timestamp = new Date(log.timestamp).toISOString();
logContext += `[${timestamp}] ${log.type.toUpperCase()}: ${log.message}\n`;
});
return logContext;
}
buildComprehensiveMessage(primaryMessage, errorContext = {}) {
let message = primaryMessage;
// Add relevant recent logs
const relevantLogs = this.getRelevantLogs(Date.now());
if (relevantLogs) {
message += relevantLogs;
}
// Add error context
if (errorContext.url) {
message += `\n\n--- Context ---\nURL: ${errorContext.url}`;
}
if (errorContext.userAgent) {
message += `\nUser Agent: ${errorContext.userAgent}`;
}
if (errorContext.stack) {
message += `\n\n--- Stack Trace ---\n${errorContext.stack}`;
}
return message;
}
async start() {
try {
// Load configuration
this.config = await this.configManager.load();
if (!this.config) {
console.warn('Wingman: No configuration found. Run "wingman init <accessToken>" first.');
return;
}
if (!this.config.enabled) {
console.log('Wingman: Monitoring is disabled.');
return;
}
if (this.isStarted) {
console.warn('Wingman: Already started.');
return;
}
this.setupErrorHandlers();
this.isStarted = true;
console.log(`Wingman: Monitoring started for ${this.config.environment} environment`);
}
catch (error) {
console.error('Wingman: Failed to start monitoring:', error);
}
}
stop() {
if (!this.isStarted)
return;
this.removeErrorHandlers();
this.isStarted = false;
console.log('Wingman: Monitoring stopped');
}
setupErrorHandlers() {
// Handle uncaught exceptions (Node.js)
if (typeof process !== 'undefined' && process.on) {
process.on('uncaughtException', this.handleUncaughtException.bind(this));
process.on('unhandledRejection', this.handleUnhandledRejection.bind(this));
}
// Handle browser errors
if (typeof window !== 'undefined') {
this.originalErrorHandler = window.onerror;
this.originalUnhandledRejectionHandler = window.onunhandledrejection;
window.onerror = this.handleWindowError.bind(this);
window.onunhandledrejection = this.handleWindowUnhandledRejection.bind(this);
}
// Monkey patch console methods for log capture and error tracking
this.originalConsoleError = console.error;
this.originalConsoleWarn = console.warn;
this.originalConsoleLog = console.log;
console.error = (...args) => {
this.originalConsoleError.apply(console, args);
this.captureConsoleLog('error', args);
this.handleConsoleError(args);
};
console.warn = (...args) => {
this.originalConsoleWarn.apply(console, args);
this.captureConsoleLog('warn', args);
};
console.log = (...args) => {
this.originalConsoleLog.apply(console, args);
this.captureConsoleLog('log', args);
};
}
removeErrorHandlers() {
if (typeof process !== 'undefined' && process.removeListener) {
process.removeListener('uncaughtException', this.handleUncaughtException);
process.removeListener('unhandledRejection', this.handleUnhandledRejection);
}
if (typeof window !== 'undefined') {
if (this.originalErrorHandler) {
window.onerror = this.originalErrorHandler;
}
if (this.originalUnhandledRejectionHandler) {
window.onunhandledrejection = this.originalUnhandledRejectionHandler;
}
}
}
handleUncaughtException(error) {
const timestamp = Date.now();
const comprehensiveMessage = this.buildComprehensiveMessage(error.message, {
stack: error.stack,
url: typeof window !== 'undefined' ? window.location?.href : undefined,
userAgent: typeof window !== 'undefined' ? navigator?.userAgent : undefined
});
this.reportError({
message: comprehensiveMessage,
stack: error.stack,
timestamp,
environment: this.config?.environment || 'unknown',
projectInfo: this.getProjectInfo(),
errorType: 'uncaughtException',
severity: 'critical',
accessToken: this.config?.accessToken || ''
});
}
handleUnhandledRejection(reason) {
const timestamp = Date.now();
const message = reason instanceof Error ? reason.message : String(reason);
const stack = reason instanceof Error ? reason.stack : undefined;
const comprehensiveMessage = this.buildComprehensiveMessage(message, {
stack,
url: typeof window !== 'undefined' ? window.location?.href : undefined,
userAgent: typeof window !== 'undefined' ? navigator?.userAgent : undefined
});
this.reportError({
message: comprehensiveMessage,
stack,
timestamp,
environment: this.config?.environment || 'unknown',
projectInfo: this.getProjectInfo(),
errorType: 'unhandledRejection',
severity: 'high',
accessToken: this.config?.accessToken || ''
});
}
handleWindowError(message, source, lineno, colno, error) {
const timestamp = Date.now();
const errorMessage = typeof message === 'string' ? message : 'Unknown error';
const comprehensiveMessage = this.buildComprehensiveMessage(errorMessage, {
stack: error?.stack,
url: window.location?.href,
userAgent: navigator?.userAgent,
source,
lineno,
colno
});
this.reportError({
message: comprehensiveMessage,
stack: error?.stack,
timestamp,
environment: this.config?.environment || 'unknown',
projectInfo: this.getProjectInfo(),
errorType: 'windowError',
severity: 'medium',
accessToken: this.config?.accessToken || '',
metadata: {
source,
lineno,
colno
}
});
return this.originalErrorHandler ? this.originalErrorHandler(message, source, lineno, colno, error) : true;
}
handleWindowUnhandledRejection(event) {
const timestamp = Date.now();
const reason = event.reason;
const message = reason instanceof Error ? reason.message : String(reason);
const stack = reason instanceof Error ? reason.stack : undefined;
const comprehensiveMessage = this.buildComprehensiveMessage(message, {
stack,
url: window.location?.href,
userAgent: navigator?.userAgent
});
this.reportError({
message: comprehensiveMessage,
stack,
timestamp,
environment: this.config?.environment || 'unknown',
projectInfo: this.getProjectInfo(),
errorType: 'windowUnhandledRejection',
severity: 'high',
accessToken: this.config?.accessToken || ''
});
if (this.originalUnhandledRejectionHandler) {
this.originalUnhandledRejectionHandler(event);
}
return true;
}
handleConsoleError(args) {
const timestamp = Date.now();
const message = args.map(arg => typeof arg === 'string' ? arg : JSON.stringify(arg)).join(' ');
// Only report if it looks like an actual error
if (message.toLowerCase().includes('error') || args.some(arg => arg instanceof Error)) {
const comprehensiveMessage = this.buildComprehensiveMessage(message, {
url: typeof window !== 'undefined' ? window.location?.href : undefined,
userAgent: typeof window !== 'undefined' ? navigator?.userAgent : undefined
});
this.reportError({
message: comprehensiveMessage,
timestamp,
environment: this.config?.environment || 'unknown',
projectInfo: this.getProjectInfo(),
errorType: 'consoleError',
severity: 'low',
accessToken: this.config?.accessToken || '',
metadata: {
args: args.map(arg => {
try {
return typeof arg === 'object' ? JSON.stringify(arg) : String(arg);
}
catch {
return '[Circular Object]';
}
})
}
});
}
}
async reportError(errorReport) {
if (!this.config)
return;
try {
// Get webhook URL from environment variable or default (same as CLI)
const webhookUrl = process.env.WINGMAN_WEBHOOK_URL || 'https://patchworks-seven.vercel.app/webhook';
// Format payload to match webhook's expected structure
const payload = {
event: 'error.runtime',
data: {
message: errorReport.message,
errorType: errorReport.errorType,
severity: errorReport.severity,
environment: errorReport.environment,
accessToken: errorReport.accessToken,
timestamp: errorReport.timestamp, // This is now a number (Date.now())
stack: errorReport.stack,
projectInfo: errorReport.projectInfo,
metadata: errorReport.metadata
},
timestamp: errorReport.timestamp,
source: 'wingman-monitor'
};
await axios_1.default.post(webhookUrl, payload, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${errorReport.accessToken}`,
'User-Agent': 'Wingman-Monitor/1.0.0'
},
timeout: 5000 // 5 second timeout
});
console.log('Wingman: Error reported successfully');
}
catch (error) {
console.error('Wingman: Failed to report error:', error);
}
}
getProjectInfo() {
try {
// Try to read package.json for project info
const fs = require('fs');
const path = require('path');
const packageJsonPath = path.join(this.config?.projectPath || process.cwd(), 'package.json');
if (fs.existsSync(packageJsonPath)) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
return {
name: packageJson.name,
version: packageJson.version,
url: packageJson.homepage || packageJson.repository?.url
};
}
}
catch (error) {
// Ignore errors when reading package.json
}
return {};
}
// Public method to manually report errors
async reportCustomError(error, metadata) {
const timestamp = Date.now();
const comprehensiveMessage = this.buildComprehensiveMessage(error.message, {
stack: error.stack,
url: typeof window !== 'undefined' ? window.location?.href : undefined,
userAgent: typeof window !== 'undefined' ? navigator?.userAgent : undefined
});
await this.reportError({
message: comprehensiveMessage,
stack: error.stack,
timestamp,
environment: this.config?.environment || 'unknown',
projectInfo: this.getProjectInfo(),
errorType: 'customError',
severity: 'medium',
accessToken: this.config?.accessToken || '',
metadata
});
}
// Check if monitoring is active
isActive() {
return this.isStarted && this.config?.enabled === true;
}
// Temporary method for testing webhook calls
async testWebhook() {
const testError = new Error('Temporary test error');
const timestamp = Date.now();
const comprehensiveMessage = this.buildComprehensiveMessage(testError.message, {
stack: testError.stack,
url: typeof window !== 'undefined' ? window.location?.href : undefined,
userAgent: typeof window !== 'undefined' ? navigator?.userAgent : undefined
});
await this.reportError({
message: comprehensiveMessage,
stack: testError.stack,
timestamp,
environment: this.config?.environment || 'test',
projectInfo: this.getProjectInfo(),
errorType: 'testError',
severity: 'low',
accessToken: this.config?.accessToken || '',
metadata: { test: true }
});
console.log('Test webhook call sent successfully.');
}
}
exports.WingmanMonitor = WingmanMonitor;
//# sourceMappingURL=monitor.js.map