| name | outfitter-logging |
| version | 0.1.0 |
| description | Patterns for @outfitter/logging including structured logging, sinks, redaction, and child loggers. Use when configuring logging, adding redaction, creating sinks, or when "logger", "structured logging", "redaction", "log level", or "@outfitter/logging" are mentioned. |
| allowed-tools | Read Write Edit Glob Grep |
Logging Patterns
Deep dive into @outfitter/logging patterns.
Creating a Logger
import { createLogger, createConsoleSink } from "@outfitter/logging";
const logger = createLogger({
name: "my-app",
level: "info",
sinks: [createConsoleSink()],
redaction: { enabled: true },
});
Log Levels
| Level | Method | Use For |
|---|
trace | logger.trace() | Very detailed debugging |
debug | logger.debug() | Development debugging |
info | logger.info() | Normal operations |
warn | logger.warn() | Unexpected but handled |
error | logger.error() | Failures requiring attention |
fatal | logger.fatal() | Unrecoverable failures |
Level hierarchy: trace < debug < info < warn < error < fatal
Setting level to info hides trace and debug.
Structured Logging
Always use metadata objects:
logger.info("User created", {
userId: user.id,
email: user.email,
duration: performance.now() - start,
});
logger.info("User " + user.name + " created in " + duration + "ms");
Child Loggers
Add context that persists across calls:
import { createChildLogger } from "@outfitter/logging";
const requestLogger = createChildLogger(logger, {
requestId: ctx.requestId,
handler: "createUser",
});
requestLogger.info("Processing");
requestLogger.debug("Validated input");
requestLogger.info("User created", { userId });
Redaction
Enable Redaction
const logger = createLogger({
name: "my-app",
level: "info",
sinks: [createConsoleSink()],
redaction: { enabled: true },
});
logger.info("Config", {
apiKey: "secret-123",
password: "hunter2",
email: "user@example.com",
});
Default Redaction Patterns
Automatically redacted:
password, pwd
apiKey, api_key
secret, secretKey
token, accessToken
auth, authorization
key (when containing sensitive data)
credential, credentials
Custom Patterns
const logger = createLogger({
name: "my-app",
redaction: {
enabled: true,
patterns: ["password", "apiKey", "myCustomSecret", "internalToken"],
},
});
Deep Redaction
Nested values are also redacted:
logger.info("Request", {
headers: {
authorization: "Bearer token",
},
body: {
user: {
password: "secret",
},
},
});
Sinks
Console Sink
import { createConsoleSink } from "@outfitter/logging";
const consoleSink = createConsoleSink({
colorize: true,
prettyPrint: true,
timestampFormat: "iso",
});
File Sink
import { createFileSink } from "@outfitter/logging";
const fileSink = createFileSink({
path: "/var/log/myapp/app.log",
maxSize: 10 * 1024 * 1024,
maxFiles: 5,
});
Multiple Sinks
const logger = createLogger({
name: "my-app",
level: "debug",
sinks: [
createConsoleSink({ level: "info" }),
createFileSink({
path: "/var/log/myapp/debug.log",
level: "debug",
}),
],
});
Custom Sink
const customSink = {
log: (record) => {
externalService.send({
level: record.level,
message: record.message,
metadata: record.metadata,
timestamp: record.timestamp,
});
},
};
const logger = createLogger({
name: "my-app",
sinks: [customSink],
});
Environment Configuration
const logger = createLogger({
name: "my-app",
level: process.env.LOG_LEVEL || "info",
sinks: [
createConsoleSink({
colorize: process.stdout.isTTY,
prettyPrint: process.env.NODE_ENV !== "production",
}),
],
});
Handler Context Integration
import { createContext } from "@outfitter/contracts";
import { createLogger, createChildLogger } from "@outfitter/logging";
const baseLogger = createLogger({ name: "my-app", level: "info" });
export function createHandlerContext() {
const ctx = createContext({ logger: baseLogger });
return {
...ctx,
logger: createChildLogger(baseLogger, { requestId: ctx.requestId }),
};
}
const myHandler: Handler<Input, Output, Error> = async (input, ctx) => {
ctx.logger.info("Processing", { input });
};
Performance
Conditional Logging
if (logger.isEnabled("debug")) {
const expensiveData = computeDebugInfo();
logger.debug("Debug info", { data: expensiveData });
}
Lazy Evaluation
logger.debug("State", () => ({
memory: process.memoryUsage(),
connections: getActiveConnections(),
}));
Best Practices
- Structured metadata — Always use objects, not string concatenation
- Child loggers — Add request context that persists
- Enable redaction — Prevent secrets from leaking
- Level per environment — Debug in dev, info in prod
- Request IDs — Include for tracing across handlers
- Lazy evaluation — Avoid expensive computations at disabled levels
Related Skills
stack:patterns — Context creation
stack:outfitter-daemon — Daemon logging
stack:debug — Troubleshooting logging