Recipes
Common tracing patterns and real-world examples.
1. Order Processing
Trace a multi-step order workflow with nested spans for validation, persistence, and payment. Each step records relevant attributes and events, and payment failures are captured via recordException.
typescript
import { Injectable, Inject } from '@nestjs/common';
import { TRACING_SERVICE, ITracingService } from '@nestjs-redisx/tracing';
import { CreateOrderDto, Order, OrderRepository, PaymentService } from '../types';
@Injectable()
export class OrderService {
constructor(
@Inject(TRACING_SERVICE) private readonly tracing: ITracingService,
private readonly orderRepo: OrderRepository,
private readonly paymentService: PaymentService,
) {}
async createOrder(dto: CreateOrderDto): Promise<Order> {
return this.tracing.withSpan('order.create', async () => {
this.tracing.setAttribute('order.total', dto.total);
this.tracing.setAttribute('order.items_count', dto.items.length);
this.tracing.setAttribute('customer.id', dto.customerId);
// Validate
await this.tracing.withSpan('order.validate', async () => {
this.tracing.addEvent('validation.started');
await this.validateOrder(dto);
this.tracing.addEvent('validation.completed');
});
// Create order
const order = await this.tracing.withSpan('order.save', async () => {
this.tracing.setAttribute('db.operation', 'INSERT');
return this.orderRepo.create(dto);
});
// Process payment
await this.tracing.withSpan('payment.process', async () => {
this.tracing.setAttribute('payment.method', dto.paymentMethod);
this.tracing.setAttribute('payment.amount', dto.total);
try {
await this.paymentService.charge(order.id, dto.total);
this.tracing.addEvent('payment.succeeded');
} catch (error) {
this.tracing.addEvent('payment.failed', {
'error.type': (error as Error).name,
'error.message': (error as Error).message,
});
this.tracing.recordException(error as Error);
throw error;
}
});
return order;
});
}
private async validateOrder(_dto: CreateOrderDto): Promise<void> {
// validation logic
}
}2. User Registration
Trace a user registration flow spanning cache lookups, database queries, password hashing, and email sending. Demonstrates cache hit/miss tracking via span attributes and cross-service span nesting.
typescript
import { Injectable, Inject, ConflictException } from '@nestjs/common';
import { TRACING_SERVICE, ITracingService } from '@nestjs-redisx/tracing';
import { RegisterDto, User, UserRepository, CacheStore, EmailService } from '../types';
@Injectable()
export class AuthService {
constructor(
@Inject(TRACING_SERVICE) private readonly tracing: ITracingService,
private readonly userRepo: UserRepository,
private readonly cache: CacheStore,
private readonly emailService: EmailService,
) {}
async register(dto: RegisterDto): Promise<User> {
return this.tracing.withSpan('user.register', async () => {
this.tracing.setAttribute('user.email', dto.email);
// Check if user exists
const existingUser = await this.tracing.withSpan(
'user.check_exists',
async () => {
this.tracing.addEvent('cache.lookup');
const cached = await this.cache.get(`user:${dto.email}`);
if (cached) {
this.tracing.setAttribute('cache.hit', true);
return cached;
}
this.tracing.setAttribute('cache.hit', false);
this.tracing.addEvent('db.query');
return this.userRepo.findByEmail(dto.email);
},
);
if (existingUser) {
this.tracing.addEvent('user.already_exists');
throw new ConflictException('User already exists');
}
// Hash password
const hashedPassword = await this.tracing.withSpan(
'password.hash',
async () => {
this.tracing.setAttribute('hash.algorithm', 'bcrypt');
this.tracing.setAttribute('hash.rounds', 10);
return `hashed_${dto.password}`;
},
);
// Create user
const user = await this.tracing.withSpan('user.create', async () => {
this.tracing.addEvent('db.insert');
return this.userRepo.create({
email: dto.email,
password: hashedPassword,
});
});
// Send welcome email
await this.tracing.withSpan('email.send_welcome', async () => {
this.tracing.setAttribute('email.to', user.email);
this.tracing.setAttribute('email.template', 'welcome');
await this.emailService.sendWelcome(user.email);
this.tracing.addEvent('email.sent');
});
return user;
});
}
}3. Batch Processing
Wrap a batch operation in a parent span with per-item child spans. Tracks success and error counts as attributes, and records exceptions on individual failures without stopping the entire batch.
typescript
import { Injectable, Inject } from '@nestjs/common';
import { TRACING_SERVICE, ITracingService } from '@nestjs-redisx/tracing';
import { User, UserRepository } from '../types';
@Injectable()
export class ReportService {
constructor(
@Inject(TRACING_SERVICE) private readonly tracing: ITracingService,
private readonly userRepo: UserRepository,
) {}
async generateMonthlyReports(): Promise<void> {
await this.tracing.withSpan('reports.generate_monthly', async () => {
const users = await this.tracing.withSpan('users.fetch_all', async () => {
return this.userRepo.findAll();
});
this.tracing.setAttribute('users.count', users.length);
let successCount = 0;
let errorCount = 0;
for (const user of users) {
await this.tracing.withSpan('report.generate_for_user', async () => {
this.tracing.setAttribute('user.id', user.id);
try {
await this.generateReport(user);
successCount++;
} catch (error) {
errorCount++;
this.tracing.recordException(error as Error);
throw error;
}
}).catch(() => {}); // Continue on individual failures
}
this.tracing.setAttribute('reports.success_count', successCount);
this.tracing.setAttribute('reports.error_count', errorCount);
this.tracing.addEvent('reports.completed', {
'reports.total': users.length,
});
});
}
private async generateReport(_user: User): Promise<void> {
// report generation logic
}
}Next Steps
- Troubleshooting — Debug common issues
- Overview — Back to overview