在Puppeteer中使用Strapi日志时,evaluate内日志无法显示的问题排查
我来帮你拆解并解决这个问题,你遇到的核心问题是Puppeteer的page.evaluate()运行在浏览器沙箱环境中,和Node.js(也就是Strapi日志所在的后端环境)完全隔离,再加上当前日志监听逻辑的几个小问题,导致evaluate里的日志传不出来,甚至现有监听也只输出空内容。下面一步步讲清楚问题和修复方案:
一、先搞懂核心问题:两个环境的隔离性
page.evaluate()里的代码是在Chromium浏览器的上下文里跑的,这里的console.log、console.error都是浏览器自带的控制台方法,和Node.js环境的console(以及Strapi的log)完全不是一回事。要让浏览器里的日志传到Strapi,必须通过Puppeteer提供的page.on('console')这个“桥梁”来转发,但你当前的转发逻辑有bug,导致消息没正确传过来。
二、现有日志监听的两个问题
msg.text()无法正确获取日志内容
Puppeteer的ConsoleMessage.text()方法在处理多参数console调用(比如console.log('a', 'b', 123))或非字符串参数时,可能返回空或不完整的内容。你看到的空[Log]:就是因为这个原因。未处理重复监听问题
如果你的Puppeteer Cluster开启了页面复用,每次cluster.execute都会给同一个page添加新的console监听,会导致同一条日志被重复输出,甚至出现异常。
三、完整修复方案
1. 修复日志转发逻辑
把原来的page.on('console')替换成能正确解析所有console参数的逻辑,同时避免重复监听:
// 修改后的puppeteer code export const test = async (log?) => { const cluster = getCluster(); const jobData = { url: 'https://google.com', }; const data = await cluster.execute(jobData, async ({ page, data }) => { const { url } = data; page.setDefaultNavigationTimeout(0); // 修复console监听:先移除旧监听,再添加新的,确保参数正确解析 if (log) { // 先移除之前的监听,避免页面复用导致重复输出 page.off('console'); page.on('console', async (msg) => { try { // 解析console的所有参数(支持多参数、对象类型) const args = await Promise.all( msg.args().map(async (arg) => { try { return await arg.jsonValue(); } catch (e) { // 对无法序列化的对象,返回toString结果 return await arg.toString(); } }) ); // 把参数展开传给Strapi的log log('[Log]:', ...args); } catch (err) { log('[Console解析错误]:', err.message); } }); } await page.goto(url, { waitUntil: 'load' }); const results = await page.evaluate(async () => { const tic = Date.now(); try { // 这里写你的业务逻辑 } catch (err) { // 这个error会被page.on('console')捕获并转发到Strapi console.error('Detail fetch error:', err); } // 这条日志现在能正常传到Strapi了 console.log(`Elapsed time: ${Date.now() - tic}ms`); return []; // 替换成你实际要返回的数据 }); return results; }); return data; };
2. (可选)按日志级别转发到Strapi对应方法
如果想让浏览器的console.error对应Strapi的log.error,console.log对应log.info,可以调整调用方式和监听逻辑:
- 调用时传整个Strapi log对象:
// call puppeteer function in cron tab await test(strapi.log); // 不需要bind,因为我们会直接调用对应级别方法 - 调整监听逻辑:
if (log) { page.off('console'); page.on('console', async (msg) => { try { const args = await Promise.all( msg.args().map(async (arg) => { try { return await arg.jsonValue(); } catch (e) { return await arg.toString(); } }) ); const logLevel = msg.type(); // 拿到日志级别:log/error/warn/info等 // 调用Strapi对应级别的日志方法,兜底用debug if (typeof log[logLevel] === 'function') { log[logLevel](`[${logLevel.toUpperCase()}]:`, ...args); } else { log.debug(`[${logLevel.toUpperCase()}]:`, ...args); } } catch (err) { log.error('[Console解析错误]:', err.message); } }); }
四、为什么这样能解决问题?
- 参数解析逻辑:通过
msg.args()获取浏览器console调用的所有参数,并转换为JSON值,确保能拿到evaluate里的Elapsed time日志,同时处理了对象类型的日志参数。 - 重复监听处理:用
page.off('console')先移除旧监听,避免Cluster复用页面时,同一条日志被多次输出。 - 环境桥接:通过
page.on('console')把浏览器上下文的日志事件转发到Node.js环境的Strapi log,彻底解决两个环境的隔离问题。
备注:内容来源于stack exchange,提问作者bgyooPtr

