Are you not bothered to write logger.info('ServiceName.methodName.')
And logger.info('ServiceName.methodName -> done.')
For each? Maybe you, like me, have repeatedly thought about automating this business? In this article, the story will focus on class-logger , as one of the solutions to the problem with just two decorators.
Not all perfectionists are engineers, but all engineers are perfectionists (well, almost). We like beautiful laconic abstractions. We see beauty in a set of cracks that a person unprepared and cannot read. We like to feel like gods, creating micro-universes that live by our rules. Presumably, all these qualities originate from our immense laziness. No, the engineer is not afraid of work as a class, but he hates the routine repetitive actions that the hands are trying to automate.
Having written several thousand lines of code intended only for logging, we usually arrive at the creation of some of our own patterns and standards for writing messages. Unfortunately, we still have to apply these patterns manually. The most important "why" class-logger is to provide a declarative, configurable way to easily log template messages when creating a class and calling its methods.
Without wandering around the bush, let's go straight to the code.
import { LogClass, Log } from 'class-logger' @LogClass() class ServiceCats { @Log() eat(food: string) { return 'purr' } }
This service will create three entries in the log:
eat
with a list of arguments.eat
with the list of arguments and the result of the execution.In words of code:
// ServiceCats // `ServiceCats.construct. Args: [].` const serviceCats = new ServiceCats() // `eat` // `ServiceCats.eat. Args: [milk].` serviceCats.eat('milk') // `eat` // `ServiceCats.eat -> done. Args: [milk]. Res: purr.`
A complete list of events that can be logged:
A functional property is a switch function assigned to a property (class ServiceCats { private meow = () => null }
). Most often used to save the execution context (this
).
class-logger three levels of configuration hierarchy:
When calling each method, all three levels merge together. The library provides a sensible global config by default, so it can be used without any prior configuration.
It is created for the entire application. You can override it with setConfig
.
import { setConfig } from 'class-logger' setConfig({ log: console.info, })
It is its own for each class and is used for all methods of this class. Can redefine global config.
import { LogClass } from 'class-logger' setConfig({ log: console.info, }) @LogClass({ // log: console.debug, }) class ServiceCats {}
It works only for the method itself. It takes precedence over the class config and the global config.
import { LogClass } from 'class-logger' setConfig({ log: console.info, }) @LogClass({ // log: console.debug, }) class ServiceCats { private energy = 100 @Log({ // log: console.warn, }) eat(food: string) { return 'purr' } // `console.debug` sleep() { this.energy += 100 } }
We considered how to change the config, but still have not figured out exactly what can be changed in it.
Here you can find many examples of configuration objects for different cases .
An interface reference if you understand TypeScript better. than russian :)
The configuration object has the following properties:
This is a function that deals, in fact, with logging. She comes to the input formatted messages as strings. Used to log the following events:
The default is console.log
.
This is a function that also deals with logging, but only error messages. She also comes to the input formatted messages as strings. Used to log the following events:
The default is console.error
.
This is an object with two methods: start
and end
. These methods create that very formatted message.
start
creates messages for the following events:
end
creates messages for the following events:
The default is new ClassLoggerFormatterService()
.
Config of what should be included in the final message.
This can be either a boolean value or an object.
If it is boolean
, then it sets whether to include a list of arguments (remember Args: [milk]
?) In all messages ( start
and end
).
If it is an object, then it should have two boolean properties: start
and end
. start
specifies whether to include the argument list for start
messages, end
for end
messages.
The default is true
.
This is a boolean
that controls whether to create a class or not.
The default is true
.
This is a boolean
that controls the logged value returned from the method. It is worth noting that the return value is not only what the method returns when it is successfully executed, but also an error that it throws in case of unsuccessful execution. Remember Res: purr
? If this flag is false
, then there will be no Res: purr
.
The default is true
.
This can be either a boolean value or an object. The concept is the same as include.args
.
Adds a serialized view of your class instance to messages. In other words, if your class has any properties, they will be serialized in JSON and added to the logs.
Not all properties are serializable. The class-logger uses the following logic:
function
.function
type are simply pointer functions that are not done by conventional methods for the sake of preserving the context ( this
). They rarely have a dynamic nature, so there is no sense in logging them.ClassLoggerFormatterService
considers an object simple if its prototype is an 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 } } // `Test` // 'Test.construct. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}.' const test = new Test() // `method2` // 'Test.method2. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}.' test.method2() // `method2` // 'Test.method2 -> done. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}. Res: 42.'
The default is false
.
What to do if you like the idea, but your idea of the beautiful insists on a different format of message lines? You can take complete control over the formatting of messages by transferring your own formatter.
You can write your own formatter from scratch. Of course. But this option will not be covered under this (if you are interested in this option, take a look at the "Formatting" section in the README).
The quickest and easiest way to redefine formatting is to inherit your formatter from the default formatter - ClassLoggerFormatterService
.
ClassLoggerFormatterService
has the following protected
methods that create small blocks of final messages:
base
ServiceCats.eat
.operation
-> done
or -> error
, depending on whether the method succeeds.args
. Args: [milk]
. It uses fast-safe-stringify to serialize objects under the hood.classInstance
. Class instance: {"prop1":42,"prop2":{"test":42}}
. If you included include.classInstance
in the config, but for some reason the instance itself is not available at the time of logging (for example, for static methods or before creating a class), returns N/A
result
error.constructor.name
).error.code
).error.message
).error.name
).error.stack
).final
.
. Just .
.The start
message consists of:
base
args
classInstance
final
The end
message consists of:
base
operation
args
classInstance
result
final
You can override only the base methods you need.
Let's take a look at how you can add a timestamp to all messages.
I'm not saying that this should be done in real projects. pino , winston and most other loggers can do this on their own. This example is for educational purposes only.
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(), })
Remember to follow the installation instructions and review the requirements before using this library on your project.
I hope you did not waste your time, and the article was just a little useful to you. Please kick and criticize. We will learn to code better together.
Source: https://habr.com/ru/post/447970/
All Articles