@erickluis00/otelviewer
Version:
Shared OpenTelemetry tracing utilities, types, and batch processor for Realtime OpenTelemetry Viewer [WIP]
204 lines • 8.02 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.startRemoteExecutorServer = startRemoteExecutorServer;
exports.stopRemoteExecutorServer = stopRemoteExecutorServer;
exports.getRemoteExecutorPort = getRemoteExecutorPort;
exports.autoStartRemoteExecutorIfNeeded = autoStartRemoteExecutorIfNeeded;
const http_1 = require("http");
const tracing_utils_1 = require("./tracing-utils");
const api_1 = require("@opentelemetry/api");
let remoteExecutorServer = null;
let serverPort = null;
/**
* Parse request body (JSON or form-encoded)
*/
function parseBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
try {
const contentType = req.headers['content-type'] || '';
if (contentType.includes('application/x-www-form-urlencoded')) {
// Parse form-encoded data
const params = new URLSearchParams(body);
const key = params.get('key');
const argsString = params.get('args');
const args = argsString ? JSON.parse(argsString) : [];
resolve({ key, args });
}
else {
// Parse JSON (fallback)
resolve(JSON.parse(body));
}
}
catch (error) {
reject(new Error('Invalid request body'));
}
});
req.on('error', reject);
});
}
/**
* Send JSON response
*/
function sendJSON(res, statusCode, data) {
res.writeHead(statusCode, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
});
res.end(JSON.stringify(data));
}
/**
* Start the remote executor server (DEV ONLY)
*/
function startRemoteExecutorServer(port = 3033) {
return new Promise((resolve, reject) => {
if (process.env.NODE_ENV === 'production') {
console.warn('[OTEL Remote] Remote executor is disabled in production');
reject(new Error('Remote executor is disabled in production'));
return;
}
if (remoteExecutorServer) {
resolve(serverPort);
return;
}
remoteExecutorServer = (0, http_1.createServer)(async (req, res) => {
// Handle CORS preflight
if (req.method === 'OPTIONS') {
sendJSON(res, 200, { ok: true });
return;
}
const url = req.url || '';
if (url === '/' && req.method === 'GET') {
sendJSON(res, 200, { ok: true });
return;
}
// List all executable methods
if (url === '/remote-exec/list' && req.method === 'GET') {
const executables = (0, tracing_utils_1.getRemoteExecutables)();
const list = Array.from(executables.entries()).map(([key, method]) => ({
key,
className: method.className,
methodName: method.methodName,
isStatic: method.isStatic,
spanPrefix: method.spanPrefix,
icon: method.icon,
iconColor: method.iconColor,
}));
sendJSON(res, 200, { success: true, data: list });
return;
}
// Execute a method
if (url === '/remote-exec/execute' && req.method === 'POST') {
try {
const body = await parseBody(req);
const { key, args = [] } = body;
if (!key || typeof key !== 'string') {
sendJSON(res, 400, { success: false, error: 'Missing or invalid key parameter' });
return;
}
if (!Array.isArray(args)) {
sendJSON(res, 400, { success: false, error: 'args must be an array' });
return;
}
// Get traceId from the HTTP span (automatically created by OpenTelemetry)
const activeSpan = api_1.trace.getActiveSpan();
const traceId = activeSpan?.spanContext().traceId;
// Return traceId immediately
sendJSON(res, 200, { success: true, traceId });
// Execute method in background (fire and forget)
(0, tracing_utils_1.executeRemoteMethod)(key, args).catch((error) => {
console.error('[OTEL Remote] Background execution error:', error);
});
}
catch (error) {
// Only handle validation errors (before getting traceId)
const errorMessage = error instanceof Error ? error.message : String(error);
sendJSON(res, 500, { success: false, error: errorMessage });
}
return;
}
// Health check
if (url === '/remote-exec/health' && req.method === 'GET') {
sendJSON(res, 200, {
success: true,
data: {
status: 'ok',
registeredMethods: (0, tracing_utils_1.getRemoteExecutables)().size
}
});
return;
}
// Not found
sendJSON(res, 404, { success: false, error: 'Not found' });
});
remoteExecutorServer.on('error', (error) => {
if (error.code === 'EADDRINUSE') {
console.warn(`[OTEL Remote] Port ${port} is in use, trying ${port + 1}`);
startRemoteExecutorServer(port + 1)
.then(resolve)
.catch(reject);
}
else {
reject(error);
}
});
remoteExecutorServer.listen(port, '127.0.0.1', () => {
serverPort = port;
console.log(`\n${'='.repeat(60)}`);
console.log(`[OTEL Remote] ✅ Executor server running on http://127.0.0.1:${port}`);
console.log(`[OTEL Remote] 🌐 Tunnel URL: https://remote-runner-server.localto.net`);
console.log(`[OTEL Remote] Available endpoints:`);
console.log(` - GET /remote-exec/list - List all executable methods`);
console.log(` - POST /remote-exec/execute - Execute a method`);
console.log(` - GET /remote-exec/health - Health check`);
console.log(`${'='.repeat(60)}\n`);
resolve(port);
});
});
}
/**
* Stop the remote executor server
*/
function stopRemoteExecutorServer() {
return new Promise((resolve) => {
if (!remoteExecutorServer) {
resolve();
return;
}
remoteExecutorServer.close(() => {
remoteExecutorServer = null;
serverPort = null;
resolve();
});
});
}
/**
* Get the current server port
*/
function getRemoteExecutorPort() {
return serverPort;
}
/**
* Auto-start server if there are any registered executables
*/
function autoStartRemoteExecutorIfNeeded(port = 3033) {
if (process.env.NODE_ENV === 'production') {
return;
}
// Delay to allow classes to be registered first
setTimeout(() => {
const executables = (0, tracing_utils_1.getRemoteExecutables)();
if (executables.size > 0 && !remoteExecutorServer) {
startRemoteExecutorServer(port).catch((error) => {
console.error('[OTEL Remote] Failed to start executor server:', error);
});
}
}, 1000);
}
//# sourceMappingURL=remote-executor.js.map