Using Symfon2 and its Monolog framework to for logging, I am having some trouble.
On the one hand, I have a service configured with my own logger. It is working properly and I get to "info" and "err" messages without problems.
services:
my_logger:
class: Monolog\Logger
arguments: [my_info]
calls:
- [pushHandler, [#my_log_handler]]
my_log_handler:
class: Monolog\Handler\StreamHandler
arguments: [%kernel.root_dir%/logs/my_info.log, 100]
Using the following in the controller causes proper messages to be written
$this->get('my_logger')->info('info message');
$this->get('my_logger')->err('error message');
Here comes my question
Once I have placed planty of those ->err and ->info logging messages, how I tell the configuration to just write those written through the err method?
At the beginning may be I need many information, and for that reason I would write many info messages. But in a while, I may prefer to level up the logging messages through setting the action level to warning or error, avoiding the info logs to be written.
Any idea?
You just need to tweak the level at which your handler is listening. In this case it means changing the 100 to something higher like 400 for errors. So that gives you:
my_log_handler:
class: Monolog\Handler\StreamHandler
arguments: [%kernel.root_dir%/logs/my_info.log, 400]
Related
I am working on migrating an existing Symfony 2.8 which I did not create. The previous developers are not available for questions.
Most of the code easy to understand, but at one point I simply cannot figure out how are why this works:
A controller/action parameter is automatically de-serialized from JSON data to a custom object. This is nothing special, I do not understand how/where the Symfony is told what to do. Form my point of view important config data is missing but it works anyway.
That's would be fine, but without understanding why it works in this case, I cannot figure out why it does not work anymore when migrating the project to Symfony 3.4...
Sorry for the (very) long question, but I tried different ways to solve the problem and answer the questions but they all resulted in different problems...
Using the following Bundles
sensio/framework-extra-bundle v3.0.29
friendsofsymfony/rest-bundle 2.4.0
jms/serializer 1.13.0
jms/serializer-bundle 1.5.0
Config:
// app/config/config.yml
sensio_framework_extra:
request: { converters: true }
fos_rest:
...
body_converter:
enabled: true
#jms_serializer: (not configured)
# ...
Code:
// src/AppBundle/Util/SortInfo.php
class SortInfo {
public $sortOrder;
public $sortBy;
}
// src/AppBundle/Resources/serializer/Util.SortInfo.yml
AppBundle\Util\SortInfo:
exclusion_policy: ALL
properties:
sortOrder:
type: string
expose: true
sortBy:
type: string
expose: true
// src/AppBundle/Controller/SortingController.php
class SortingController extends Controller {
...
/**
* #FOSRest\View()
*/
public function sortAction(SortInfo $sortInfo) {
$this->logger->info("sortAction");
$this->logger->info(" ".gettype($sortInfo)." -- ".get_class($sortInfo));
$this->logger->info(" ".$sortInfo->sortOrder." -- ".$sortInfo->sortBy);
...
}
}
That's it. After digging for some hours I could not find any explicit configuration that would tell the sortAction method to convert the JSON data from the request automatically. But it works.
A request with the following JSON content results in the following log output:
// JSON content
{"sortOrder":"ASC", "sortBy":"name"}
// Log output
sortAction
object -- AppBundle\Util\SortInfo
ASC -- name
Why does this work?
As said before this is fine, but the problem is, that the same code does not work with Symfony 3.4 and newer versions of FOSRest, ExtrasBundle and JMSSerializer:
sensio/framework-extra-bundle v5.2.4
friendsofsymfony/rest-bundle 2.5.0
jms/serializer 2.1.0
jms/serializer-bundle 3.0.0
A request with the same JSON data results in the following log output in `Symfony 3.4':
// Log output
sortAction
object -- AppBundle\Util\SortInfo
--
So although the SortInfo is created (not null), the sortBy and sortOrder properties are empty/null.
How can this be? I do not understand why the data is de-serialized in the first place, but how it is possible to create a SortInfo object without setting the properties is even more mysterious.
In Symfony 2.8 the Util.SortInfo.yml file is necessary for the process. Removing this files results in an exception:
Symfony\Component\HttpKernel\Exception\BadRequestHttpException: "You
must define a type for
AppBundle\Util\SortInfo::$sortBy."
at /.../vendor/friendsofsymfony/rest-bundle/Request/RequestBodyParamConverter.php
Here I do not understand why SortInfo::$sortBy is a problem but not SortInfo it self. Additionally removing the file in Symfony 3.4 does not change anything.
But at least this error message confirms, that the FOS RequestBodyParamConverter is involved in the process.
Following the Symfony docs it should be necessary to manually specify that the parameter that should be converted (although it works just fine without this config in Symfony 2.8):
/**
* #FOSRest\View()
* #ParamConverter("sortInfo", converter="fos_rest.request_body")
*/
public function sortAction(SortInfo $sortInfo) {
...
}
However, adding this config results in another error in Symfony 3.4:
Uncaught PHP Exception RuntimeException: "Converter
'fos_rest.request_body' does not support conversion of parameter
'$sortInfo'
Bottom line:
This is all very mysterious.
Why does it work without any problem in Symfony 2.8 although regarding to the docs essential config is missing (explicitly specify ParamConverter)
Why does it NOT work in Symfony 3.4?
In a Symfony 2.7 app, we have attempted to set up a humanize_bytes Twig filter in order to convert long numbers of bytes into human-readable form -- 10 MB, for example.
Within our HumanReadableBytesExtension.php file is the following:
public function getFilters() {
return [
new TwigFilter('humanize_bytes', [$this, 'getHumanReadableBytesFilter'])
];
}
... and in our services.yml file lies the following:
mycompany.cms.twig.extension.human_readable_bytes_extension:
class: MyCompany\TwigExtensions\HumanReadableBytesExtension
arguments:
- '#translator'
tags:
- {name: twig.extension}
... but we find that the getFilters() method is not getting called, and that when we try to call the filter in a Twig template, we get:
Unknown "humanize_bytes" filter.
Both files pass syntax validation. The cache has been cleared. Is there somewhere else where we should be registering this filter?
====
Edit: Here is the output of the app/console debug:container mycompany.cms.twig.extension.human_readable_bytes_extension command:
[container] Information for service
mycompany.cms.twig.extension.human_readable_bytes_extension Service Id
mycompany.cms.twig.extension.human_readable_bytes_extension Class
MyCompany\TwigExtensions\HumanReadableBytesExtension Tags
- twig.extension () Scope container Public yes Synthetic no Lazy no
Synchronized no Abstract no
You mentioned you are using an abstract class. Did you override the getName method in your HumanReadableBytesExtension ?
If two extensions have the same name, only one will be loaded, the second will be silently ignored.
I ultimately just took all of my changes and put them on a fresh feature branch. That "fixed" the problem, albeit in a very non-satisfactory way. (We never really did figure out what was going wrong.)
In monolog's fingers_crossed handler, the log records are held back until a certain level triggers to unload/show all those with-held records. So far, so good.
When those withheld messages are dumped after a fingers_crossed trigger, you may now actually want to know all the source lines. (And NOT before the fingers_crossed). For this the IntrospectionProcessor is used - but, as its creator specifies: 'Warning: This only works if the handler processes the logs directly. If you put the processor on a handler that is behind a FingersCrossedHandler for example, the processor will only be called once the trigger level is reached, and all the log records will have the same file/line/.. data from the call that triggered the FingersCrossedHandler.' - not good!.
This issue was raised for the 'Laravel 5' similar issue, without a satisfying answer. Here I present a working Symfony (2/3) solution.
src/AppBundle/IntrospectionPreprocessor.php:
<?php
namespace AppBundle;
class IntrospectionPreprocessor // ATTENTION TO THE 'PRE'
extends \Monolog\Processor\IntrospectionProcessor // SO USE THE ORIGINAL CLASS
{
public function processRecord(array $record)
{
return $this( $record ); // CALL TO ITS __INVOKE(..)
}
}
The services.logger.yml:
services:
monolog.processor.introspection_patch:
class: AppBundle\IntrospectionPreprocessor
arguments: [ DEBUG, [Helper, Google] ] # FILTER OUT CLASSES THAT CONTAIN [..], similar to IntrospectionProcessor
tags:
- { name: monolog.processor, method: processRecord }
This now inserts the correct file/line/class/function info, AFTER fingers_crossed triggers subsequent handlers.
Q. Is there a way to properly make this a part of my Symfony framework, so not in my application area (AppBundle), but neither touching the Symfony code (eg. gets lost with an upgrade?)
I have configured Laravel 5 to use a custom logging configuration (default is way too simple). I've added monolog's IntrospectionProcessor to log the file name and line number of the log call.
The problem is that all lines get the same file and line number:
[2015-06-29 17:31:46] local.DEBUG (/home/vagrant/project/vendor/laravel/framework/src/Illuminate/Log/Writer.php#201): Loading view... [192.168.10.1 - GET /loans/create]
Is there a way to config the IntrospectionProcessor to print the actual lines and not the facade ones?
If I do Log::getMonolog()->info('Hello'); it works and prints the correct file and line number... but I don't know how safe is to avoid calling the Writer.writeLog function because it fires a log event (is it safe to not fire that event?).
(Only tried in Laravel 4.2!)
When pushing the Introspection Processor to Monolog it is possible to give an skipClassesPartial array as second parameter in the IntrospectionProcessor contructor. With this array it is possible to skip the Laravel Illuminate classes and the logger logs the class calling the log method.
$log->pushProcessor(new IntrospectionProcessor(Logger::DEBUG, array('Illuminate\\')));
also see: https://github.com/Seldaek/monolog/blob/master/src/Monolog/Processor/IntrospectionProcessor.php
I know this is an old question but I thought I'd give a quick update because it's pretty easy to get this done now.
I haven't tried with Laravel but My own logging mechanism is within a LoggingService wrapper class. As such the introspection was only giving details about the service rather than the caller.
after reading Matt Topolski's answer, I had a look in the IntrospectionProcessor.php. the constructor looks like this:
__construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0)
All I had to do was add the processor like this:
log->pushProcessor(new IntrospectionProcessor(Logger::DEBUG, array(), 1));
This is actually the expected functionality unless you're having the handler process the logs directly (check out the comments at the top of IntrospectionProcessor.php). My guess is you have a wrapper function around the logger and you're calling it from Writer.php -- BUT
If you look at the code for IntrospectionProcessor.php you'll see a bit of code on lines 81 to 87 that decides how to format that stack trace, and it still has access to the stack. If you bump the $i values for $trace[$i - 1] / $trace[$i] up one (aka $trace[$i]/$trace[$i + 1] respectively) you can 'climb' the stack back to where you want.
It's important to note that the 'class' and 'function' parts of the trace need to be one level of the stack higher than the 'file' and 'line.'
On a personal (plz dont mod me bruhs) note, I'd like to see functionality to include a stack offset when throwing the log in. I know what function I want to blame if an error shoots out when I write the error_log('ut oh') but I might(will) forget that by the time the 'ut oh' comes.
So I recently discovered that the log4* package was available for PHP and upon seeing this eagerly downloaded the latest from the log4php website and followed the installation instructions. The instructions indicate that one is to download the tar package, untar, and place the following directory in a place of one's choosing: log4php/src/main/php
Therefore I copied the contents of log4php/src/main/php into my lib dir under lib/log4php.
In my script, as required, I required the 'Logger.php' class and indicated a properties file to manage my appenders. The properties file, log4php.properties, is located on my filesystem at "/home1/ioforgec/www/devlab/pnotes/config/log4php.properties".
Here is the logfile content:
#
# Example Logger
#
log4php.appender.EA1 = LoggerAppenderConsole
log4php.appender.EA1.target = STDOUT
log4php.appender.EA1.layout = LoggerLayoutPattern
log4php.appender.EA1.ConversionPattern = "%m"
log4php.appender.EA1.threshold = FATAL
log4php.exampleLogger = FATAL, EA1
So here is a copy of my script that implements (more or less 'wraps' the log4php functionality):
<?php
require_once('/home1/ioforgec/www/devlab/pnotes/lib/log4php/Logger.php');
class LogUtil
{
public $logger;
public $properties_file = "/home1/ioforgec/www/devlab/pnotes/config/log4php.properties";
public static function logExample($msg)
{
Logger::configure($properties_file);
$logger = Logger::getLogger("example");
$logger->debug("Shouldnt see this print because of config max level FATAL");
$logger->fatal($msg);
}
}
?>
In order to test that this is working properly, the following script calls the LogUtil.php shown above:
#!/usr/bin/php
<?php
require_once("lib/utils/LogUtil.php");
LogUtil::logExample("example message");
?>
So, despite configuring the example logger to format the messages as the plain message, despite setting the level of the logger to a max of FATAL, not only in the logger declaration but also in the threshold command, the print to the console looks to me like the default root logger:
Mon Oct 18 20:30:05 2010,705 [827] DEBUG example - Shouldnt see this print because of config max level FATAL
Mon Oct 18 20:30:05 2010,711 [827] FATAL example - example message
I see the print statement without the plain formatting as specified, and I see two print statements, the debug print (which should never have printed due to the setting of FATAL on the logger config - FATAL is MUCH higher than DEBUG)
What on earth am I doing wrong? Ive tried every combination of setup I can possibly think of. Does anyone have any suggestions?
Edit - Update - This is still an issue. No progress.
Edit2 - Really? Has no one on this forum tried to use this very popular logging package and its incarnation as a PHP module?
I have tried every conceivable avenue to try and get my question answered, including the log4php user's mailing list.
After many many more hours of manipulating the configuration file I have discovered a small, yet critically important factoid. In order to specify a 'logger' you must name it under the 'logger' hierarchy and its appenders under the 'appender' hierarchy, etc etc. For example:
# for the 'console' logger named 'example' we first define the appender:
log4php.appender.consoleAppender = LoggerAppenderConsole
log4php.appender.consoleAppender.target = STDOUT
log4php.appender.consoleAppender.layout = LoggerLayoutSimple
# we then assign the appender and the minimum log level to the logger:
log4php.logger.example = WARN, consoleAppender
My mistake was in seeing the root logger hierarchy name 'log4php.rootLogger' and assuming that in order to assign a logger one need only name a value for 'log4php.lognameLogger'. Sadly this is no excuse as there was an example, buried, in the documentation. I suppose finding it at 9+ hours is better than not at all :)
Thanks to those who reminded me that there was more debugging work to be done, I appreciated the reminder to keep trying