Files
bigtux 771cf6cf50 feat: Add OpenTelemetry OTLP HTTP receiver
- Add POST /v1/traces endpoint for OTLP JSON trace ingestion
- Convert OTLP spans to internal format and save to PostgreSQL
- Manual JSON parsing (no Go 1.24 dependencies)
- Add Node.js instrumentation example with Express
- Add Python instrumentation example with Flask
- Auto-instrumentation support for both languages
2026-02-06 14:59:29 -03:00

161 lines
4.9 KiB
JavaScript

// ═══════════════════════════════════════════════════════════
// 📱 Example Express App with OpenTelemetry
// ═══════════════════════════════════════════════════════════
//
// Run with: npm run trace
// Or: node --require ./tracing.js app.js
const express = require('express');
const { trace, SpanStatusCode } = require('@opentelemetry/api');
const app = express();
const PORT = process.env.PORT || 3000;
// Get a tracer for manual instrumentation
const tracer = trace.getTracer('example-app');
// Simulated database
const users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
{ id: 3, name: 'Charlie', email: 'charlie@example.com' },
];
// Middleware to add request tracing
app.use((req, res, next) => {
const span = trace.getActiveSpan();
if (span) {
span.setAttribute('http.user_agent', req.get('user-agent') || 'unknown');
}
next();
});
// Health check
app.get('/health', (req, res) => {
res.json({ status: 'healthy', service: 'nodejs-example' });
});
// Get all users (with manual span)
app.get('/users', async (req, res) => {
// Create a custom span for database operation
const span = tracer.startSpan('db.query.users');
span.setAttribute('db.system', 'memory');
span.setAttribute('db.operation', 'SELECT');
try {
// Simulate database latency
await sleep(Math.random() * 100);
span.setAttribute('db.row_count', users.length);
span.setStatus({ code: SpanStatusCode.OK });
res.json({ users });
} catch (error) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
});
span.recordException(error);
res.status(500).json({ error: error.message });
} finally {
span.end();
}
});
// Get user by ID
app.get('/users/:id', async (req, res) => {
const userId = parseInt(req.params.id);
const span = tracer.startSpan('db.query.user_by_id');
span.setAttribute('db.system', 'memory');
span.setAttribute('db.operation', 'SELECT');
span.setAttribute('user.id', userId);
try {
await sleep(Math.random() * 50);
const user = users.find(u => u.id === userId);
if (!user) {
span.setStatus({ code: SpanStatusCode.ERROR, message: 'User not found' });
res.status(404).json({ error: 'User not found' });
} else {
span.setStatus({ code: SpanStatusCode.OK });
res.json({ user });
}
} finally {
span.end();
}
});
// Create order (simulates complex operation with nested spans)
app.post('/orders', express.json(), async (req, res) => {
const parentSpan = tracer.startSpan('order.create');
try {
// Step 1: Validate inventory
const inventorySpan = tracer.startSpan('inventory.check', {
attributes: { 'order.items': req.body.items?.length || 0 },
});
await sleep(Math.random() * 100);
inventorySpan.setStatus({ code: SpanStatusCode.OK });
inventorySpan.end();
// Step 2: Process payment
const paymentSpan = tracer.startSpan('payment.process', {
attributes: { 'payment.method': 'credit_card' },
});
await sleep(Math.random() * 200);
paymentSpan.setStatus({ code: SpanStatusCode.OK });
paymentSpan.end();
// Step 3: Create order record
const createSpan = tracer.startSpan('db.insert.order');
await sleep(Math.random() * 50);
const orderId = Date.now().toString(36);
createSpan.setAttribute('order.id', orderId);
createSpan.setStatus({ code: SpanStatusCode.OK });
createSpan.end();
parentSpan.setStatus({ code: SpanStatusCode.OK });
res.json({ orderId, status: 'created' });
} catch (error) {
parentSpan.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
parentSpan.recordException(error);
res.status(500).json({ error: error.message });
} finally {
parentSpan.end();
}
});
// Simulate error endpoint (for testing error traces)
app.get('/error', (req, res) => {
const span = trace.getActiveSpan();
const error = new Error('Simulated error for testing');
if (span) {
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
}
res.status(500).json({ error: error.message });
});
// Helper
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Start server
app.listen(PORT, () => {
console.log(`🚀 Example app listening on http://localhost:${PORT}`);
console.log('');
console.log('Try these endpoints:');
console.log(` GET http://localhost:${PORT}/health`);
console.log(` GET http://localhost:${PORT}/users`);
console.log(` GET http://localhost:${PORT}/users/1`);
console.log(` POST http://localhost:${PORT}/orders`);
console.log(` GET http://localhost:${PORT}/error`);
});