Use when implementing logging in application code. Covers log level selection, structured logging, error tracking integration, and avoiding noise.
From casaflownpx claudepluginhub casaperks/casaflow --plugin casaflowThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
PURPOSE: Guidelines for appropriate log levels and structured logging to keep error tracking actionable and signal-to-noise ratio high.
Errors should be actionable. Warnings are informational.
logger.error() -- Goes to error tracking (Sentry, Datadog, etc.) -- Requires human actionlogger.warn() -- Goes to logs only -- FYI, already handledlogger.info() -- Significant state changes -- standard operational visibilitylogger.debug() -- Detailed diagnostics -- verbose, off in production by defaultUse logger.error() when the situation requires immediate investigation or action:
Rule of thumb: If you log it as error, someone should investigate. If nobody needs to investigate, it's not an error.
Use logger.warn() when the application handles the error gracefully and continues:
Use logger.info() for operational visibility into what the application is doing:
Rule of thumb: If you were investigating a production issue, what would you want to see in the logs? That's info.
Use logger.debug() for verbose information useful during development or troubleshooting:
Always include context with log messages. Structured fields are searchable; prose embedded in strings is not.
Include these fields when available:
| Field | When | Why |
|---|---|---|
requestId | Any request-scoped operation | Correlate logs across a single request |
userId | User-initiated actions | Know who was affected |
entity / entityId | Entity operations | Know what was affected |
operation | Service methods | Know what was attempted |
duration | Timed operations | Spot slow operations |
error | Catch blocks | Preserve stack trace and error type |
Good -- structured context:
logger.error('Failed to process payment', {
requestId,
userId,
orderId,
amount,
error: error.message,
stack: error.stack,
});
Bad -- buried context in string:
logger.error(`Failed to process payment for user ${userId} order ${orderId}`);
The bad example loses the ability to filter/search by userId or orderId in log aggregation tools.
token: "...abc123"userId: "usr_123" not user: "Jane Doe"A user requesting a resource that doesn't exist is normal. The 404 response is the correct behavior. Don't treat it as an error.
// BAD -- logs error for normal operation
user = findById(id);
if (!user) {
logger.error('User not found'); // This is not an error!
throw NotFoundException('User not found');
}
// GOOD -- no log needed, the exception is the response
user = findById(id);
if (!user) {
throw NotFoundException('User not found');
}
Info logs for every database query, every cache hit, or every successful API call create noise that drowns out the signal.
// BAD -- noise
logger.info('Successfully fetched user');
logger.info('Successfully updated user');
logger.info('Cache hit for user');
// GOOD -- log the boundary, not every step
logger.info('Request completed', { requestId, duration, status: 200 });
If you log an error and then throw it, the caller will likely log it again. Log at the point where the error is handled, not at every level it passes through.
// BAD -- logged twice (here and in the caller's catch block)
try {
result = await riskyOperation();
} catch (error) {
logger.error('Operation failed', error);
throw error; // Caller will also log this!
}
// GOOD -- let the handler log it
try {
result = await riskyOperation();
} catch (error) {
throw new ServiceException('Operation failed', { cause: error });
}
// The top-level error handler logs it once
Does the error prevent the operation from completing successfully?
|-- Yes --> Is the error thrown/rethrown?
| |-- Yes --> logger.error()
| +-- No --> Does the user get an error response?
| |-- Yes --> logger.warn()
| +-- No --> logger.error()
+-- No --> Has the error been handled?
|-- Yes (fallback/retry/default) --> logger.warn()
+-- No --> logger.error()
logger.error() should be the threshold for alerting tools (Sentry, Datadog, PagerDuty, etc.). This means:
logger.error() call may trigger an alert -- make sure it's worth someone's attentionlogger.warn() is visible in log aggregation but does NOT page anyoneBefore using logger.error(), ask:
If you answered "yes" to all, use ERROR. Otherwise, use WARN.
Remember: If the application keeps working (with fallback/default/retry), it's a WARN, not an ERROR.