| name | logging |
| description | Comprehensive logging standards for applications including structured logging, log levels, message formatting, and observability guidelines |
Logging Standards
Comprehensive logging guidelines for consistent, structured, and maintainable logging across the monorepo.
Core Principles
1. Structured Logging
All logs MUST be structured (JSON-serializable) for efficient querying and analysis.
2. Contextual Information
Include relevant context (request ID, user ID, session ID) in every log.
3. Appropriate Levels
Use correct log levels to enable effective filtering.
4. Performance Aware
Avoid expensive operations in log statements.
Log Levels
| Level | When to Use | Examples |
|---|
| ERROR | Application errors requiring attention | Unhandled exceptions, failed critical operations |
| WARN | Unexpected but recoverable situations | Retry attempts, deprecated usage, rate limiting |
| INFO | Significant application events | Request handling, job completion, config changes |
| DEBUG | Detailed diagnostic information | Variable values, execution paths, timing |
| TRACE | Very detailed debugging | Loop iterations, method entry/exit |
Message Format Pattern
REQUIRED: All log messages MUST follow this pattern:
[SERVICE_OR_CLASS]: MESSAGE
The prefix should be the actual class name or service name enclosed in square brackets.
Language-Specific Examples
TypeScript/JavaScript
import { logger } from "@{{PROJECT_NAME}}/logging";
logger.info("[UserService]: User created", {
userId: user.id,
email: user.email,
source: "registration",
});
logger.error("[PaymentService]: Payment processing failed", {
error: err.message,
stack: err.stack,
orderId: order.id,
amount: order.total,
});
logger.info(`User ${userId} created with email ${email}`);
logger.error("Payment failed");
Python
import structlog
logger = structlog.get_logger()
logger.info("[UserService]: User created",
user_id=user.id,
email=user.email,
source="registration"
)
logger.error("[PaymentService]: Payment processing failed",
error=str(e),
order_id=order.id,
amount=order.total,
exc_info=True
)
logger.info(f"User {user_id} created with email {email}")
C# / .NET
_logger.LogInformation("[UserService]: User created - UserId: {UserId}, Email: {Email}",
user.Id, user.Email);
_logger.LogError(ex, "[PaymentService]: Payment failed - OrderId: {OrderId}, Amount: {Amount}",
order.Id, order.Total);
_logger.LogInformation($"User {userId} created with email {email}");
Go
logger.Info("[UserService]: User created",
zap.String("userId", user.ID),
zap.String("email", user.Email),
zap.String("source", "registration"),
)
logger.Error("[PaymentService]: Payment failed",
zap.Error(err),
zap.String("orderId", order.ID),
zap.Float64("amount", order.Total),
)
logger.Info(fmt.Sprintf("User %s created", userId))
Contextual Logging
Request Context
Always include request-scoped identifiers:
const requestContext = {
requestId: req.headers["x-request-id"] || uuid(),
userId: req.user?.id,
sessionId: req.session?.id,
traceId: req.headers["x-trace-id"],
};
logger.info("[OrderController]: Order submitted", {
...requestContext,
orderId: order.id,
itemCount: order.items.length,
});
Correlation IDs
For async operations, propagate correlation:
async function processOrder(order: Order, correlationId: string) {
logger.info("[OrderProcessor]: Starting order processing", {
correlationId,
orderId: order.id,
});
await inventoryService.reserve(order.items, { correlationId });
await paymentService.charge(order.total, { correlationId });
logger.info("[OrderProcessor]: Order processing complete", {
correlationId,
orderId: order.id,
duration: Date.now() - startTime,
});
}
Performance Logging
Timing Operations
const startTime = performance.now();
await expensiveOperation();
const duration = performance.now() - startTime;
logger.info("[DataProcessor]: Operation completed", {
operationType: "batch-import",
recordCount: records.length,
durationMs: Math.round(duration),
recordsPerSecond: Math.round(records.length / (duration / 1000)),
});
Conditional Debug Logging
if (logger.isDebugEnabled()) {
logger.debug("[CacheService]: Cache contents", {
keys: cache.keys(),
size: cache.size,
hitRate: cache.stats.hitRate,
});
}
Error Logging Best Practices
Include Full Context
try {
await processPayment(order);
} catch (error) {
logger.error("[PaymentService]: Payment processing failed", {
error: error.message,
stack: error.stack,
errorCode: error.code,
orderId: order.id,
userId: order.userId,
amount: order.total,
paymentMethod: order.paymentMethod,
requestId: context.requestId,
retryable: isRetryableError(error),
attemptNumber: attempt,
});
throw error;
}
Categorize Errors
logger.error("[AuthService]: Authentication failed", {
errorCategory: "SECURITY",
errorType: "INVALID_CREDENTIALS",
userId: attemptedUserId,
ipAddress: request.ip,
userAgent: request.headers["user-agent"],
});
Sensitive Data
Never Log
- Passwords or secrets
- Full credit card numbers
- Social security numbers
- API keys or tokens
- Personal health information
Masking Patterns
const maskEmail = (email: string) => {
const [local, domain] = email.split("@");
return `${local.slice(0, 2)}***@${domain}`;
};
const maskCardNumber = (card: string) => {
return `****-****-****-${card.slice(-4)}`;
};
logger.info("[PaymentService]: Payment authorized", {
email: maskEmail(user.email),
cardNumber: maskCardNumber(card),
amount: order.total,
});
Monorepo Logger Package
Create a shared logging package for consistency:
packages/
└── logging/
├── src/
│ └── index.ts
├── package.json
└── README.md
import pino from "pino";
export const createLogger = (service: string) => {
return pino({
name: service,
level: process.env.LOG_LEVEL || "info",
formatters: {
level: (label) => ({ level: label }),
},
base: {
service,
environment: process.env.NODE_ENV,
version: process.env.APP_VERSION,
},
});
};
import { createLogger } from "@{{PROJECT_NAME}}/logging";
const logger = createLogger("{{APP_NAME_1}}");
Environment Configuration
LOG_LEVEL=info
LOG_FORMAT=json
LOG_DESTINATION=stdout
LOG_SERVICE_URL=
Observability Integration
OpenTelemetry Context
import { trace, context } from "@opentelemetry/api";
function logWithTrace(level: string, message: string, data: object) {
const span = trace.getSpan(context.active());
const traceContext = span
? {
traceId: span.spanContext().traceId,
spanId: span.spanContext().spanId,
}
: {};
logger[level](message, { ...data, ...traceContext });
}
Quick Reference
Log Level Decision Tree
Is it an error that needs attention? → ERROR
Is it unexpected but handled? → WARN
Is it a significant business event? → INFO
Is it helpful for debugging? → DEBUG
Is it very detailed/verbose? → TRACE
Checklist Before Logging
Related Skills
- See
.agents/skills/systematic-debugging/ for debugging workflows
- See
.agents/skills/verification-before-completion/ for testing logs