经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » webpack » 查看文章
教你巧用webpack在日志中记录文件行号
来源:jb51  时间:2022/11/19 17:13:01  对本文有异议

前言

在做前端项目时,会在各个关键节点打印日志,方便后续数据分析和问题排查。当日志越来越多之后,又会遇到通过日志反查代码所在文件和所在行的场景,于是一个很自然的需求就出来了:

在打印日志的时候,自动注入当前文件名、行号、列号。

举个例子,有个 logger 函数,我们在 index.js 的业务代码某一行添加打印逻辑:

  1. const { logLine } = require('./utils')
  2.  
  3. function getJuejinArticles() {
  4. const author = 'keliq'
  5. const level = 'LV.5'
  6. // ... 业务代码省略,获取文章列表
  7. logLine(author, level)
  8. // ...
  9. }
  10.  
  11. getJuejinArticles()

正常情况下会输出:

  1. keliq LV.5

但是希望能够输出带文件名和行号,即:

  1. [index.js:7:3] keliq LV.5

表明当前这次打印输出来源于 index.js 文件中的第 7 行第 3 列代码,也就是 logLine 函数所在的具体位置。那如何实现这个需求呢?我的脑海中浮现了两个思路:

通过提取 Error 错误栈

因为 error 错误栈里面天然带有此类信息,可以人工制造了一个 Error,然后捕获它:

  1. exports.logLine = (...args) => {
  2. try {
  3. throw new Error()
  4. } catch (e) {
  5. console.log(e.stack)
  6. }
  7. }

仔细观察打印的结果:

Error
    at logLine (/test/src/utils.js:3:11)
    at getJuejinArticles (/test/src/index.js:7:3)
    at Object.<anonymous> (/test/src/index.js:11:1)
    at Module._compile (node:internal/modules/cjs/loader:1105:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)
    at node:internal/main/run_main_module:17:47

第三行的内容不正是我们想要的结果吗?只需要把这一行的字符串进行格式化一下,提取出 index.js:7:3 即可:

  1. at getJuejinArticles (/test/src/index.js:7:3)

由于代码结构是这样的:

.
└── src
    ├── index.js
    └── utils.js

只需要改成下面的代码即可:

  1. exports.logLine = (...args) => {
  2. try {
  3. throw new Error()
  4. } catch (e) {
  5. const lines = e.stack.split('\n')
  6. const fileLine = lines[2].split('/src/').pop().slice(0, -1)
  7. console.log(`[${fileLine}]`, ...args)
  8. }
  9. }

命令行试一试:

  1. $ test node src/index.js
  2. [index.js:7:3] keliq LV.5

问题似乎完美解决,然而还是想的太简单了,上述场景仅限于 node.js 环境,而在 web 环境,所有的产物都会被 webpack 打到一个或多个 js 文件里面,而且做了压缩混淆处理,由于 error 是在运行时被捕获到的 ,所以我没根本无法拿到开发状态下的文件名、行号和列号,如下图所示:

通过 webpack 预处理

那怎么办呢?解铃还须系铃人,既然 webpack 对代码进行了加工处理,那就只能在预处理最开始的阶段介入进来,写一个自定义的 loader 来解析源码文件,拿到文件名、行号和列号。说干就干,创建一个 inject-line.loader.js,写下模板代码:

  1. module.exports = function (content) {
  2. content = content.toString('utf-8')
  3. if (this.cacheable) this.cacheable()
  4. console.log(this.resourcePath) // 打印文件路径
  5. console.log(content) // 打印文件内容
  6. return content
  7. }
  8. module.exports.raw = true

然后在 webpack.config.js 中做配置:

  1. module.exports = {
  2. entry: './src/index.js',
  3. output: {
  4. filename: 'index.js',
  5. },
  6. module: {
  7. rules: [
  8. {
  9. test: /.js$/,
  10. exclude: [/node_modules/],
  11. use: [
  12. {
  13. loader: require.resolve('./loaders/inject-line.loader'),
  14. },
  15. ],
  16. },
  17. ],
  18. },
  19. }

一切准备就绪,先运行一下看看输出:

可以看到,index.js 和 utils.js 被自定义的 inject-line.loader.js 给加载到了,通过 this.resourcePath 能够拿到文件名称,行号和列号的话只能通过分析 content 字符串进行提取了,处理的代码如下:

  1. // 拿到文件路径
  2. const fileName = this.resourcePath.split('/src/').pop()
  3. // 文本内容按行处理后再拼接起来
  4. content = content
  5. .split('\n')
  6. .map((line, row) => {
  7. const re = /logLine((.*?))/g
  8. let result
  9. let newLine = ''
  10. let cursor = 0
  11. while ((result = re.exec(line))) {
  12. const col = result.index
  13. newLine += line.slice(cursor, result.index) + `logLine('${fileName}:${row + 1}:${col + 1}', ` + result[1] + ')'
  14. cursor += col + result[0].length
  15. }
  16. newLine += line.slice(cursor)
  17. return newLine
  18. })
  19. .join('\n')

这里面的逻辑,如果光看代码的话可能会云里雾里,其实思路很简单,就是下面这样的:

这样的话,即使代码经过各种压缩转换,也不会改变开发状态下代码所在的文件名、行与列的位置了。打开 webpack 打包后的文件看一下:

到这里,功能就已经开发完了,不过还有一个小小的缺陷就是 logLine 函数名是写死的,能不能让用户自己定义这个函数名呢?当然可以,在 webpack 配置文件中,支持利用 options 属性传递 config 配置参数:

  1. module.exports = {
  2. entry: './src/index.js',
  3. output: {
  4. filename: 'index.js',
  5. },
  6. module: {
  7. rules: [
  8. {
  9. test: /.js$/,
  10. exclude: [/node_modules/],
  11. use: [
  12. {
  13. loader: require.resolve('./loaders/inject-line.loader'),
  14. options: {
  15. config: {
  16. name: 'customLogName',
  17. },
  18. },
  19. },
  20. ],
  21. },
  22. ],
  23. },
  24. }

然后在 inject-line.loader.js 代码中通过 this.query.config 拿到该配置即可,不过正则表达式也要根据这个配置动态创建,字符串替换的时候也要换成该配置变量,最终代码如下:

  1. module.exports = function (content) {
  2. content = content.toString('utf-8')
  3. if (this.cacheable) this.cacheable()
  4. const { name = 'logLine' } = this.query.config || {}
  5. const fileName = this.resourcePath.split('/src/').pop()
  6. content = content
  7. .split('\n')
  8. .map((line, row) => {
  9. const re = new RegExp(`${name}\((.*?)\)`, 'g')
  10. let result
  11. let newLine = ''
  12. let cursor = 0
  13. while ((result = re.exec(line))) {
  14. const col = result.index
  15. newLine += line.slice(cursor, result.index) + `${name}('${fileName}:${row + 1}:${col + 1}', ` + result[1] + ')'
  16. cursor += col + result[0].length
  17. }
  18. newLine += line.slice(cursor)
  19. return newLine
  20. })
  21. .join('\n')
  22.  
  23. return content
  24. }
  25. module.exports.raw = true

总结

到此这篇关于如何巧用webpack在日志中记录文件行号的文章就介绍到这了,更多相关webpack日志记录文件行号内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号