Skip to content

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

Released under the MIT License.