We often have to use different micro-services who write in many log files. Use utilities like Kibana is a good thing, but in order to take full advantage of its features we have to try to standardize and normalize the logs.
The company where I work having introduced Kibana recently, he asked me to implement a proper strategy to log all the micro-services.
First we defined the basic rules of the logs.
General logging rules of team
Mandatory Fields
- Date + Hours : 2017-02-09 10:24:00
- Log Level : TRACE DEBUG INFO WARN ERROR FATAL
- Customer Id
- Site or ProductLine
- Message
If log is ERROR mandatory fields are also:
- File
- Class
- Method
- Line
Describing the exact point of the code that has launched the exception.
When logging
- Start and End of service call
- External call
- Exceptions
- Key points of the application
Where logging
- 1 file for service
- 1 error file for service
Technology
Monolog
Monolog sends your logs to files, sockets, inboxes, databases and various web services. See the complete list of handlers below. Special handlers allow you to build advanced logging strategies.
This library implements the PSR-3 interface that you can type-hint against in your own libraries to keep a maximum of interoperability. You can also use it in your applications to make sure you can always use another compatible logger at a later time. As of 1.11.0 Monolog public APIs will also accept PSR-3 log levels. Internally Monolog still uses its own level scheme since it predates PSR-3. Reference:https://github.com/Seldaek/monolog
Working with Symfony and Monolog
Symfony comes with native Monlog library that allows you to create logs that can be stored in a variety of different places.
The logger service has different methods for different logging levels/priorities. You can configure the logger to do different things based on the level of a message (e.g. send an email when an error occurs).
The logger has a stack of handlers, and each can be used to write the log entries to different locations (e.g. files, database, Slack, etc).
For a proper and complete vision of Monolog in Symfony, refer to the official documentation: http://symfony.com/doc/current/logging.htmlhttp://symfony.com/doc/current/logging/processors.html http://symfony.com/doc/current/reference/configuration/monolog.html
Usage example
We use as example one of many micro-services that my company use: “serviceactivator” web service.
What it does the micro-service does not matter. What interests us is how it write logs.
1) Configuration of monolog
|
2) Base Logger: the parent logger class
|
3) The specific logger class
|
4) Configuration of services
|
5) Creation of Monolog Processor
Monolog allows you to process the record before logging it to add some extra data. A processor can be applied for the whole handler stack or only for a specific handler.
A processor is simply a callable receiving the record as its first argument. Processors are configured using the monolog.processor DIC tag. See the reference about it.
|
This processor, for example, is responsible to place in extra all key/value pairs passed in the context.
6) Declare service Processor
|
The method __invoke of Processor will be called every times that ServiceActivatorLogger write log.
7) Call log Example
|
Generated log:
|
8) Specific Processor for log error
We will use IntrospectionProcessor that will be called only if application want log a error:
|
Generated log:
[2017-03-10 10:40:38] [61762462] [DATING]serviceactivator.ERROR: : user not active { "file" : "<path>/ConstraintsAboidActifValidator.php" , "line" :19, "class" :"ServiceActivatorWSBundle\\Validator\\Constraints ConstraintsAboidActifValidator ", " function ":" validate"} |
9) Separate file for error log
Nothing could be simpler with monolog:
|
10) Custom Formatter
So far everything is fine, but what happens if we have not ‘customerId’ or productLine in context array? We will have a log like this:
[2017-03-13 11:09:14] [] [] serviceactivator_error.ERROR: Error parameters validator { "error" : "6176243423423423462: user not active" , "customerId" : "6176243423423423462" , "productLine" : "DATING" } { "file" : "<path>/ConstraintsAboidActifValidator.php" , "line" :19, "class" :"Meetic\\Payment\\ServiceActivatorWSBundle\\Validator\\Constraints ConstraintsAboidActifValidator "," function ":" validate"} |
You can see that brackets that should contain customerId and productLine are empty.
So, the last step, act to printing well logs are customize monolog formatter class. Specifically we will customize LineFormatter class.
} |
If customerId and productLine are empty the application use default format (SIMPLE_FORMAT) and we will have a log like this:
[2017-03-13 11:43:30] serviceactivator_error.ERROR: Error parameters validator {"error":"DATINGa is not a valid ProductLine","customerId":"29341843","productLine":"DATINGa"} {"file":"<path>/ConstraintsProductLineAcceptedValidator.php","line":26, "class":"ServiceActivatorWSBundle\\Validator\\Constraints\\ConstraintsProductLineAcceptedValidator","function":"validate"} |
So now, the formatter service can change in this way:
|
We can do this because we put the custom logic, act to handle format of log, inside custom format method of formatter class.
Evolutions
The next development could be use configuration files to tell to processor which fields include in extra field of record structure:
|
And pass it to service:
|
References
- https://www.webfactory.de/blog/logging-with-monolog-in-symfony2
- https://blog.dev-art.fr/php/monolog/
- http://symfony.com/doc/current/logging/processors.html
Good point. What do you think, if BaseLogger implements PSR-3 as well to make the class more reliable?
LikeLike
Base Logger use Monolog\Logger and therefore PS3 is implemented by default. 😉
LikeLike
[…] UPDATE: You might be interested in https://stefanoalletti.wordpress.com/2017/03/09/symfony-and-monolog-how-use-processor-in-your-projec… […]
LikeLike
Where is source of IntrospectionProcessor class?
LikeLike
What happend when you use the IntrospectionProcessor? What file it log?
LikeLike
What line, file, function, etc. logs it? When I implemented something like this its log the BaseLogger call…
LikeLike
What do you think, if BaseLogger implements PSR-3 as well to make the class more reliable?
LikeLike
What do you think, if BaseLogger implements PSR-3 as well to make the class more reliable?
LikeLike
When I implemented something like this its log the BaseLogger call… What do you think, if BaseLogger implements PSR-3 as well to make the class more reliable?
LikeLike
When I implemented something like this its log the BaseLogger call… What do you think, if BaseLogger implements PSR-3 as well to make the class more reliable?
LikeLike