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
This commit is contained in:
160
examples/otel-nodejs/app.js
Normal file
160
examples/otel-nodejs/app.js
Normal file
@@ -0,0 +1,160 @@
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 📱 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`);
|
||||
});
|
||||
Reference in New Issue
Block a user