+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 215 of 355

๐Ÿ“Š Performance Monitoring: APM Tools

Master performance monitoring: apm tools in TypeScript with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿš€Intermediate
25 min read

Prerequisites

  • Basic understanding of JavaScript ๐Ÿ“
  • TypeScript installation โšก
  • VS Code or preferred IDE ๐Ÿ’ป
  • Node.js backend development basics ๐Ÿ–ฅ๏ธ

What you'll learn

  • Understand APM fundamentals and monitoring concepts ๐ŸŽฏ
  • Implement performance monitoring in Node.js apps ๐Ÿ—๏ธ
  • Debug performance issues with APM tools ๐Ÿ›
  • Build type-safe monitoring solutions โœจ

๐ŸŽฏ Introduction

Welcome to the world of Application Performance Monitoring (APM)! ๐ŸŽ‰ In this guide, weโ€™ll explore how to make your TypeScript applications lightning-fast and rock-solid reliable.

Think of APM as your appโ€™s personal fitness tracker ๐Ÿƒโ€โ™‚๏ธ. Just like a fitness tracker monitors your heart rate, steps, and sleep patterns, APM tools monitor your applicationโ€™s response times, database queries, and error rates. Pretty cool, right?

By the end of this tutorial, youโ€™ll be able to spot performance bottlenecks faster than you can say โ€œoptimizationโ€! Letโ€™s turn your apps into performance powerhouses! ๐Ÿš€

๐Ÿ“š Understanding APM Tools

๐Ÿค” What is Application Performance Monitoring?

APM is like having a 24/7 health check-up for your application ๐Ÿฅ. Imagine if your app could tell you: โ€œHey, Iโ€™m feeling slow today because that database query is taking too long!โ€ Thatโ€™s exactly what APM does.

In TypeScript terms, APM tools collect metrics, traces, and logs to help you:

  • โœจ Identify slow endpoints and functions
  • ๐Ÿš€ Track database query performance
  • ๐Ÿ›ก๏ธ Monitor error rates and exceptions
  • ๐Ÿ“Š Visualize user experience metrics

๐Ÿ’ก Why Use APM Tools?

Hereโ€™s why developers love APM tools:

  1. Performance Insights ๐Ÿ“ˆ: See exactly where your app is slow
  2. Proactive Monitoring ๐Ÿšจ: Catch issues before users complain
  3. Error Tracking ๐Ÿ›: Get detailed stack traces and context
  4. Capacity Planning ๐Ÿ“Š: Know when to scale your infrastructure

Real-world example: Imagine running an e-commerce site ๐Ÿ›’. With APM, you can monitor checkout performance, track payment gateway response times, and get alerts when your database is overloaded during Black Friday sales!

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Setting Up APM with TypeScript

Letโ€™s start with a popular APM solution - New Relic:

// ๐Ÿ‘‹ Hello, APM monitoring!
import newrelic from 'newrelic';

// ๐ŸŽจ Custom transaction tracking
const trackCustomTransaction = (name: string, callback: () => Promise<void>) => {
  return newrelic.startBackgroundTransaction(name, async () => {
    try {
      await callback();
    } catch (error) {
      // ๐Ÿšจ Log the error with context
      newrelic.recordError(error as Error);
      throw error;
    }
  });
};

// ๐Ÿ“Š Custom metrics
const recordCustomMetric = (name: string, value: number) => {
  newrelic.recordMetric(`Custom/${name}`, value);
  console.log(`๐Ÿ“ˆ Recorded metric: ${name} = ${value}`);
};

๐Ÿ’ก Explanation: APM tools typically work by instrumenting your code automatically, but you can add custom tracking for business-specific metrics!

๐ŸŽฏ Common APM Patterns

Here are patterns youโ€™ll use daily:

// ๐Ÿ—๏ธ Pattern 1: Database query monitoring
interface DatabaseMetrics {
  queryTime: number;
  rowsAffected: number;
  queryType: 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE';
}

