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:
- Performance Insights ๐: See exactly where your app is slow
- Proactive Monitoring ๐จ: Catch issues before users complain
- Error Tracking ๐: Get detailed stack traces and context
- 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
- ๐ฏ Monitor What Matters: Focus on business-critical operations, not every function call
- ๐ Use Sampling: Donโt trace 100% of requests in high-traffic applications
- ๐ Async Everything: Never block the main thread for monitoring
- ๐ Implement Circuit Breakers: Disable monitoring if APM service is down
- ๐ Meaningful Names: Use clear, searchable metric and trace names
- โก Batch Operations: Send metrics in batches to reduce overhead
- ๐ก๏ธ Handle Failures: APM failures shouldnโt crash your app
- ๐ 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:
- ๐ป Implement monitoring in your current project
- ๐๏ธ Set up alerting for critical metrics
- ๐ Explore advanced topics like distributed tracing
- ๐ 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! ๐๐โจ