我想在PHP中实现日志记录机制:
例如:
Class A {
public function f_A {
log_to_file($message);
}
}
Class B {
public function f_B {
log_to_file($message);
}
}
我将非常感谢任何提示。我想实现一些简单而优雅的解决方案。
我正在考虑它(谢谢你的回答),我想我会这样做(也许,有一些错误,我是从头开始写的):
interface Logger {
public function log_message($message);
}
class LoggerFile implements Logger {
private $log_file;
public function __construct($log_file) {
$this->log_file = $log_file;
}
public function log_message($message) {
if (is_string($message)) {
file_put_contents($this->log_file, date("Y-m-d H:i:s")." ".$message."\n", FILE_APPEND);
}
}
}
//maybe in the future logging into database
class LoggerDb implements Logger {
private $db;
public function __construct($db) {
//some code
}
public function log_message($message) {
//some code
}
}
Class A {
private $logger;
public function __construct(Logger $l) {
$this->logger = $l;
}
public function f_A {
$this->logger->log_message($message);
}
}
Class B {
private $logger;
public function __construct(Logger $l) {
$this->logger = $l;
}
public function f_B {
$this->logger->log_message($message);
}
}
//usage:
//in config.php:
define("CONFIG_LOG_FILE", "log/app_log.log");
//in the index.php or some other files
$logger = new LoggerFile(CONFIG_LOG_FILE);
$instance_a = new A($logger);
$instance_b = new B($logger);
答案 0 :(得分:18)
通常,在代码中使用记录器有两个主要用例:
侵入式日志记录:
大多数人都使用这种方法,因为它最容易理解。
实际上,如果日志记录是域逻辑本身的一部分,那么您应该只使用侵入式日志记录。例如 - 在处理敏感信息的支付或管理的类中。
非侵入式伐木:
使用此方法而不是更改要记录的类,可以将现有实例包装在容器中,以便跟踪实例与应用程序其余部分之间的每次交换。
您还可以临时启用此类日志记录,同时调试开发环境之外的某些特定问题,或者在对用户行为进行一些研究时。由于记录实例的类永远不会改变,因此与侵入式日志记录相比,破坏项目行为的风险要低得多。
为此,您有两种主要方法可供选择。您可以注入实现Logger
接口的实例,也可以为类提供工厂,而工厂只会在必要时初始化日志记录系统。
注意:
由于看起来直接注射对你来说不是一个隐藏的秘密,我会把那部分留下......只有我会敦促你避免使用已定义文件之外的常量。
现在..使用工厂和延迟加载实现。
您首先要定义您将使用的API (在完美的世界中,您从单元测试开始)。
class Foobar
{
private $loggerFactory;
public function __construct(Creator $loggerFactory, ....)
{
$this->loggerFactory = $loggerFactory;
....
}
....
public function someLoggedMethod()
{
$logger = $this->loggerFactory->provide('simple');
$logger->log( ... logged data .. );
....
}
....
}
这家工厂还有两个好处:
注意:
实际上,当以这种方式编写时,Foobar类只依赖于实现Creator接口的实例。通常,您将注入构建器(如果需要实例类型,可能需要某些设置)或工厂(如果要创建具有相同接口的不同实例)。
下一步将实施工厂:
class LazyLoggerFactory implements Creator
{
private $loggers = [];
private $providers = [];
public function addProvider($name, callable $provider)
{
$this->providers[$name] = $provider;
return $this;
}
public function provide($name)
{
if (array_key_exists($name, $this->loggers) === false)
{
$this->loggers[$name] = call_user_func($this->providers[$name]);
}
return $this->loggers[$name];
}
}
当您致电$factory->provide('thing');
时,工厂会查询是否已创建实例。如果搜索失败,则会创建一个新实例。
注意:我实际上并不完全确定这可以称为“工厂”,因为实例化实际上已封装在匿名函数中。
最后一步实际上是与提供商进行连接:
$config = include '/path/to/config/loggers.php';
$loggerFactory = new LazyLoggerFactory;
$loggerFactory->addProvider('simple', function() use ($config){
$instance = new SimpleFileLogger($config['log_file']);
return $instance;
});
/*
$loggerFactory->addProvider('fake', function(){
$instance = new NullLogger;
return $instance;
});
*/
$test = new Foobar( $loggerFactory );
当然要完全理解这种方法,你必须知道闭包在PHP中是如何工作的,但无论如何你都必须学习它们。
这种方法的核心思想是,不是注入记录器,而是将现有实例放在容器中,该容器充当所述实例和应用程序之间的隔膜。然后,这个膜可以执行不同的任务,其中一个就是记录。
class LogBrane
{
protected $target = null;
protected $logger = null;
public function __construct( $target, Logger $logger )
{
$this->target = $target;
$this->logger = $logger;
}
public function __call( $method, $arguments )
{
if ( method_exists( $this->target, $method ) === false )
{
// sometime you will want to log call of nonexistent method
}
try
{
$response = call_user_func_array( [$this->target, $method],
$arguments );
// write log, if you want
$this->logger->log(....);
}
catch (Exception $e)
{
// write log about exception
$this->logger->log(....);
// and re-throw to not disrupt the behavior
throw $e;
}
}
}
此类也可以与上述惰性工厂一起使用。
要使用此结构,只需执行以下操作:
$instance = new Foobar;
$instance = new LogBrane( $instance, $logger );
$instance->someMethod();
此时,包装实例的容器将成为原始文件的完全功能替代品。您的应用程序的其余部分可以处理它,就好像它是一个简单的对象(传递,调用方法)。并且包装的实例本身并不知道它正在被记录。
如果您在某个时候决定删除日志记录,那么可以在不重写其他应用程序的情况下完成。
答案 1 :(得分:1)
Logger的目标是保存调试信息。 记录器必须是具有存储消息和遇险级别的接口的类。 实施是次要的。今天你想要文件记录。明天您可能希望将日志放入数据库。所以逻辑必须写在logger类的一面。 已有一个名为Monolog https://github.com/Seldaek/monolog
的漂亮记录器答案 2 :(得分:1)
如果您需要完整的日志记录框架,并且支持记录到不同的输出,则log4PHP是一个开源解决方案。
如果您想要一个适合您需求的小型实现,那么应该这样做
class Logger
{
const INFO = 'info';
const ERROR = 'error';
private static $instance;
private $config = array();
private function __construct()
{
$this->config = require "/path/to/config.php";
}
private static function getInstance()
{
if(!self::$instance)
{
self::$instance = new Logger();
}
return self::$instance;
}
private function writeToFile($message)
{
file_put_contents($this->config['log_file'], "$message\n", FILE_APPEND);
}
public static function log($message, $level = Logger::INFO)
{
$date = date('Y-m-d H:i:s');
$severity = "[$level]";
$message = "$date $severity ::$message";
self::getInstance()->writeToFile($message);
}
}
//config.php
return array(
'log_file' => '/tmp/my_log.txt'
);
Logger::log($message);
未经测试,但应该可以使用。