class DatabaseMonitor {
  static async trackQuery<T>(
    queryName: string,
    queryFn: () => Promise<T>
  ): Promise<T> {
    const startTime = Date.now();
    
    try {
      const result = await queryFn();
      const duration = Date.now() - startTime;
      
      // ๐Ÿ“Š Record successful query metrics
      recordCustomMetric(`Database/${queryName}/Duration`, duration);
      console.log(`โœ… Query ${queryName} completed in ${duration}ms`);
      
      return result;
    } catch (error) {
      // ๐Ÿšจ Track database errors
      newrelic.recordError(error as Error, {
        customAttributes: {
          queryName,
          duration: Date.now() - startTime
        }
      });
      
      throw error;
    }
  }
}

// ๐ŸŽจ Pattern 2: HTTP endpoint monitoring
interface EndpointMetrics {
  path: string;
  method: string;
  statusCode: number;
  responseTime: number;
  userAgent?: string;
}

const monitorEndpoint = (req: any, res: any, next: any) => {
  const startTime = Date.now();
  
  res.on('finish', () => {
    const metrics: EndpointMetrics = {
      path: req.path,
      method: req.method,
      statusCode: res.statusCode,
      responseTime: Date.now() - startTime,
      userAgent: req.get('User-Agent')
    };
    
    // ๐Ÿ“ˆ Send metrics to APM
    recordCustomMetric(`HTTP/${req.method}${req.path}`, metrics.responseTime);
    console.log(`๐ŸŒ ${req.method} ${req.path} - ${metrics.statusCode} (${metrics.responseTime}ms)`);
  });
  
  next();
};

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: E-commerce Performance Monitoring

Letโ€™s build a comprehensive monitoring solution for an online store:

// ๐Ÿช E-commerce performance monitoring
interface ProductMetrics {
  productId: string;
  searchTime: number;
  inventoryCheckTime: number;
  priceCalculationTime: number;
}

interface OrderMetrics {
  orderId: string;
  processingTime: number;
  paymentGatewayTime: number;
  inventoryUpdateTime: number;
  emailNotificationTime: number;
}

class EcommerceAPM {
  private metricsBuffer: Map<string, any> = new Map();
  
  // ๐Ÿ” Monitor product search performance
  async monitorProductSearch(searchQuery: string): Promise<Product[]> {
    const transactionName = 'Product/Search';
    
    return await newrelic.startBackgroundTransaction(transactionName, async () => {
      const startTime = Date.now();
      
      try {
        // ๐ŸŽฏ Simulate product search
        const products = await this.searchProducts(searchQuery);
        const searchTime = Date.now() - startTime;
        
        // ๐Ÿ“Š Record search metrics
        const metrics: ProductMetrics = {
          productId: 'search_results',
          searchTime,
          inventoryCheckTime: 0,
          priceCalculationTime: 0
        };
        
        // ๐Ÿš€ Custom attributes for deeper analysis
        newrelic.addCustomAttributes({
          searchQuery,
          resultCount: products.length,
          searchTime
        });
        
        console.log(`๐Ÿ” Search for "${searchQuery}" took ${searchTime}ms, found ${products.length} products`);
        
        return products;
      } catch (error) {
        // ๐Ÿšจ Track search errors with context
        newrelic.recordError(error as Error, {
          customAttributes: {
            searchQuery,
            searchTime: Date.now() - startTime
          }
        });
        
        throw error;
      }
    });
  }
  
