Node.js+Express(Pino)服务频繁报Unexpected token %错误排查求助
Hey there, let's dig into this frustrating issue you're facing with your Node.js server using Pino logging. First, let's recap the error you're seeing for clarity:
SyntaxError: Unexpected token % at EventEmitter.Pino (/code/node_modules/pino/pino.js:144:19) at EventEmitter.child (/code/node_modules/pino/pino.js:284:10) at loggingMiddleware (/code/node_modules/pino-http/logger.js:45:32) at Layer.handle [as handle_request] (/code/node_modules/express/lib/router/layer.js:95:5) at trim_prefix (/code/node_modules/express/lib/router/index.js:317:13) at /code/node_modules/express/lib/router/index.js:284:7 at Function.process_params (/code/node_modules/express/lib/router/index.js:335:12) at next (/code/node_modules/express/lib/router/index.js:275:10) at /code/node_modules/express-boom/index.js:28:5 at Layer.handle [as handle_request] (/code/node_modules/express/lib/router/layer.js:95:5) at trim_prefix (/code/node_modules/express/lib/router/index.js:317:13) at /code/node_modules/express/lib/router/index.js:284:7 at Function.process_params (/code/node_modules/express/lib/router/index.js:335:12) at next (/code/node_modules/express/lib/router/index.js:275:10) at expressInit (/code/node_modules/express/lib/middleware/init.js:40:5) at Layer.handle [as handle_request] (/code/node_modules/express/lib/router/layer.js:95:5) at trim_prefix (/code/node_modules/express/lib/router/index.js:317:13) at /code/node_modules/express/lib/router/index.js:284:7 at Function.process_params (/code/node_modules/express/lib/router/index.js:335:12) at next (/code/node_modules/express/lib/router/index.js:275:10) at query (/code/node_modules/express/lib/middleware/query.js:45:5) at Layer.handle [as handle_request] (/code/node_modules/express/lib/router/layer.js:95:5) at trim_prefix (/code/node_modules/express/lib/router/index.js:317:13) at /code/node_modules/express/lib/router/index.js:284:7 at Function.process_params (/code/node_modules/express/lib/router/index.js:335:12) at next (/code/node_modules/express/lib/router/index.js:275:10) at Function.handle (/code/node_modules/express/lib/router/index.js:174:3) at EventEmitter.handle (/code/node_modules/express/lib/application.js:174:10) at Server.app (/code/node_modules/express/lib/express.js:39:9) at emitTwo (events.js:106:13) at Server.emit (events.js:191:7) at HTTPParser.parserOnIncoming [as onIncoming] (_http_server.js:546:12) at HTTPParser.parserOnHeadersComplete (_http_common.js:99:23)
1. What's triggering this error?
The Unexpected token % error points to a problem with how Pino parses log content. Here are the most likely root causes:
- Unescaped
%characters in log data: Pino uses%as a placeholder for formatted values (similar toprintf). If your log content (like dynamic request data, user input, or API responses) includes an unescaped%, Pino tries to parse it as a placeholder but fails, throwing this syntax error. - Intermittent problematic requests: The error happens randomly because it only triggers when a request contains a
%in data that gets passed to Pino (e.g., a query parameter, request body, or URL path). Restarting works temporarily until another such request comes in. - Version-specific bugs: Older versions of Pino or
pino-httphad known bugs handling special characters like%in log payloads. These issues are often fixed in newer releases. - Middleware interference: A preceding middleware might be modifying request/response data in unexpected ways, introducing unescaped
%characters that Pino tries to process.
2. Fixes you can implement right now
Here are actionable steps to resolve the issue:
Escape % characters in log data
Before passing dynamic content to Pino, escape any % by replacing it with %%. You can create a simple utility function for this:
const escapeLogContent = (content) => { if (typeof content === 'string') { return content.replace(/%/g, '%%'); } // Recursively escape string values in objects if (typeof content === 'object' && content !== null) { return Object.fromEntries( Object.entries(content).map(([key, val]) => [key, escapeLogContent(val)]) ); } return content; }; // Use it when logging: logger.info(escapeLogContent({ requestQuery: req.query }));
Upgrade Pino and pino-http
Update to the latest stable versions to fix known parsing bugs. Run these commands:
npm install pino@latest pino-http@latest
Validate and sanitize input data
Add validation for incoming requests (using libraries like Joi or Zod) to catch or sanitize unexpected special characters before they reach the logging layer. This not only fixes the Pino error but also improves overall security.
Enable Pino's safe mode
Some versions of Pino include a safe option that automatically handles problematic characters. Enable it when initializing your logger:
const pino = require('pino'); const logger = pino({ safe: true });
Add error catching to prevent server crashes
Wrap the pino-http middleware or add a global Express error handler to catch these syntax errors and log the problematic request details, so you can identify exactly what's triggering the issue:
app.use((err, req, res, next) => { if (err instanceof SyntaxError && err.message.includes('Unexpected token %')) { logger.error('Pino parsing error triggered by request:', { url: req.url, query: req.query, body: req.body }); res.status(500).send('Server error'); } else { next(err); } });
Verify middleware order
Ensure that pino-http is placed after any middleware that modifies request data, but before your route handlers. This ensures Pino receives clean, processed data without unexpected modifications.
内容的提问来源于stack exchange,提问作者Sai Raman Kilambi

