← Back to scenarios
ScenarioCatalog DB

Logging, metrics, and tracing

Layer logging, metrics, and tracing around a core operation while keeping cross-cutting concerns separate.

Problem

A service must apply independently composable observability concerns around an operation without embedding logging, metrics, and tracing behavior into every core service implementation.

Related patterns

Implementations

Decorator

TypeScriptApplication

Decorator in TypeScript

Adds logging and retry behavior to a notification sender by composing wrappers that preserve the same sending interface.

TypeScript
interface NotificationSender {
  send(recipient: string, message: string): Promise<void>;
}

class EmailSender implements NotificationSender {
  async send(recipient: string, message: string) {
    console.log(`Sending email to ${recipient}: ${message}`);
  }
}

abstract class NotificationSenderDecorator
  implements NotificationSender
{
  constructor(
    protected readonly sender: NotificationSender,
  ) {}

  send(recipient: string, message: string) {
    return this.sender.send(recipient, message);
  }
}

class LoggingSender extends NotificationSenderDecorator {
  async send(recipient: string, message: string) {
    console.log(`Starting notification for ${recipient}`);

    await super.send(recipient, message);

    console.log(`Completed notification for ${recipient}`);
  }
}

class RetryingSender extends NotificationSenderDecorator {
  async send(recipient: string, message: string) {
    try {
      await super.send(recipient, message);
    } catch (error) {
      console.warn("First attempt failed; retrying once.");
      await super.send(recipient, message);
    }
  }
}

const sender = new LoggingSender(
  new RetryingSender(new EmailSender()),
);

void sender.send("team@example.com", "Deployment completed.");

Associated technologies