-
Notifications
You must be signed in to change notification settings - Fork 0
OpenTelemetry Migration Guides
π Enterprise-grade migration paths that maintain existing workflows while adding powerful capabilities
β
Incremental Adoption - Start small, expand gradually
β
Backward Compatibility - Keep existing logging during transition
β
Zero Downtime - Deploy changes safely without service interruption
β
Team Onboarding - Clear patterns for team adoption
// Typical current code
app.get('/users/:id', async (req, res) => {
console.log('Getting user', req.params.id)
const user = await db.users.findById(req.params.id)
console.log('User found:', user ? 'yes' : 'no')
if (!user) {
console.log('User not found, returning 404')
return res.status(404).json({ error: 'Not found' })
}
console.log('Returning user data')
res.json(user)
})
Goal: Introduce structured, categorized logging without removing existing logs
const { RDCPClient } = require('@rdcp/server')
const rdcp = new RDCPClient({
apiKey: process.env.RDCP_API_KEY || 'dev-key-32-characters-minimum-length'
})
app.get('/users/:id', async (req, res) => {
console.log('Getting user', req.params.id) // β Keep existing
rdcp.debug.api('User request started', { userId: req.params.id }) // β Add structured
const user = await db.users.findById(req.params.id)
console.log('User found:', user ? 'yes' : 'no') // β Keep existing
rdcp.debug.database('User query completed', { // β Add structured
userId: req.params.id,
found: !!user,
table: 'users'
})
if (!user) {
console.log('User not found, returning 404') // β Keep existing
rdcp.debug.api('User not found', { userId: req.params.id }) // β Add structured
return res.status(404).json({ error: 'Not found' })
}
console.log('Returning user data') // β Keep existing
rdcp.debug.api('User response sent', { userId: user.id }) // β Add structured
res.json(user)
})
Benefits Immediate:
- β Structured, searchable debug logs
- β Runtime enable/disable by category
- β Existing console.logs still work
- β Team can evaluate RDCP value
Goal: Connect debug logs to distributed traces
const { setupRDCPWithOpenTelemetry } = require('@rdcp.dev/otel-plugin')
// Add OpenTelemetry setup (see Framework Examples for complete setup)
setupRDCPWithOpenTelemetry(rdcp)
// Same code as Phase 1 - but now debug logs include trace correlation automatically!
Benefits Added:
- β Perfect trace β debug log correlation
- β Click trace ID, find exact debug context
- β Distributed request tracking across services
Goal: Replace console.log with RDCP as team adopts
app.get('/users/:id', async (req, res) => {
// console.log('Getting user', req.params.id) // β Remove when comfortable
rdcp.debug.api('User request started', { userId: req.params.id })
const user = await db.users.findById(req.params.id)
// console.log('User found:', user ? 'yes' : 'no') // β Remove when comfortable
rdcp.debug.database('User query completed', {
userId: req.params.id,
found: !!user,
table: 'users'
})
if (!user) {
// console.log('User not found, returning 404') // β Remove when comfortable
rdcp.debug.api('User not found', { userId: req.params.id })
return res.status(404).json({ error: 'Not found' })
}
// console.log('Returning user data') // β Remove when comfortable
rdcp.debug.api('User response sent', { userId: user.id })
res.json(user)
})
Migration Timeline:
- Week 1-2: Phase 1 (Add RDCP alongside)
- Week 3: Phase 2 (Add OpenTelemetry correlation)
- Month 2-3: Phase 3 (Gradually replace console.log)
const winston = require('winston')
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'app.log' }),
new winston.transports.Console()
]
})
app.get('/orders/:id', async (req, res) => {
logger.info('Order request received', { orderId: req.params.id })
const order = await db.orders.findById(req.params.id)
logger.info('Database query completed', {
orderId: req.params.id,
found: !!order,
duration: '45ms'
})
if (!order) {
logger.warn('Order not found', { orderId: req.params.id })
return res.status(404).json({ error: 'Order not found' })
}
logger.info('Order retrieved successfully', { orderId: order.id })
res.json(order)
})
Goal: Keep Winston for persistent logging, add RDCP for runtime debug control
const { RDCPClient } = require('@rdcp/server')
// Keep existing Winston logger
const logger = winston.createLogger({ /* existing config */ })
// Add RDCP for runtime-controllable debug logs
const rdcp = new RDCPClient({
apiKey: process.env.RDCP_API_KEY || 'dev-key-32-characters-minimum-length'
})
app.get('/orders/:id', async (req, res) => {
// Keep Winston for persistent logging
logger.info('Order request received', { orderId: req.params.id })
// Add RDCP for detailed debug info (can be enabled/disabled at runtime)
rdcp.debug.api('Order request details', {
orderId: req.params.id,
userAgent: req.headers['user-agent'],
ip: req.ip,
timestamp: new Date().toISOString()
})
const order = await db.orders.findById(req.params.id)
// Winston for important events
logger.info('Database query completed', {
orderId: req.params.id,
found: !!order,
duration: '45ms'
})
// RDCP for detailed debug info
rdcp.debug.database('Order query details', {
orderId: req.params.id,
table: 'orders',
query: `SELECT * FROM orders WHERE id = ${req.params.id}`,
duration: '45ms',
indexUsed: 'idx_orders_id',
found: !!order
})
if (!order) {
logger.warn('Order not found', { orderId: req.params.id })
rdcp.debug.api('Order not found details', {
orderId: req.params.id,
searchedTables: ['orders', 'archived_orders'],
suggestedAction: 'check_archive'
})
return res.status(404).json({ error: 'Order not found' })
}
logger.info('Order retrieved successfully', { orderId: order.id })
rdcp.debug.api('Order response prepared', {
orderId: order.id,
responseSize: JSON.stringify(order).length,
includesSensitiveData: false
})
res.json(order)
})
Benefits of Dual Approach:
- β Winston: Persistent logging, alerting, log aggregation
- β RDCP: Runtime-controllable detailed debugging
- β Best of Both: Production logs + on-demand debug detail
const { setupRDCPWithOpenTelemetry } = require('@rdcp.dev/otel-plugin')
setupRDCPWithOpenTelemetry(rdcp)
// Same code as Phase 1, but now RDCP debug logs include trace correlation!
// Winston logs remain unchanged
Goal: Bridge Winston and RDCP with trace correlation
// Custom Winston transport that includes trace context
const { RDCPWinstonTransport } = require('@rdcp/winston-transport') // Hypothetical
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'app.log' }),
new winston.transports.Console(),
new RDCPWinstonTransport({ rdcpClient: rdcp }) // Bridge to RDCP
]
})
// Now Winston logs also get trace correlation when available!
const { NodeSDK } = require('@opentelemetry/sdk-node')
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger')
const { trace } = require('@opentelemetry/api')
// Existing OpenTelemetry setup
const sdk = new NodeSDK({
traceExporter: new JaegerExporter({
endpoint: 'http://localhost:14268/api/traces'
})
})
sdk.start()
app.get('/products/:id', async (req, res) => {
const span = trace.getActiveSpan()
span?.setAttributes({
'product.id': req.params.id,
'user.agent': req.headers['user-agent']
})
const product = await db.products.findById(req.params.id)
span?.addEvent('Database query completed', {
'product.found': !!product,
'query.duration': '32ms'
})
if (!product) {
span?.recordException(new Error('Product not found'))
return res.status(404).json({ error: 'Product not found' })
}
span?.setAttributes({ 'product.name': product.name })
res.json(product)
})
Goal: Add structured debug logs that automatically correlate with existing traces
// Keep existing OpenTelemetry setup exactly as-is
const { RDCPClient } = require('@rdcp/server')
const { setupRDCPWithOpenTelemetry } = require('@rdcp.dev/otel-plugin')
const rdcp = new RDCPClient({
apiKey: process.env.RDCP_API_KEY || 'dev-key-32-characters-minimum-length'
})
// β¨ Magic: This connects to your existing OpenTelemetry traces
setupRDCPWithOpenTelemetry(rdcp)
app.get('/products/:id', async (req, res) => {
const span = trace.getActiveSpan()
// Keep existing span attributes
span?.setAttributes({
'product.id': req.params.id,
'user.agent': req.headers['user-agent']
})
// Add RDCP debug logs - automatically correlated with traces!
rdcp.debug.api('Product request received', {
productId: req.params.id,
userAgent: req.headers['user-agent'],
requestSource: 'web'
})
const product = await db.products.findById(req.params.id)
// Keep existing span events
span?.addEvent('Database query completed', {
'product.found': !!product,
'query.duration': '32ms'
})
// Add detailed RDCP debug info
rdcp.debug.database('Product query executed', {
productId: req.params.id,
found: !!product,
table: 'products',
query: `SELECT * FROM products WHERE id = ${req.params.id}`,
duration: '32ms',
cacheChecked: true,
cacheHit: false
})
if (!product) {
span?.recordException(new Error('Product not found'))
rdcp.debug.api('Product not found', {
productId: req.params.id,
searchAttempts: 1,
suggestedActions: ['check_inventory', 'verify_product_id']
})
return res.status(404).json({ error: 'Product not found' })
}
span?.setAttributes({ 'product.name': product.name })
rdcp.debug.cache('Product cache operations', {
productId: product.id,
cacheKey: `product:${product.id}`,
cacheMiss: true,
willCache: true,
ttl: 3600
})
rdcp.debug.api('Product response prepared', {
productId: product.id,
productName: product.name,
responseSize: JSON.stringify(product).length
})
res.json(product)
})
Immediate Benefits:
- β Perfect Correlation: Debug logs include trace IDs automatically
- β Enhanced Context: More detailed information than span events
- β Runtime Control: Enable/disable debug categories without touching traces
- β Zero Disruption: Existing OpenTelemetry setup unchanged
The Power: Click on any trace in Jaeger β Search logs by trace ID β See exactly what your application was doing!
// Feature flag approach for team adoption
const enableRDCP = process.env.ENABLE_RDCP === 'true' || process.env.NODE_ENV === 'development'
if (enableRDCP) {
const { RDCPClient } = require('@rdcp/server')
const { setupRDCPWithOpenTelemetry } = require('@rdcp.dev/otel-plugin')
const rdcp = new RDCPClient({
apiKey: process.env.RDCP_API_KEY || 'dev-key-32-characters-minimum-length'
})
setupRDCPWithOpenTelemetry(rdcp)
}
// Usage with conditional debugging
app.get('/api/data', async (req, res) => {
// Existing logging always works
logger.info('Data request', { endpoint: '/api/data' })
// RDCP logging only when enabled
if (enableRDCP) {
rdcp.debug.api('Detailed request info', {
endpoint: '/api/data',
headers: req.headers,
query: req.query,
timestamp: Date.now()
})
}
const data = await fetchData()
res.json(data)
})
// Microservice migration strategy
const serviceName = process.env.SERVICE_NAME || 'unknown'
const enableRDCPForService = ['user-service', 'order-service'].includes(serviceName)
if (enableRDCPForService) {
// Enable RDCP + OpenTelemetry for specific services
const rdcp = new RDCPClient({
apiKey: process.env.RDCP_API_KEY,
tags: { service: serviceName }
})
setupRDCPWithOpenTelemetry(rdcp)
}
// Different logging strategies per environment
const config = {
development: {
useRDCP: true,
useWinston: true,
rdcpCategories: ['api', 'database', 'cache', 'validation']
},
staging: {
useRDCP: true,
useWinston: true,
rdcpCategories: ['api', 'database'] // Reduced categories
},
production: {
useRDCP: false, // Disable until confident
useWinston: true,
rdcpCategories: []
}
}
const env = process.env.NODE_ENV || 'development'
const { useRDCP, useWinston, rdcpCategories } = config[env]
- Audit Current Logging - Document existing console.log/Winston/Pino usage
- Identify Key Endpoints - Pick 2-3 critical endpoints for initial migration
- Set Up Development Environment - Install RDCP SDK and test basic functionality
- Team Alignment - Present migration plan to team, get buy-in
- Add RDCP to Development - Implement alongside existing logging
- Configure Categories - Set up debug categories matching your domain
- Test Runtime Control - Verify enable/disable functionality works
- Measure Overhead - Confirm performance impact is acceptable
- Set Up OpenTelemetry - Configure tracing for your stack
- Enable RDCP Correlation - Add
setupRDCPWithOpenTelemetry(rdcp)
- Verify Correlation - Confirm trace IDs appear in debug logs
- Test End-to-End - Verify correlation works across service boundaries
- Deploy to Staging - Test with realistic traffic patterns
- Monitor Performance - Watch for any performance degradation
- Train Team - Show developers how to use RDCP debugging
- Graduate Endpoints - Migrate additional endpoints based on success
- Feature Flag Deployment - Deploy with RDCP disabled by default
- Gradual Enablement - Enable categories one at a time
- Monitor and Tune - Adjust based on production behavior
- Document Best Practices - Create team guidelines for RDCP usage
- Migration Progress: % of endpoints using RDCP
- Performance Impact: Overhead measurement (target: <1% latency increase)
- Trace Correlation: % of debug logs with trace IDs
- Debug Efficiency: Time to diagnose issues (before/after comparison)
- Developer Usage: # of developers actively using RDCP debug controls
- Debug Categories: # of active debug categories per service
- Incident Resolution: Mean time to resolution for production issues
- Production Incidents: Reduction in debugging time
- Development Velocity: Faster development due to better debugging
- Customer Impact: Reduced issue resolution time
Solution: Start with minimal categories, expand gradually
// Start conservative
const rdcp = new RDCPClient({
defaultCategories: ['api'], // Only API calls initially
autoEnable: false // Require explicit enablement
})
// Expand as team gets comfortable
// rdcp.enable('database')
// rdcp.enable('cache')
Solution: Measure and tune, use conditional debugging
// Performance monitoring
const debugOverhead = process.env.MEASURE_DEBUG_OVERHEAD === 'true'
if (debugOverhead) {
const start = process.hrtime.bigint()
rdcp.debug.api('Request processed', data)
const end = process.hrtime.bigint()
console.log('Debug overhead:', Number(end - start) / 1_000_000, 'ms')
}
Solution: Gradual introduction, clear value demonstration
// Optional usage pattern
const debugEnabled = process.env.DEVELOPER_DEBUG === 'true'
// Developers can opt-in individually
if (debugEnabled) {
rdcp.debug.api('Enhanced debugging for developer', {
developerId: process.env.USER,
debugLevel: 'verbose'
})
}
- Choose Your Migration Path - Pick the scenario that matches your current setup
- Start Small - Begin with 1-2 endpoints in development
- Measure Impact - Document performance and developer experience improvements
- Scale Gradually - Expand to more endpoints and services based on success
- Configure Backends - Set up your observability platform with Backend Configurations
π― Result: Enterprise-grade observability that enhances rather than replaces your existing logging infrastructure.
Ready to start? Pick your scenario above and follow the phase-by-phase guide!
Getting Started: Installation β’ Basic Usage β’ Authentication
Migration: From Manual Implementation β’ Framework Examples β’ Publishing Guide
Protocol: RDCP v1.0 Specification β’ Implementation Guide β’ API Reference
π Home | π¦ NPM Package | π GitHub | π Issues
RDCP SDK v1.0.0 - Runtime Debug Control Protocol implementation for JavaScript/Node.js applications
- Implementation-Status
- JavaScript-vs-TypeScript-Boundaries
- Core-Package-Boundaries
- Publishing-Setup
- Contributing
- API-Reference
- Protocol Specification
- Implementation Guide
- RDCP-Primitive-Types
- Protocol-Schemas
- Protocol-Error-Codes
- API-Reference
Version: 1.0.0
Protocol: RDCP v1.0
License: Apache-2.0