Skip to content

Latest commit

 

History

History
449 lines (345 loc) · 11.6 KB

File metadata and controls

449 lines (345 loc) · 11.6 KB

Distributed Tracing and Correlation IDs Guide

This guide explains the distributed tracing and correlation ID implementation in the Amana project, which provides end-to-end request tracing for faster production incident triage.

Overview

The distributed tracing system provides:

  • Correlation IDs: Unique identifiers that span multiple services and requests
  • Request IDs: Unique identifiers for individual HTTP request/response pairs
  • OpenTelemetry Integration: Industry-standard distributed tracing
  • Automatic Propagation: Headers automatically propagated across services
  • Comprehensive Observability: Detailed tracing for frontend-backend interactions

Architecture

Backend Components

  1. Correlation ID Middleware (src/middleware/correlationId.middleware.ts)

    • Generates/validates correlation IDs from HTTP headers
    • Creates unique request IDs for each request
    • Attaches IDs to request objects for downstream use
  2. Tracing Middleware (src/middleware/tracing.middleware.ts)

    • OpenTelemetry span creation for HTTP requests
    • Integration with correlation ID system
    • Request/response attribute tracking
  3. Tracing Configuration (src/config/tracing.ts)

    • OpenTelemetry SDK initialization
    • Exporter configuration (Jaeger, Zipkin, Prometheus)
    • Tracing utilities for custom spans
  4. Traced HTTP Client (src/lib/traced-http-client.ts)

    • Automatic correlation ID propagation for external service calls
    • OpenTelemetry span creation for HTTP clients
    • Error handling and retry logic
  5. Service Integration

    • IPFS service tracing (src/services/ipfs.service.ts)
    • Stellar service tracing (src/services/stellar.service.ts)
    • Extensible to other services

Frontend Components

  1. Traced HTTP Client (src/lib/traced-fetch.ts)

    • Browser-based HTTP client with correlation ID propagation
    • Automatic request/response timing
    • Error handling and retry logic
  2. React Hooks (src/hooks/useTracedFetch.ts)

    • useTracedFetch: Generic traced HTTP requests with state management
    • useTracedGet: Simplified GET requests
    • useTracedMutation: POST/PUT/PATCH/DELETE requests

Headers

The system uses these HTTP headers for tracing:

Header Purpose Source
x-correlation-id Logical trace ID spanning multiple services Client-generated or server-generated
x-request-id Unique ID for specific HTTP request/response Always server-generated

Configuration

Environment Variables

# OpenTelemetry Configuration
JAEGER_ENDPOINT=http://localhost:14268/api/traces
ZIPKIN_ENDPOINT=http://localhost:9411/api/v2/spans
PROMETHEUS_PORT=9464

# Service Configuration
OTEL_SERVICE_NAME=amana-backend
OTEL_EXPORTER_JAEGER_AGENT_HOST=localhost
OTEL_EXPORTER_JAEGER_AGENT_PORT=6831

Backend Setup

  1. Dependencies (already added to package.json):
{
  "@opentelemetry/api": "^1.8.0",
  "@opentelemetry/auto-instrumentations-node": "^0.46.1",
  "@opentelemetry/exporter-jaeger": "^1.22.0",
  "@opentelemetry/exporter-prometheus": "^0.48.0",
  "@opentelemetry/exporter-zipkin": "^1.22.0",
  "@opentelemetry/instrumentation": "^0.48.0",
  "@opentelemetry/instrumentation-express": "^0.40.1",
  "@opentelemetry/instrumentation-http": "^0.48.0",
  "@opentelemetry/resources": "^1.22.0",
  "@opentelemetry/sdk-metrics": "^1.22.0",
  "@opentelemetry/sdk-node": "^0.48.0",
  "@opentelemetry/sdk-trace-base": "^1.22.0",
  "@opentelemetry/semantic-conventions": "^1.22.0"
}
  1. Initialization (already in src/index.ts):
import { initializeTracing } from "./config/tracing";

// Initialize distributed tracing before any other imports
initializeTracing();
  1. Middleware Registration (already in src/app.ts):
app.use(correlationIdMiddleware);
app.use(tracingMiddleware);
app.use(loggerMiddleware);

Frontend Setup

  1. Initialize HTTP Client:
import { initializeHttpClient } from './lib/traced-fetch';

// Initialize with backend URL
initializeHttpClient('http://localhost:4000');
  1. Use in Components:
import { useTracedFetch } from './hooks/useTracedFetch';

function MyComponent() {
  const { data, loading, error, correlationId } = useTracedFetch('/api/trades');
  
  // Component logic...
}

Usage Examples

Backend - Custom Tracing

import { TracingHelper } from '../config/tracing';

// Wrap async operations with tracing
const result = await TracingHelper.withSpan(
  'database.query',
  async (span) => {
    span.setAttributes({
      'db.operation': 'SELECT',
      'db.table': 'trades',
    });
    
    const data = await database.query('SELECT * FROM trades');
    return data;
  }
);

// Add attributes to current span
TracingHelper.setAttributes({
  'user.id': userId,
  'operation.type': 'trade_creation',
});

// Add events to current span
TracingHelper.addEvent('validation_start', { field: 'amount' });

// Record exceptions
try {
  await riskyOperation();
} catch (error) {
  TracingHelper.recordException(error);
}

Backend - External Service Calls

import { tracedHttpClient } from '../lib/traced-http-client';

// Automatic tracing and correlation ID propagation
const response = await tracedHttpClient.get('/external/api/data');

// POST with data
const result = await tracedHttpClient.post('/external/api/create', {
  name: 'test',
  value: 123,
});

