NodeJS日志记录变得容易


对于您要记录的服务的每种方法,您分别写了logger.info('ServiceName.methodName.')logger.info('ServiceName.methodName -> done.')多少次? 您是否希望它是自动化的并且在整个应用程序中具有相同的不变签名? 如果是这样,我们非常相似,我们经历了同样多次的痛苦,现在我们终于可以尝试解决它。 在一起 女士们,先生们,让我介绍一下... class-logger


类记录器的 “为什么”


工程师通常是完美主义者。 完美主义者到了极致。 我们喜欢简洁的抽象。 我们喜欢干净的代码。 我们用别人甚至看不懂的人造语言看到美。 我们喜欢按照我们设定的规则来制造小型数字世界。 我们很喜欢所有这些,因为我们很懒。 不,我们不怕工作,但是我们讨厌做任何可以自动化的工作。


只写了几千行日志代码,我们通常会提出某些模式,以标准化我们要记录的内容。 但是,我们仍然必须手动应用这些模式。 因此, 类记录器的核心思想是提供一种声明性的高度可配置的标准化方式,以在执行类方法之前和之后记录消息。


快速上手


让我们动起来,看看实际的代码是什么样子。


 import { LogClass, Log } from 'class-logger' @LogClass() class ServiceCats { @Log() eat(food: string) { return 'purr' } } 

该服务将记录三遍:


  • 在创建时,将参数列表传递给构造函数。
  • eat前使用其参数列表执行。
  • eat后将执行其参数和结果列表。

用代码的话:


 // Logs before the actual call to the constructor // `ServiceCats.construct. Args: [].` const serviceCats = new ServiceCats() // Logs before the actual call to `eat` // `ServiceCats.eat. Args: [milk].` serviceCats.eat('milk') // Logs after the actual call to `eat` // `ServiceCats.eat -> done. Args: [milk]. Res: purr.` 

现场演示


我们还能记录什么? 这是事件的完整列表:


  • 在上课之前。
  • 之前的同步和异步静态和非静态方法和功能属性。
  • 经过同步和异步的静态和非静态方法和功能特性。
  • 同步和异步静态和非静态方法和功能属性的错误。

功能属性是分配给属性( class ServiceCats { private meow = () => null } )的箭头函数。

根据我们的需求进行调整


到目前为止,一切都很好,但是我们被保证可以“自定义”,对吗? 那么我们如何调整呢?


类记录器提供了三层分层配置:


  • 全球性
  • 班级
  • 方法

在每个方法调用中,将对它们全部三个进行评估,并从上到下合并在一起。 有一些合理的默认全局配置,因此您可以使用该库而无需进行任何配置。


全局配置


这是应用程序范围的配置。 可以使用setConfig调用进行设置。


 import { setConfig } from 'class-logger' setConfig({ log: console.info, }) 

类配置