  // ๐Ÿ›’ Monitor checkout process
  async monitorCheckout(cartItems: CartItem[], userId: string): Promise<Order> {
    const transactionName = 'Order/Checkout';
    
    return await newrelic.startBackgroundTransaction(transactionName, async () => {
      const checkoutStart = Date.now();
      const metrics: OrderMetrics = {
        orderId: '',
        processingTime: 0,
        paymentGatewayTime: 0,
        inventoryUpdateTime: 0,
        emailNotificationTime: 0
      };
      
      try {
        // ๐Ÿ’ณ Process payment
        const paymentStart = Date.now();
        const paymentResult = await this.processPayment(cartItems, userId);
        metrics.paymentGatewayTime = Date.now() - paymentStart;
        
        // ๐Ÿ“ฆ Update inventory
        const inventoryStart = Date.now();
        await this.updateInventory(cartItems);
        metrics.inventoryUpdateTime = Date.now() - inventoryStart;
        
        // ๐Ÿ“ง Send confirmation email
        const emailStart = Date.now();
        await this.sendConfirmationEmail(userId, paymentResult.orderId);
        metrics.emailNotificationTime = Date.now() - emailStart;
        
        metrics.orderId = paymentResult.orderId;
        metrics.processingTime = Date.now() - checkoutStart;
        
        // ๐ŸŽ‰ Record successful checkout metrics
        this.recordCheckoutMetrics(metrics);
        
        console.log(`โœ… Checkout completed in ${metrics.processingTime}ms for order ${metrics.orderId}`);
        
        return paymentResult;
      } catch (error) {
        // ๐Ÿ’ฅ Track checkout failures
        newrelic.recordError(error as Error, {
          customAttributes: {
            userId,
            cartValue: cartItems.reduce((sum, item) => sum + item.price, 0),
            processingTime: Date.now() - checkoutStart
          }
        });
        
        throw error;
      }
    });
  }
  
  // ๐Ÿ“Š Record detailed checkout metrics
  private recordCheckoutMetrics(metrics: OrderMetrics): void {
    recordCustomMetric('Checkout/TotalTime', metrics.processingTime);
    recordCustomMetric('Checkout/PaymentGatewayTime', metrics.paymentGatewayTime);
    recordCustomMetric('Checkout/InventoryUpdateTime', metrics.inventoryUpdateTime);
    recordCustomMetric('Checkout/EmailTime', metrics.emailNotificationTime);
    
    // ๐ŸŽฏ Business metrics
    newrelic.incrementMetric('Business/Orders/Completed');
    console.log(`๐Ÿ“ˆ Recorded comprehensive metrics for order ${metrics.orderId}`);
  }
  
  // ๐Ÿ”„ Helper methods (simplified for demo)
  private async searchProducts(query: string): Promise<Product[]> {
    // Simulate database search
    await new Promise(resolve => setTimeout(resolve, Math.random() * 100));
    return [{ id: '1', name: 'TypeScript Book', price: 29.99, emoji: '๐Ÿ“˜' }];
  }
  
  private async processPayment(items: CartItem[], userId: string): Promise<Order> {
    // Simulate payment processing
    await new Promise(resolve => setTimeout(resolve, Math.random() * 500));
    return { orderId: `order_${Date.now()}`, userId, total: 29.99 };
  }
  
  private async updateInventory(items: CartItem[]): Promise<void> {
    // Simulate inventory update
    await new Promise(resolve => setTimeout(resolve, Math.random() * 200));
  }
  
  private async sendConfirmationEmail(userId: string, orderId: string): Promise<void> {
    // Simulate email sending
    await new Promise(resolve => setTimeout(resolve, Math.random() * 300));
  }
}

// ๐ŸŽฎ Usage example
const apm = new EcommerceAPM();

๐ŸŽฎ Example 2: Gaming API Performance Tracking

Letโ€™s monitor a gaming API with real-time features:

// ๐ŸŽฎ Gaming API performance monitoring
interface GameMetrics {
  gameId: string;
  playerId: string;
  actionType: 'move' | 'attack' | 'heal' | 'levelup';
  processingTime: number;
  serverLoad: number;
}

class GameAPM {
  private activeGames = new Map<string, number>();
  
  // โšก Monitor real-time game actions
  async monitorGameAction(
    gameId: string,
    playerId: string,
    actionType: GameMetrics['actionType'],
    actionFn: () => Promise<any>
  ): Promise<any> {
    const transactionName = `Game/${actionType}`;
    
    return await newrelic.startBackgroundTransaction(transactionName, async () => {
      const startTime = Date.now();
      const serverLoad = this.getServerLoad();
      
      try {
        const result = await actionFn();
        const processingTime = Date.now() - startTime;
        
        const metrics: GameMetrics = {
          gameId,
          playerId,
          actionType,
          processingTime,
          serverLoad
        };
        
        // ๐ŸŽฏ Record game-specific metrics
        this.recordGameMetrics(metrics);
        
        // ๐Ÿšจ Alert on slow actions
        if (processingTime > 100) {
          console.log(`โš ๏ธ Slow ${actionType} action: ${processingTime}ms`);
          newrelic.recordError(new Error('Slow game action'), {
            customAttributes: metrics
          });
        }
        
        console.log(`๐ŸŽฎ ${actionType} action completed in ${processingTime}ms`);
        return result;
      } catch (error) {
        // ๐Ÿ’ฅ Track game action failures
        newrelic.recordError(error as Error, {
          customAttributes: {
            gameId,
            playerId,
            actionType,
            serverLoad,
            processingTime: Date.now() - startTime
          }
        });
        
        throw error;
      }
    });
  }
  
