Skip to content

Logging

amqp-contract includes an optional, framework-agnostic logging abstraction. When you provide a Logger instance, both client and worker emit structured log messages for publish/consume operations, retries, errors, and more.

Logger Interface

The Logger type is exported from @amqp-contract/core:

typescript
import type { Logger, LoggerContext } from "@amqp-contract/core";
typescript
type LoggerContext = Record<string, unknown> & {
  error?: unknown;
};

type Logger = {
  debug(message: string, context?: LoggerContext): void;
  info(message: string, context?: LoggerContext): void;
  warn(message: string, context?: LoggerContext): void;
  error(message: string, context?: LoggerContext): void;
};

Each method receives a human-readable message and an optional context object containing structured data relevant to the log entry.

Usage with Client

Pass a logger to TypedAmqpClient.create():

typescript
import { TypedAmqpClient } from "@amqp-contract/client";

const client = await TypedAmqpClient.create({
  contract,
  urls: ["amqp://localhost"],
  logger, 
});

Usage with Worker

Pass a logger to TypedAmqpWorker.create():

typescript
import { TypedAmqpWorker } from "@amqp-contract/worker";

const worker = await TypedAmqpWorker.create({
  contract,
  urls: ["amqp://localhost"],
  handlers,
  logger, 
});

What Gets Logged

Client

LevelMessageContext
infoMessage published successfullypublisherName, exchange, routingKey, compressed

Worker

Consume Lifecycle

LevelMessageContext
infoMessage consumed successfullyconsumerName, queueName
warnConsumer cancelled by serverconsumerName, queueName
warnFailed to cancel consumer during closeconsumerTag, error
error{field} validation failedconsumerName, queueName, error
errorFailed to decompress messageconsumerName, queueName, error

Error Handling

LevelMessageContext
errorError processing messageconsumerName, queueName, errorType, error
errorNon-retryable error, sending to DLQ immediatelyconsumerName, errorType, error

Retry — Quorum-Native Mode

LevelMessageContext
warnMessage at final delivery attempt (quorum-native mode)consumerName, queueName, deliveryCount, deliveryLimit, error
warnRetrying message (quorum-native mode)consumerName, queueName, deliveryCount, deliveryLimit, error

Retry — TTL-Backoff Mode

LevelMessageContext
warnRetrying message (ttl-backoff mode)consumerName, retryCount, delayMs, error
warnFailed to parse message for retry, using original bufferqueueName, error
warnCannot retry: queue does not have DLX configured, falling back to nack with requeuequeueName
errorMax retries exceeded, sending to DLQconsumerName, retryCount, maxRetries, error
errorFailed to publish message for retry (write buffer full)queueName, waitRoutingKey, retryCount
infoMessage published for retryqueueName, waitRoutingKey, retryCount, delayMs

Dead-Letter Queue

LevelMessageContext
warnQueue does not have DLX configured - message will be lost on nackqueueName
infoSending message to DLQqueueName, deliveryTag

Integration Examples

Pino

typescript
import pino from "pino";
import type { Logger } from "@amqp-contract/core";

const pinoLogger = pino({ name: "amqp" });

const logger: Logger = {
  debug: (message, context) => pinoLogger.debug(context, message),
  info: (message, context) => pinoLogger.info(context, message),
  warn: (message, context) => pinoLogger.warn(context, message),
  error: (message, context) => pinoLogger.error(context, message),
};

Winston

typescript
import winston from "winston";
import type { Logger } from "@amqp-contract/core";

const winstonLogger = winston.createLogger({
  level: "debug",
  format: winston.format.json(),
  transports: [new winston.transports.Console()],
});

const logger: Logger = {
  debug: (message, context) => winstonLogger.debug(message, context),
  info: (message, context) => winstonLogger.info(message, context),
  warn: (message, context) => winstonLogger.warn(message, context),
  error: (message, context) => winstonLogger.error(message, context),
};

Console

typescript
import type { Logger } from "@amqp-contract/core";

const logger: Logger = {
  debug: (message, context) => console.debug(message, context),
  info: (message, context) => console.info(message, context),
  warn: (message, context) => console.warn(message, context),
  error: (message, context) => console.error(message, context),
};

Logging vs OpenTelemetry

Logging and OpenTelemetry observability serve complementary purposes:

ConcernLoggingOpenTelemetry
PurposeHuman-readable operational messagesStructured traces, metrics, and context propagation
When to useDebugging, audit trails, operational monitoringDistributed tracing, performance dashboards, alerting
OverheadMinimal — synchronous string formattingSlightly higher — span creation, metric recording
DependenciesNone — bring your own loggerRequires @opentelemetry/api

Both can be enabled simultaneously — logging provides immediate human-readable output while OpenTelemetry provides deep observability across services.

Released under the MIT License.