在项目中,我们会频繁用到 console.log() 来输出一些关键信息到控制台中,有助于开发调试,以及问题的排查,待项目上线后,这些调试日志又得及时清除。
同时在前端质量要求下,我们会做“前端埋点”,用于远程上报一些关键行为信息,用于在出问题时还原用户的操作路径,复现 BUG,从而解决问题,而各种各样的上报若是能在业务开发中抹平差异,也有助于研发提效。
因此,有必要在团队中封装日志工具(Logger),用于统一管理日志输出和格式化上报,降低开发者对多平台上报差异的心智负担。
预期日志管理工具(Logger)需要有如下能力:
在阅读完 Axios 的源码后,个人认为 Axios 里对于设计模式的应用是非常灵活,同理,一个好的日志工具也应当遵守着一定的软件设计模式原则。
作为项目中用到的日志工具,单例模式应当是更适合的选择!
Logger 的打印输出能力,本质上还是借助了 window.console 对象中的方法:
Console 对象
在面向对象编程中,我们可以认为 console 是一个已经初始化的实例,同时也是一个单例,因为它是全局唯一。
而单例模式的最大好处就是全局唯一,对于做日志统一管理有着天然的友好支持基础。
接下来通过具体的代码,来逐一实现并完善我们的 Logger 日志工具类。
在 ESM 规范下,我们可以直接通过直接导出实例方式( export default new ClassName() ),来实现单例模式。
Logger 的基础结构就有了:
/**
* 日志打印工具,统一管理日志输出&上报
*/
class Logger {
/** 命名空间(scope),用于区分所在执行文件 */
private namespace: string
constructor(namespace = 'unknown') {
this.namespace = namespace
}
}
export default new Logger()
参考 Axios 的设计 ,因此我们还提供 create() 方法,为创建新实例留一个入口方法。
/**
* 创建新的 Logger 实例
*
* @param namespace 命名空间
* @returns Logger
*/
public create(namespace = 'unknown') {
return new Logger(namespace);
}
当需要重新定义一个 logger 实例时,就可以参考如下方式:
import logger from '@/utils/logger'
const newLogger = logger.create('custom')
logger.info(newLogger === logger) // [unknown] false
需要区分 info 、 warn 、 error 三种类型的日志,实现如下:
定义日志枚举类型:
const enum LogLevel {
/** 普通日志 */
Log,
/** 警告日志 */
Warning,
/** 错误日志 */
Error,
}
const Styles = ['color: green;', 'color: orange;', 'color: red;']
const Methods = ['info', 'warn', 'error'] as const
private _log(level: LogLevel, args: unknown[]) {
if (!__DEV__) return
console[Methods[level]](`%c${this.namespace}`, Styles[level], ...args)
}
/**
* 打印输出信息 :bug:
*
* @param args 任意参数
*/
public info(...args: unknown[]) {
this._log(LogLevel.Log, args)
return this
}
/**
* 打印输出警告信息 :grey_exclamation:
*
* @param args 任意参数
*/
public warn(...args: unknown[]) {
this._log(LogLevel.Warning, args)
return this
}
/**
* 打印输出错误信息 :x:
*
* @param args 任意参数
*/
public error(...args: unknown[]) {
this._log(LogLevel.Error, args)
return this
}
在 _log() 方法中,通过 __DEV__ 环境变量区分“生产”和“开发”:
if (!__DEV__) return
这种变量可以理解为“ 开关 ”:
生产环境则控制台不输出信息,在实际应用中,可以扩展“是否输出信息”的变量,来针对性扩展,例如线上需要通过特定参数展示调试日志,用于线上定位问题,那么就可以综合多个条件来决定是否输出控制台,毕竟编程最核心的问题是解决需求。
在开发模式下,针对不同的信息类型,会标注不同的颜色:
Chrome 浏览器下的效果
与此同时,在每个“输出”方法中都返回了 this (当前实例),因而便可以为 链式调用方法 提供了使用基础。
namespace 最重要的作用是: 区分在不同组件或文件下的日志 ,便于问题定位排查。
由于 Logger 将所有的输出集中到了统一文件,在 console.log() 中文件定位永远是 Logger 类定义实现所在文件,因此需要 namespace 来区分。
新增 setNamespace() 方法:
/**
* 设置命名空间(日志前缀)
* @param namespace
*/
public setNamespace(namespace = '') {
this.namespace = `[${namespace}]`
return this
}
在 TypeScript 环境下,会提供代码提示,例如某个文件下输出错误信息的方式。而 setNamespace() 方法,并不是每次都需要调用的,只需在文件中调用一次即可。
在一些关键时机,例如进入页面、点击“付费按钮”等一些关键操作上,一般会加上一些上报到远程,用于记录用户操作路径,以此便于在出现问题后,复现 BUG 并“对症下药”。
而埋点上报一般有三类:代码埋点、可视化埋点、无痕埋点。
我们这里通过给 Logger 增加远程上报的方式就是代码埋点。
一般情况下,埋点上报属于“前端监控”方面,前端监控是一个独立的管理系统,它的职能是负责前端项目的监控、异常报警等,因此通常会有用于项目集成的前端 SDK
有了 Logger 实例,我们可以在 Logger 中直接统一集成“ 前端监控 SDK ”的主动上报方法即可!
在 Logger 类中新增三个方法:
reportLog()
reportEvent()
reportException()
/**
* 远程上报
* TODO: 根据基建环境自定义扩展
*/
public reportLog() {
this.info() // 用于在本地输出
}
public reportEvent() {
this.info()
}
public reportException() {
this.error()
}
至于为什么添加着两个方法,实际是根据“前端监控 SDK”提供的 api 来决定
例如常见的 “Sentry - 应用监控错误溯源” 平台,针对主动上报,提供了三种方法,通常为了保持一致性,降低心智负担,因此新增对应的三个上报方法。
具体的上报参数和逻辑,则需要大家根据自己的业务区扩展。
从上面 Logger 类的实现,可以发现一个明显的问题,如果业务需要扩展功能,则需要修改 Logger 类内部的方法,Logger 类中的方法和逻辑,我们可以理解为是所有业务都通用的,业务定制化的功能应该通过额外扩展方式来完善。
那有没有什么办法,可以实现不修改方法,而扩展 Logger 的功能呐?
有几个方案:
个人推荐第二个方案,但如果每一次调用,都按照如下方式:
logger.info('message', () => {})
但这种设计比较粗糙
参考 Axios 的拦截器设计,也就是 AOP(面向切面编程模式)的设计思想,来扩展 _log() 方法。
新增类型申明:
/**
* 日志的配置类型
*/
type LoggerConfigType = {
/** 命名空间 */
namespace?: string
}
/**
* 拦截器函数类型
*/
type InterceptorFuncType = (config: LoggerConfigType) => void
将 Logger 的配置集中的 config 私有变量中,并新增 addBeforeFunc() 和 addAfterFunc() 两个方法,用于新增自定义“拦截器”函数
其中一个细节是,日志打印之后的拦截器,按照 FCLS (First Come Last Serve,先到后服务)的策略,和 Axios 的响应拦截器执行顺序对齐,与此同时,拦截器函数中会注入当前 Logger 的 config 配置。
通过简单的“ 拦截器 ”,即可实现功能的扩展,这种方式的功能扩展不会影响到主体功能,后期的维护升级是无侵入性的,还算比较优雅的,是吧!
这里还可以考虑更多设计,例如参考 发布订阅设计模式 来改造,通过生命周期的关键点,被动触发,主动通知并执行所有订阅了对应消息的事件,可以参阅《 聊一聊发布订阅设计模式》
也可以用 插件模式 方式来实现扩展,类似发布订阅模式,给 _log() 函数添加执行的钩子函数 (回调函数),例如这种设计下,把“埋点上报”等功能拆分成插件,再实现一个简单的事件队列模型,集成一下子!
至此,一个基本的日志工具就实现完成了,但并未完完全全遵守设计原则,这里在生产实践中还需要封装、抽离相应“职责”,增加可维护性。
在团队中以此作为基础结构,然后针对团队、项目、业务的特点做适当的扩展,构建符合当前团队特性的通用日志工具模块,应该也不是什么难事!
来源: DYBOY
在前端应用越来复杂的今天,为了监控前端应用是否正常运行,通常会在前端收集一些错误与性能等数据,最终我们会将这些数据上报到服务端。上报的方式有很多,理论上我们只要能把数据发给服务端就行了
如果想统计网站的访问来源信息,可以用 php 获取信息,记录到数据库的形式,也可以直接使用 nginx 提供的访问日志,来记录网站的访问详情,管理员可以通过分析 nginx 的访问日志,来分析用户的访问来源,访问行为详情
日志记录是每个开发人员从第一天编写代码时就要做的事情,但很少有人知道它可以产生的价值和最佳实践。在本文中,我们将讨论以下主题:什么是日志,为什么很重要性?
随着程序的增长,日志记录成为跟踪所有内容的关键部分。它对于调试目的尤为重要。现在已经有了 npm 的日志记录模块。这些模块可以将日志存储在不同格式或级别的文件中。我们将使用流行的ORM Mongoose
平常都使用console来打印 node 脚本执行时需要看到的信息,但这些信息也就只能在控制台查看。假如你希望将打印的信息记录到文件查看的话,那就往下看看吧。使用 node.js 对日志进行存储,就一定会对本地文件的增删改查,那么我们需要用到fs。
通常我们在写Node.js程序时,都习惯使用console.log打印日志信息,但这也仅限于控制台输出,有时候我们需要将信息输出到日志文件中,实际上利用console也可以达到这个目的的,今天就来简单介绍一下。
nodeJs 自己的log 输出一堆讯息,要从这么多又杂的讯息中找出问题,是件非常辛苦的事情。log4js 是一款基于Node 环境下较好用的log 模组,本篇简单介绍log4js 的使用方式。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!