// Custom client for specific service
const stellarClient = createTracedClient('https://horizon.stellar.org', 'stellar-service');
const balance = await stellarClient.get(`/accounts/${publicKey}`);

Frontend - HTTP Requests

import { tracedHttpClient } from './lib/traced-fetch';

// Basic GET request
const response = await tracedHttpClient.get('/api/trades');
console.log('Correlation ID:', response.correlationId);

// POST with data
const result = await tracedHttpClient.post('/api/trades', {
  commodity: 'gold',
  quantity: 100,
});

// With custom correlation ID
const response = await tracedHttpClient.get('/api/trades', {
  correlationId: 'user-flow-123',
});

Frontend - React Hooks

import { useTracedFetch, useTracedMutation } from './hooks/useTracedFetch';

// GET request with state management
function TradeList() {
  const { data: trades, loading, error, correlationId } = useTracedFetch('/api/trades');
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <div>
      <div>Correlation ID: {correlationId}</div>
      {trades?.map(trade => <TradeItem key={trade.id} trade={trade} />)}
    </div>
  );
}

// Mutation with loading/error states
function CreateTradeForm() {
  const { mutate: createTrade, loading, error } = useTracedMutation();
  
  const handleSubmit = async (tradeData) => {
    try {
      await createTrade('POST', '/api/trades', tradeData);
      // Success handling
    } catch (err) {
      // Error handling
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      {/* Form fields */}
      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create Trade'}
      </button>
      {error && <div>Error: {error.message}</div>}
    </form>
  );
}

Monitoring and Observability

Jaeger UI

Access Jaeger UI at http://localhost:16686 to:

  • Search traces by correlation ID, service name, or operation
  • View detailed trace timelines
  • Analyze performance bottlenecks
  • Debug distributed request flows

Prometheus Metrics

Access metrics at http://localhost:9464/metrics:

  • HTTP request metrics
  • Custom application metrics
  • OpenTelemetry instrumentation metrics

Log Correlation

All logs include correlation and request IDs:

{
  "level": "info",
  "correlationId": "550e8400-e29b-41d4-a716-446655440000",
  "requestId": "123e4567-e89b-12d3-a456-426614174000",
  "msg": "Trade created successfully",
  "tradeId": "trade_123"
}

Testing

Backend Tests

# Run all tests
npm test

# Run specific tracing tests
npm test -- --testNamePattern="tracing"

# Run with coverage
npm test -- --coverage

Frontend Tests

# Run tests (when implemented)
npm test

Best Practices

1. Correlation ID Generation

  • Use UUIDs for correlation IDs
  • Propagate correlation IDs across all service calls
  • Include correlation IDs in all logs and errors

2. Span Naming

  • Use descriptive, consistent span names
  • Include operation type and target resource
  • Follow naming conventions: service.operation

3. Attributes

  • Add relevant attributes to spans for context
  • Include business context (user IDs, operation types)
  • Avoid sensitive data in attributes

4. Error Handling

  • Always record exceptions in spans
  • Include error context and correlation IDs
  • Use appropriate span status codes

5. Performance

  • Keep spans focused on specific operations
  • Avoid overly long-running spans
  • Use events for significant milestones

Troubleshooting

Common Issues

  1. Missing Correlation IDs

    • Ensure middleware is registered in correct order
    • Check that correlation ID middleware runs before logger
    • Verify headers are not being stripped by proxies
  2. Spans Not Appearing in Jaeger

    • Check Jaeger endpoint configuration
    • Verify network connectivity to Jaeger
    • Ensure service name is correctly configured
  3. High Memory Usage

    • Check for span leaks (unclosed spans)
    • Verify proper error handling in spans
    • Monitor span duration and count

Debug Mode

Enable debug logging:

OTEL_LOG_LEVEL=debug npm run dev

Health Check

Verify tracing is working:

curl -H "x-correlation-id: test-123" http://localhost:4000/health

Should return correlation ID headers:

x-correlation-id: test-123
x-request-id: generated-uuid

Migration Guide

Adding Tracing to Existing Services

  1. Import TracingHelper:
import { TracingHelper } from '../config/tracing';
  1. Wrap Operations:
// Before
async function processTrade(tradeId: string) {
  const trade = await getTrade(tradeId);
  return validateTrade(trade);
}

// After
async function processTrade(tradeId: string) {
  return TracingHelper.withSpan(
    'trade.process',
    async (span) => {
      span.setAttributes({ 'trade.id': tradeId });
      
      const trade = await getTrade(tradeId);
      const result = validateTrade(trade);
      
      return result;
    }
  );
}
  1. Update External Calls:
// Before
const response = await axios.get('/external/api');

// After
import { tracedHttpClient } from '../lib/traced-http-client';
const response = await tracedHttpClient.get('/external/api');

Security Considerations

  1. Header Validation: Correlation IDs are validated to prevent header injection
  2. Data Privacy: Avoid sensitive data in span attributes
  3. Access Control: Ensure tracing endpoints are properly secured
  4. Data Retention: Configure appropriate retention policies for trace data

Performance Impact

The tracing system is designed for minimal performance impact:

  • Overhead: < 5ms per request
  • Memory: ~1MB per 1000 concurrent spans
  • Network: Minimal additional header size (~100 bytes)

Future Enhancements

  1. Sampling: Add configurable sampling strategies
  2. Metrics: Expand custom metrics collection
  3. Alerting: Integration with monitoring systems
  4. Dashboard: Custom tracing dashboards
  5. Service Mesh: Integration with Istio/Linkerd

Support

For questions or issues with the tracing implementation:

  1. Check this documentation
  2. Review test files for examples
  3. Check Jaeger UI for trace visualization
  4. Review logs for correlation ID propagation