CWE-778 (Insufficient Logging) and OWASP A09 (Security Logging & Monitoring Failures) both identify the absence of structured, machine-readable logs as a root cause of failed incident detection. When logging is ad-hoc console.log calls, production log platforms cannot parse, index, or alert on log data — a single exception swallowed in unstructured output can leave a breach undetected for days. NIST AU-2 requires that audit-relevant events be logged in a format that supports analysis; plain-text console output fails that bar. Structured JSON logs (with timestamp, level, message, and context fields) are what log aggregation platforms like Better Stack, Axiom, and Datadog index and query against.
High because unstructured logs disable the entire observability pipeline — alerting, search, and incident response all depend on parseable log entries.
Replace ad-hoc console.log calls with a central structured logger. For Next.js, Pino is the standard choice — fast, JSON-native, and supported by all major log platforms.
Create lib/logger.ts:
import pino from 'pino'
const logger = pino({
level: process.env.LOG_LEVEL ?? 'info',
...(process.env.NODE_ENV === 'development'
? { transport: { target: 'pino-pretty' } }
: {})
})
export default logger
Then replace every console.log(...) in server-side code with logger.info(...), logger.error(...), etc. Run a search for console.log in app/api/ and lib/ to find all call sites.
ID: saas-logging.app-logging.structured-logging
Severity: high
What to look for: Enumerate all relevant files and Check package.json for structured logging libraries: winston, pino, bunyan, consola, loglevel, @opentelemetry/api, @logtail/node, axiom-node, or any cloud provider logging SDK. Also check for a custom logger module (lib/logger.*, utils/logger.*) that wraps console with structured output (JSON format with level, timestamp, and message fields). Plain console.log scattered throughout the codebase without a wrapper does not pass. Quote the exact code pattern or configuration value found.
Pass criteria: At least 1 conforming pattern must exist. A structured logging library is present in dependencies, OR a custom logger module exists that produces JSON-formatted log entries with at minimum a timestamp, level, and message field. Usage of the logger is visible in server-side code (API routes, middleware, server components/actions).
Fail criteria: No structured logging library found; logging is exclusively raw console.log / console.error calls spread throughout the codebase with no central logger abstraction. A partial or incomplete implementation does not count as pass.
Skip (N/A) when: Project is a static site with no server-side code — no API routes, server components, server actions, or backend logic of any kind. Signal: no app/api/ or pages/api/ directory, no server functions, framework reports as static-only.
Cross-reference: For security evaluation of log data handling and access controls, the Security Headers audit covers information exposure and data protection.
Detail on fail: List the logging approach found. Example: "No structured logging library in package.json; logging is ad-hoc console.log calls found in 12+ API route files with no central logger module"
Remediation: Ad-hoc console.log calls produce unstructured output that is hard to search, filter, and alert on in production log systems. A structured logger produces machine-readable JSON entries that platforms like Better Stack, Axiom, Datadog, and Sentry can index and query.
For a Next.js project, install Pino (fast, JSON-native):
npm install pino pino-pretty
Create a central logger at lib/logger.ts:
import pino from 'pino'
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
...(process.env.NODE_ENV === 'development' ? { transport: { target: 'pino-pretty' } } : {})
})
export default logger
Then import and use logger.info(...), logger.error(...) instead of console.log.
Verify by running the app and confirming log output is JSON in production mode.