- 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
161 lines
4.9 KiB
JavaScript
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`);
|
|
});
|