  // ๐Ÿ“Š Record comprehensive game metrics
  private recordGameMetrics(metrics: GameMetrics): void {
    // ๐ŸŽฏ Action-specific metrics
    recordCustomMetric(`Game/Actions/${metrics.actionType}`, metrics.processingTime);
    recordCustomMetric('Game/ServerLoad', metrics.serverLoad);
    
    // ๐Ÿ“ˆ Player engagement metrics
    newrelic.incrementMetric(`Game/Players/${metrics.playerId}/Actions`);
    newrelic.incrementMetric(`Game/Games/${metrics.gameId}/Actions`);
    
    // ๐Ÿš€ Performance thresholds
    if (metrics.processingTime < 50) {
      newrelic.incrementMetric('Game/Performance/Fast');
    } else if (metrics.processingTime < 100) {
      newrelic.incrementMetric('Game/Performance/Normal');
    } else {
      newrelic.incrementMetric('Game/Performance/Slow');
    }
  }
  
  // ๐Ÿ“Š Get current server load
  private getServerLoad(): number {
    return Math.random() * 100; // Simplified server load calculation
  }
  
  // ๐ŸŽฏ Monitor game session duration
  startGameSession(gameId: string, playerId: string): void {
    const sessionKey = `${gameId}:${playerId}`;
    this.activeGames.set(sessionKey, Date.now());
    
    newrelic.incrementMetric('Game/Sessions/Started');
    console.log(`๐ŸŽฎ Started monitoring session for player ${playerId} in game ${gameId}`);
  }
  
  endGameSession(gameId: string, playerId: string): void {
    const sessionKey = `${gameId}:${playerId}`;
    const startTime = this.activeGames.get(sessionKey);
    
    if (startTime) {
      const sessionDuration = Date.now() - startTime;
      recordCustomMetric('Game/SessionDuration', sessionDuration);
      
      newrelic.incrementMetric('Game/Sessions/Completed');
      console.log(`๐Ÿ Game session ended after ${sessionDuration}ms`);
      
      this.activeGames.delete(sessionKey);
    }
  }
}

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Topic 1: Custom Instrumentation

When youโ€™re ready to level up, try building your own instrumentation:

// ๐ŸŽฏ Advanced custom instrumentation
class CustomInstrumentor {
  private timers = new Map<string, number>();
  private counters = new Map<string, number>();
  
  // โฑ๏ธ High-precision timing
  startTimer(name: string): void {
    this.timers.set(name, performance.now());
  }
  
  endTimer(name: string): number {
    const startTime = this.timers.get(name);
    if (!startTime) {
      throw new Error(`Timer ${name} not found! ๐Ÿ˜ฑ`);
    }
    
    const duration = performance.now() - startTime;
    this.timers.delete(name);
    
    // ๐Ÿ“Š Send to APM
    recordCustomMetric(`Custom/Timer/${name}`, duration);
    return duration;
  }
  
  // ๐Ÿ“ˆ Thread-safe counters
  increment(name: string, value: number = 1): void {
    const current = this.counters.get(name) || 0;
    this.counters.set(name, current + value);
    
    newrelic.incrementMetric(`Custom/Counter/${name}`, value);
  }
  
