Monolog implementation with common class

2019-07-08 04:11发布

问题:

I succeeded in implementing a Monolog logger for test purposes. And now I'm trying to use it in a project. This project doesn't use any MVC framework.

I'm trying to write a common class file to wrap access to the Monolog instance.

Common class file: File: app_log.php

require 'autoload.php';
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Processor\UidProcessor;
use Monolog\Processor\WebProcessor;
use Monolog\Processor\MemoryUsageProcessor;
use Monolog\Processor\ProcessIdProcessor;
use Monolog\Formatter\LineFormatter;

class app_log {
  public function info(){
    $logger = new Logger('applog');
    $handler = new RotatingFileHandler('useractivity.log', 0, Logger::INFO);
    $handler->setFormatter(new LineFormatter("[%datetime%] %extra.process_id% %channel%.%level_name%: %message% %extra% %context% \n"));
    $logger->pushHandler($handler);
    $logger->pushProcessor(new WebProcessor);
  }  
}

Other File: users.php

include_once 'app_log.php';
class users extends dbconnector{
    function login(){
      // Some project code. 
      $logger = new app_log();
      $logger->info('User logged successfully');
    }
}

Up to this works well and i want to include filename, method name, request parameters. But i am getting app_log.php file name instead users.php and Method name is 'info' instead 'login' in the logs.

Example:

[2018-06-07 20:55:50] 4410 applog.INFO: User logged successfully {"file":"/var/www/portal/lib/app_log.php","line":59,"class":"app_log","function":"info"} []

Could you guys help on this part?

回答1:

I'm afraid your whole design is simply wrong.

Instead of instantiating a new Logger every time you need to log, you should be creating a single $logger to use as a service around your application.

Without knowing more about your application (and having to rewrite your application would make the question too broad anyway), it's hard to guess how to implement dependency injection here.

But a simplistic and naive approach would be:

class users extends dbconnector {

    protected $logger;

    public function _construct(Logger $logger) {
       $this->logger = $logger;
    }

    function login() {
      $this->logger->info('User logged successfully');
    }
}

And then:

$logger  = new Logger('applog');
$handler = new RotatingFileHandler('useractivity.log', 0, Logger::INFO);
$handler->setFormatter(new LineFormatter("[%datetime%] %extra.process_id% %channel%.%level_name%: %message% %extra% %context% \n"));
$logger->pushHandler($handler);
$logger->pushProcessor(new WebProcessor);
$logger->pushProcessor(new IntrospectionProcessor);

$users = new users($logger);

If you inject that Logger instance in the objects that need it, you can use it directly without having to create your own "wrapper" (which in your example was poorly designed), and the output in the log files will match your expectations.

Notice that you are not using the IntrospectionProcessor, which you'd need to capture the filename and file line number. In the above example I'm also pushing it in $logger, the Logger instance.

(And also note that simply adding this processor to your code won't solve your issue, since the call to Logger::info() would always happen in app_log::info()).

Remember that you'll need to add the corresponding use statement: use Monolog\Processor\IntrospectionProcessor;

I do not know all the details of your system, and can't build it all for you anyway, but if dependency injection is still too much for you, you could just cheat temporarily with a global-state approach.

E.g.:

function get_logger() {
   static $logger;

   if ($logger !== null) {
      return $logger;
   }

   $logger  = new Logger('applog');
   $handler = new RotatingFileHandler('useractivity.log', 0, Logger::INFO);
   $handler->setFormatter(new LineFormatter("[%datetime%] %extra.process_id% %channel%.%level_name%: %message% %extra% %context% \n"));
   $logger->pushHandler($handler);
   $logger->pushProcessor(new WebProcessor);
   $logger->pushProcessor(new IntrospectionProcessor);

   return $logger;
}

You could put this in a file that you require_once, and in each of these places where you need access to the logger you can simply do:

 get_logger()->info('write to the log!');

This is not something I'd endorse, but a crummy workaround for you to get moving until you advance somewhat in your understanding of other OOP topics. This is nothing else than a poor's man singleton, which more often than not is a pattern to avoid anyway; but that could help you right now..



回答2:

WebProcessor will not add data you need, I mean file and line.

IntrospectionProcessor does what you need, try

$logger->pushProcessor(new IntrospectionProcessor());