它对您的类的每个方法都有影响。 它可以覆盖全局配置。


 import { LogClass } from 'class-logger' setConfig({ log: console.info, }) @LogClass({ // It overrides global config for this service log: console.debug, }) class ServiceCats {} 

方法配置


它仅影响方法本身。 覆盖类配置,因此覆盖全局配置。


 import { LogClass } from 'class-logger' setConfig({ log: console.info, }) @LogClass({ // It overrides global config for this service log: console.debug, }) class ServiceCats { private energy = 100 @Log({ // It overrides class config for this method only log: console.warn, }) eat(food: string) { return 'purr' } // This method stil uses `console.debug` provided by class config sleep() { this.energy += 100 } } 

现场演示


配置选项


好了,我们已经学习了如何更改默认值,但是覆盖配置内容也不会受到伤害,是吗?


在这里,您可以找到许多有用的配置替代

如果您说TypeScript的语言比英语好,这是配置对象的接口的链接:)

配置对象具有以下属性:


日志


该功能可以对最终格式化的消息进行实际记录。 它用于记录以下事件:


  • 在上课之前。
  • 之前的同步和异步静态和非静态方法和功能属性。
  • 经过同步和异步的静态和非静态方法和功能特性。

默认值: console.log


logError


该函数可以对最终格式化的错误消息进行实际记录。 它用于记录此唯一事件:


  • 同步和异步静态和非静态方法和功能属性的错误。

默认值: console.error


格式化程序


这是一个有两种方法的对象: startend 。 它将记录数据格式化为最终字符串。


start格式化这些事件的消息:


  • 在上课之前。
  • 之前的同步和异步静态和非静态方法和功能属性。

end格式化这些事件的消息:


  • 经过同步和异步的静态和非静态方法和功能特性。
  • 同步和异步静态和非静态方法和功能属性的错误。

默认值: new ClassLoggerFormatterService()


包括


消息中应包含的内容的配置。


args

它可以是布尔值或对象。


如果是布尔值,则设置是否将参数列表(记住Args: [milk]吗?)同时包括在开始(构造之前和方法调用之前)和结束(之后方法调用,错误方法调用)消息中。


如果它是一个对象,则应具有两个布尔属性: startendstart包含/排除开始消息的参数列表, end对结束消息进行相同的操作。


默认值: true


构筑

一个布尔标志,用于设置是否记录类构造。


默认值: true


结果

另一个布尔标志,用于设置是包括方法调用的返回值还是由其引发的错误。 还记得Res: purr吗? 如果将此标志设置为false则不会有Res: purr


默认值: true


classInstance

再一次是布尔值或对象。
如果启用它,则将类实例的字符串表示形式添加到日志中。 换句话说,如果您的类实例具有某些属性,它们将被转换为JSON字符串并添加到日志消息中。


并非所有属性都会被添加。 类记录器遵循以下逻辑:


  • 采用实例的自身(非原型)属性。
    • 怎么了 原型动态更改的情况很少见,因此记录它几乎没有任何意义。
  • 删除任何具有function类型的function
    • 怎么了 大多数时间function属性只是不可变的箭头函数,而不是用于保留this上下文的常规类方法。 用这些函数的字符串化主体来膨胀日志没有太大意义。
  • 丢弃其中任何不是普通对象的对象。
    • 什么是普通对象? 如果对象的原型严格等于Object.prototype ,则Object.prototype其视为普通对象。
    • 怎么了 通常,我们将其他类的实例作为属性包含(将它们作为依赖项注入)。 如果我们包含这些依赖项的字符串化版本,我们的日志将变得极其繁琐。
  • 整理剩下的东西。

 class ServiceA {} @LogClass({ include: { classInstance: true, }, }) class Test { private serviceA = new ServiceA() private prop1 = 42 private prop2 = { test: 42 } private method1 = () => null @Log() public method2() { return 42 } } // Logs to the console before the class' construction: // 'Test.construct. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}.' const test = new Test() // Logs to the console before the method call: // 'Test.method2. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}.' test.method2() // Logs to the console after the method call: // 'Test.method2 -> done. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}. Res: 42.' 

默认值: false


控制格式


那么,如果您喜欢总体思路,但希望您的信息看起来与众不同,该怎么办? 您可以通过自己的自定义格式化程序来完全控制格式化。


您可以从头开始编写自己的格式化程序。 完全是。 但是,我们不会在这里讨论此选项(如果您对此真的很感兴趣,请参阅README的“格式设置”部分 )。


最快且可能最简单的方法是将内置的默认格式化程序ClassLoggerFormatterService子类ClassLoggerFormatterService


ClassLoggerFormatterService具有以下受保护的方法,用作最终消息的构建块:


  • base
    • 返回带有方法名称的类名称。 例如: ServiceCats.eat
  • operation
    • 根据是方法成功执行还是错误返回-> done-> error
  • args
    • 返回参数的字符串化列表。 示例: . Args: [milk] 。 它对引擎盖下的对象使用快速安全字符串化
  • classInstance
    • 返回一个字符串化的类实例。 示例: . Class instance: {"prop1":42,"prop2":{"test":42}} 。 如果选择包含类实例,但它不可用(静态方法和类构造就是这样),则它返回N/A
  • result
    • 返回执行的字符串化结果(即使是错误)。 使用fast-safe-stringify序列化对象。 字符串化错误将由以下属性组成:
    • 使用( error.constructor.name )创建错误的类(函数)的名称。
    • 错误代码( error.code )。
    • 错误消息( error.message )。
    • 错误名称( error.name )。
    • 堆栈跟踪( error.stack )。
  • final
    • 退货 。 只是.

start消息包括:


  • base
  • args
  • classInstance
  • final

end消息包括:


  • base
  • operation
  • args
  • classInstance
  • result
  • final

您可以覆盖这些构建块方法中的任何一种。 让我们看一下如何添加时间戳。 我不是说我们应该。 pinowinston和许多其他记录器都可以自行添加时间戳。 因此,该示例纯粹是教育性的。


 import { ClassLoggerFormatterService, IClassLoggerFormatterStartData, setConfig, } from 'class-logger' class ClassLoggerTimestampFormatterService extends ClassLoggerFormatterService { protected base(data: IClassLoggerFormatterStartData) { const baseSuper = super.base(data) const timestamp = Date.now() const baseWithTimestamp = `${timestamp}:${baseSuper}` return baseWithTimestamp } } setConfig({ formatter: new ClassLoggerTimestampFormatterService(), }) 

现场演示


结论


请不要忘记遵循安装步骤并熟悉要求,然后再决定使用此库。


希望您已经找到了对您的项目有用的东西。 随时向我传达您的反馈! 我非常感谢任何批评和疑问。

Source: https://habr.com/ru/post/zh-CN447358/


All Articles