  // ๐ŸŽจ Decorator for automatic method monitoring
  static monitor(target: any, propertyName: string, descriptor: PropertyDescriptor) {
    const method = descriptor.value;
    
    descriptor.value = async function (...args: any[]) {
      const className = target.constructor.name;
      const methodName = `${className}.${propertyName}`;
      const startTime = performance.now();
      
      try {
        const result = await method.apply(this, args);
        const duration = performance.now() - startTime;
        
        recordCustomMetric(`Methods/${methodName}`, duration);
        console.log(`โœจ ${methodName} executed in ${duration.toFixed(2)}ms`);
        
        return result;
      } catch (error) {
        newrelic.recordError(error as Error, {
          customAttributes: {
            className,
            methodName,
            duration: performance.now() - startTime
          }
        });
        throw error;
      }
    };
    
    return descriptor;
  }
}

// ๐Ÿช„ Using the custom decorator
class PaymentService {
  @CustomInstrumentor.monitor
  async processPayment(amount: number): Promise<string> {
    // ๐Ÿ’ณ Simulate payment processing
    await new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
    return `payment_${Date.now()}`;
  }
  
  @CustomInstrumentor.monitor
  async validateCard(cardNumber: string): Promise<boolean> {
    // ๐Ÿ” Simulate card validation
    await new Promise(resolve => setTimeout(resolve, Math.random() * 500));
    return cardNumber.length === 16;
  }
}

๐Ÿ—๏ธ Advanced Topic 2: Distributed Tracing with OpenTelemetry

For the brave developers building microservices:

// ๐Ÿš€ OpenTelemetry distributed tracing
import { trace, context, SpanStatusCode } from '@opentelemetry/api';

interface TraceableOperation<T = any> {
  name: string;
  attributes?: Record<string, string | number | boolean>;
  operation: () => Promise<T>;
}

class DistributedTracer {
  private tracer = trace.getTracer('my-awesome-service', '1.0.0');
  
  // ๐ŸŽฏ Trace operations across services
  async traceOperation<T>({ name, attributes, operation }: TraceableOperation<T>): Promise<T> {
    const span = this.tracer.startSpan(name, {
      attributes: {
        'service.name': 'typescript-backend',
        'service.version': '1.0.0',
        ...attributes
      }
    });
    
    return context.with(trace.setSpan(context.active(), span), async () => {
      try {
        const result = await operation();
        
        // โœ… Mark span as successful
        span.setStatus({ code: SpanStatusCode.OK });
        span.setAttribute('operation.result', 'success');
        
        console.log(`โœจ Traced operation "${name}" successfully`);
        return result;
      } catch (error) {
        // โŒ Mark span as failed
        span.setStatus({
          code: SpanStatusCode.ERROR,
          message: (error as Error).message
        });
        
        span.recordException(error as Error);
        console.log(`๐Ÿ’ฅ Traced operation "${name}" failed:`, error);
        
        throw error;
      } finally {
        span.end();
      }
    });
  }
  
  // ๐ŸŒ Trace HTTP requests with correlation
  async traceHttpRequest(method: string, url: string, headers: Record<string, string>): Promise<any> {
    return this.traceOperation({
      name: `HTTP ${method}`,
      attributes: {
        'http.method': method,
        'http.url': url,
        'http.user_agent': headers['user-agent'] || 'unknown'
      },
      operation: async () => {
        // ๐Ÿš€ Your HTTP request logic here
        const response = await fetch(url, { method, headers });
        
        // ๐Ÿ“Š Add response attributes to span
        const activeSpan = trace.getActiveSpan();
        activeSpan?.setAttributes({
          'http.status_code': response.status,
          'http.response_size': response.headers.get('content-length') || '0'
        });
        
        return response.json();
      }
    });
  }
}

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Over-Monitoring Everything

// โŒ Wrong way - monitoring every tiny detail!
function addNumbers(a: number, b: number): number {
  const span = tracer.startSpan('add_numbers'); // ๐Ÿ˜ฐ Overkill!
  const result = a + b;
  span.end();
  return result;
}

// โœ… Correct way - monitor meaningful operations!
class MathService {
  @CustomInstrumentor.monitor
  async complexCalculation(data: number[]): Promise<number> {
    // ๐ŸŽฏ This actually takes time and resources
    return data.reduce((sum, num) => sum + Math.sqrt(num), 0);
  }
  
  // Simple operations don't need monitoring
  add(a: number, b: number): number {
    return a + b; // โœจ Keep it simple!
  }
}

