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, 
}).getOrThrow();

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, 
}).getOrThrow();

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 — Immediate-Requeue Mode ​

LevelMessageContext
infoMessage published for retryqueueName, retryCount
warnFailed to parse message for retry, using original bufferqueueName, error
warnRetrying message (immediate-requeue mode)consumerName, queueName, retryCount, maxRetries, error
errorMax retries exceeded, sending to DLQ (immediate-requeue mode)consumerName, queueName, retryCount, maxRetries, error
errorFailed to publish message for retry (write buffer full)queueName, retryCount

Retry — TTL-Backoff Mode ​

LevelMessageContext
infoMessage published for retryqueueName, retryCount, delayMs
warnFailed to parse message for retry, using original bufferqueueName, error
warnRetrying message (ttl-backoff mode)consumerName, queueName, retryCount, maxRetries, delayMs, error
errorMax retries exceeded, sending to DLQ (ttl-backoff mode)consumerName, queueName, retryCount, maxRetries, error
errorFailed to publish message for retry (write buffer full)queueName, retryCount, delayMs
errorQueue does not have TTL-backoff infrastructureconsumerName, queueName

Retry — None Mode (No retry) ​

LevelMessageContext
warnRetry disabled (none mode), sending to DLQconsumerName, error

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.