๐Ÿคฏ Pitfall 2: Ignoring Performance Impact

// โŒ Dangerous - synchronous operations in monitoring!
function badMonitoring(operation: string): void {
  // ๐Ÿ’ฅ This blocks the event loop!
  const data = fs.readFileSync('/path/to/log');
  sendToAPM(data);
}

// โœ… Safe - asynchronous monitoring!
class PerformantAPM {
  private metricsQueue: any[] = [];
  
  // ๐Ÿš€ Non-blocking metrics collection
  recordMetric(name: string, value: number): void {
    this.metricsQueue.push({ name, value, timestamp: Date.now() });
    
    // ๐Ÿ“ฆ Batch send metrics
    if (this.metricsQueue.length >= 100) {
      this.flushMetrics();
    }
  }
  
  private async flushMetrics(): Promise<void> {
    const batch = this.metricsQueue.splice(0);
    
    // ๐ŸŽฏ Send asynchronously
    setImmediate(async () => {
      try {
        await this.sendBatchToAPM(batch);
        console.log(`๐Ÿ“ค Sent ${batch.length} metrics to APM`);
      } catch (error) {
        console.error('Failed to send metrics:', error);
        // ๐Ÿ”„ Could implement retry logic here
      }
    });
  }
  
  private async sendBatchToAPM(metrics: any[]): Promise<void> {
    // Your APM sending logic here
  }
}

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Monitor What Matters: Focus on business-critical operations, not every function call
  2. ๐Ÿ“Š Use Sampling: Donโ€™t trace 100% of requests in high-traffic applications
  3. ๐Ÿš€ Async Everything: Never block the main thread for monitoring
  4. ๐Ÿ”„ Implement Circuit Breakers: Disable monitoring if APM service is down
  5. ๐Ÿ“ Meaningful Names: Use clear, searchable metric and trace names
  6. โšก Batch Operations: Send metrics in batches to reduce overhead
  7. ๐Ÿ›ก๏ธ Handle Failures: APM failures shouldnโ€™t crash your app
  8. ๐Ÿ“ˆ Set Alerts: Configure meaningful thresholds for your metrics

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Complete API Monitoring System

Create a comprehensive monitoring solution for a REST API:

๐Ÿ“‹ Requirements:

  • โœ… Monitor response times for all endpoints
  • ๐Ÿท๏ธ Track database query performance
  • ๐Ÿ‘ค Monitor user authentication flows
  • ๐Ÿ“… Set up error tracking with context
  • ๐ŸŽจ Create custom business metrics
  • ๐Ÿ“Š Implement health check monitoring

๐Ÿš€ Bonus Points:

  • Add distributed tracing for microservices
  • Implement real-time alerting
  • Create performance budgets
  • Build a custom dashboard

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Complete API monitoring system!
import express from 'express';
import { trace, context } from '@opentelemetry/api';

interface APIMetrics {
  endpoint: string;
  method: string;
  responseTime: number;
  statusCode: number;
  userId?: string;
  errorMessage?: string;
}

class ComprehensiveAPMSystem {
  private app: express.Application;
  private tracer = trace.getTracer('api-service');
  private healthMetrics = new Map<string, number>();
  
  constructor() {
    this.app = express();
    this.setupMiddleware();
    this.setupRoutes();
    this.startHealthMonitoring();
  }
  
  // ๐Ÿ”ง Setup monitoring middleware
  private setupMiddleware(): void {
    // ๐Ÿ“Š Request monitoring middleware
    this.app.use((req, res, next) => {
      const startTime = Date.now();
      const span = this.tracer.startSpan(`${req.method} ${req.path}`);
      
      res.on('finish', () => {
        const metrics: APIMetrics = {
          endpoint: req.path,
          method: req.method,
          responseTime: Date.now() - startTime,
          statusCode: res.statusCode,
          userId: req.headers['user-id'] as string
        };
        
        this.recordAPIMetrics(metrics);
        span.end();
      });
      
      // ๐ŸŽฏ Add span to request context
      context.with(trace.setSpan(context.active(), span), () => {
        next();
      });
    });
    
    // ๐Ÿšจ Error monitoring middleware
    this.app.use((error: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
      const span = trace.getActiveSpan();
      
      if (span) {
        span.recordException(error);
        span.setStatus({ code: 2, message: error.message });
      }
      
      // Record error metrics
      newrelic.recordError(error, {
        customAttributes: {
          endpoint: req.path,
          method: req.method,
          userId: req.headers['user-id']
        }
      });
      
      console.log(`๐Ÿ’ฅ API Error: ${error.message}`);
      res.status(500).json({ error: 'Internal server error' });
    });
  }
  
  // ๐ŸŒ Setup monitored routes
  private setupRoutes(): void {
    // ๐Ÿ” Authentication endpoint
    this.app.post('/auth/login', async (req, res) => {
      const authSpan = this.tracer.startSpan('authentication');
      
      try {
        const { username, password } = req.body;
        
        // ๐Ÿ” Monitor database query
        const user = await this.monitorDatabaseQuery(
          'user_lookup',
          () => this.authenticateUser(username, password)
        );
        
        if (user) {
          authSpan.setAttributes({
            'auth.success': true,
            'user.id': user.id
          });
          
          // ๐Ÿ“ˆ Business metric
          newrelic.incrementMetric('Business/Logins/Successful');
          
          res.json({ token: user.token, userId: user.id });
        } else {
          authSpan.setAttributes({
            'auth.success': false,
            'auth.failure_reason': 'invalid_credentials'
          });
          
          newrelic.incrementMetric('Business/Logins/Failed');
          res.status(401).json({ error: 'Invalid credentials' });
        }
      } catch (error) {
        authSpan.recordException(error as Error);
        throw error;
      } finally {
        authSpan.end();
      }
    });
    
    // ๐Ÿ›’ Product search endpoint
    this.app.get('/products/search', async (req, res) => {
      const { query, limit = 10 } = req.query;
      
      const products = await this.monitorDatabaseQuery(
        'product_search',
        () => this.searchProducts(query as string, Number(limit))
      );
      
      // ๐Ÿ“Š Search analytics
      newrelic.addCustomAttributes({
        searchQuery: query,
        resultCount: products.length,
        searchLimit: limit
      });
      
      res.json(products);
    });
    
    // ๐Ÿ“Š Health check endpoint
    this.app.get('/health', (req, res) => {
      const healthStatus = {
        status: 'healthy',
        timestamp: new Date().toISOString(),
        metrics: Object.fromEntries(this.healthMetrics),
        version: '1.0.0'
      };
      
      res.json(healthStatus);
    });
  }
  
  // ๐ŸŽฏ Monitor database queries
  private async monitorDatabaseQuery<T>(
    queryName: string,
    queryFn: () => Promise<T>
  ): Promise<T> {
    const span = this.tracer.startSpan(`db.${queryName}`);
    const startTime = Date.now();
    
    try {
      const result = await queryFn();
      const duration = Date.now() - startTime;
      
      span.setAttributes({
        'db.operation': queryName,
        'db.duration': duration
      });
      
      recordCustomMetric(`Database/${queryName}`, duration);
      
      // ๐Ÿšจ Alert on slow queries
      if (duration > 1000) {
        console.log(`๐ŸŒ Slow query detected: ${queryName} took ${duration}ms`);
        newrelic.recordError(new Error('Slow database query'), {
          customAttributes: { queryName, duration }
        });
      }
      
      return result;
    } catch (error) {
      span.recordException(error as Error);
      throw error;
    } finally {
      span.end();
    }
  }
  
  // ๐Ÿ“Š Record comprehensive API metrics
  private recordAPIMetrics(metrics: APIMetrics): void {
    // ๐ŸŽฏ Response time metrics
    recordCustomMetric(`API/${metrics.method}${metrics.endpoint}`, metrics.responseTime);
    recordCustomMetric('API/ResponseTime/Overall', metrics.responseTime);
    
    // ๐Ÿ“ˆ Status code tracking
    newrelic.incrementMetric(`API/StatusCodes/${metrics.statusCode}`);
    
    // ๐Ÿš€ Performance categories
    if (metrics.responseTime < 100) {
      newrelic.incrementMetric('API/Performance/Fast');
    } else if (metrics.responseTime < 500) {
      newrelic.incrementMetric('API/Performance/Normal');
    } else {
      newrelic.incrementMetric('API/Performance/Slow');
    }
    
    // ๐Ÿ“Š User activity tracking
    if (metrics.userId) {
      newrelic.incrementMetric(`API/Users/${metrics.userId}/Requests`);
    }
    
    console.log(`๐Ÿ“ˆ ${metrics.method} ${metrics.endpoint} - ${metrics.statusCode} (${metrics.responseTime}ms)`);
  }
  
  // ๐Ÿ’“ Health monitoring
  private startHealthMonitoring(): void {
    setInterval(() => {
      // ๐Ÿ” Check various health metrics
      this.healthMetrics.set('memory', process.memoryUsage().heapUsed);
      this.healthMetrics.set('uptime', process.uptime());
      this.healthMetrics.set('cpu', process.cpuUsage().user);
      
      // ๐Ÿ“Š Send health metrics to APM
      for (const [metric, value] of this.healthMetrics) {
        recordCustomMetric(`Health/${metric}`, value);
      }
    }, 30000); // Every 30 seconds
  }
  
  // ๐Ÿ”„ Helper methods (simplified for demo)
  private async authenticateUser(username: string, password: string): Promise<any> {
    await new Promise(resolve => setTimeout(resolve, Math.random() * 200));
    return username === 'admin' ? { id: '1', token: 'abc123' } : null;
  }
  
  private async searchProducts(query: string, limit: number): Promise<any[]> {
    await new Promise(resolve => setTimeout(resolve, Math.random() * 300));
    return [
      { id: '1', name: 'TypeScript Guide', price: 29.99, emoji: '๐Ÿ“˜' },
      { id: '2', name: 'Node.js Book', price: 34.99, emoji: '๐Ÿ“—' }
    ].slice(0, limit);
  }
  
  // ๐Ÿš€ Start the monitored server
  start(port: number = 3000): void {
    this.app.listen(port, () => {
      console.log(`๐Ÿš€ Monitored API server running on port ${port}`);
      console.log(`๐Ÿ“Š Health check available at http://localhost:${port}/health`);
    });
  }
}

// ๐ŸŽฎ Start the comprehensive monitoring system!
const monitoredAPI = new ComprehensiveAPMSystem();
monitoredAPI.start(3000);

This solution includes:

  • โœ… Complete request/response monitoring
  • ๐Ÿ“Š Database query performance tracking
  • ๐Ÿ” Authentication flow monitoring
  • ๐Ÿšจ Error tracking with full context
  • ๐Ÿ’“ Health check monitoring
  • ๐Ÿ“ˆ Custom business metrics
  • ๐ŸŽฏ Performance categorization
  • ๐Ÿ” Distributed tracing support

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much about APM! Hereโ€™s what you can now do:

  • โœ… Implement comprehensive monitoring in your TypeScript applications ๐Ÿ’ช
  • โœ… Track performance bottlenecks before they impact users ๐Ÿ›ก๏ธ
  • โœ… Set up custom metrics for business-specific monitoring ๐ŸŽฏ
  • โœ… Handle errors gracefully with proper context ๐Ÿ›
  • โœ… Build production-ready monitoring that scales! ๐Ÿš€

Remember: Good monitoring is like having superpowers - you can see whatโ€™s happening in your application at all times! ๐Ÿฆธโ€โ™‚๏ธ

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered APM tools with TypeScript!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Implement monitoring in your current project
  2. ๐Ÿ—๏ธ Set up alerting for critical metrics
  3. ๐Ÿ“š Explore advanced topics like distributed tracing
  4. ๐ŸŒŸ Check out our next tutorial on โ€๐Ÿ” Logging: Winston and Morganโ€

Remember: Great applications arenโ€™t just fast - theyโ€™re also observable! Keep monitoring, keep optimizing, and most importantly, keep building awesome things! ๐Ÿš€


Happy monitoring! ๐ŸŽ‰๐Ÿ“Šโœจ