python日志打印被调用的类、方法和文件名

CLog是一个Python日志模块,旨在提供详细的日志信息,包括文件名、类名、方法名及参数,帮助开发者追踪程序调用链。与标准logging模块相比,CLog通过链式追踪,使日志更清晰,便于理解和调试。
部署运行你感兴趣的模型镜像

CLog的一个例子
我在文章开头,先告诉大家,CLog是干什么的,看下面的例子:

from CLog import CLog

class FooClass(object):

    def foo(self, text):
        log = CLog()
        log.write('hello world')

if __name__ == '__main__':
    fc = FooClass()
    fc.foo('def')

运行python example.py后,日志如下:

2014-03-30 19:09:35,582 [example.py:FooClass() --> example.py:foo(‘def’)] hello world
CLog的作用,就是在打印日志的同时,把文件名,类名,方法名和方法的参数都打印出来了。而我们在使用CLog的时候,是完全透明的,不需要关心这些细节,这一切都是CLog完成的。

CLog在这里:https://github.com/baniuyao/Python-CLog

为什么我要开发一个Python日志模块
之前我一直使用Python自带的logging模块来打印日志,他功能强大,可以定制非常多的东西,但唯一让我不爽的,就是当我在看我打印的日志的时候,我根本不知道这条日志是在哪个地方打印的。特别是最近我在开发一个报警系统,设计到报警规则的解析,算是一个比较复杂的系统。我需要一个强大的日志能够帮我追踪程序的每一次关键的逻辑判断,简而言之,我要知道我的这条日志,是在哪个文件的哪个类里的哪个方法中打印的,最好还能知道方法调用的参数。

比如一个foo.py

def Foo(object):
    def foo(self):
        log.log('hello world')
if __name__ == '__main__':
    f = Foo()
    f.foo()
然后日志中是这样的:
foo.py:Foo() --> foo.py:foo() hello world

是不是很棒?

基于这个需求,我开发了Python-CLog。其中的CLog是Chain Log的简称,因为CLog的目的是打印出整个调用链。

Python logging模块和CLog对比
logging模块支持打印调用logging方法的函数名和模块名,但这个功能非常的简陋,只支持当前调用这个logging方法所在的函数,不支持链式的追踪。我们看下面这个例子。

代码Foo1_logging.py:


import logging
from Foo2 import Foo2

logging.basicConfig(filename="./use_logging.log", 
                    format='%(asctime)-6s: %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s', 
                    level=logging.DEBUG)

class Foo1(object):
    def foo(self, text):
        f2 = Foo2()
        f2.foo('foo2')
        logging.info('use logging in foo1')

if __name__ == '__main__':
    f = Foo1()
    f.foo('foo1')
代码Foo2_logging.py:
import logging

logging.basicConfig(filename="./use_logging.log", 
                    format='%(asctime)-6s: %(name)s - %(levelname)s - %(module)s - %(funcName)s - %(lineno)d - %(message)s', 
                    level=logging.DEBUG)

class Foo2(object):
    def foo(self, text):
        logging.info('use logging in foo2')

使用logging模块的结果
我运行了Foo1_logging.py,获取到的use_logging.log如下:

2014-03-30 23:28:25,795: root - INFO - Foo2 - foo - 11 - use logging in foo2
2014-03-30 23:28:34,619: root - INFO - Foo2 - foo - 11 - use logging in foo2
2014-03-30 23:28:34,619: root - INFO - Foo1 - foo - 14 - use logging in foo1
如果说不看代码内容,我想能看懂Foo1和Foo2的依赖关系是非常困难的吧。如果还有并发的请求,那么日志几乎是没有用处的。而且,Foo1和Foo2的foo方法都接受一个参数,这个参数,也没有在日志里反应出来。

我们看看CLog对于这种情况,日志是怎样的,代码有稍许变动。

Foo1_cl.py:
from Foo2_cl import Foo2
from CLog import CLog

class Foo1(object):
    def foo(self, text):
        f2 = Foo2()
        f2.foo('foo2')
        cl = CLog()
        cl.write('use CL in foo1')

if __name__ == '__main__':
    f = Foo1()
    f.foo('foo1')
Foo2_cl.py:
from CLog import CLog

class Foo2(object):
    def foo(self, text):
        cl = CLog()
        cl.write('use clog in foo2')

使用CLog的日志:
2014-03-30 23:35:18,630 [Foo1_cl.py:Foo2() --> Foo1_cl.py:foo(‘foo1’) --> Foo2_cl.py:foo(‘foo2’)] use clog in foo2
2014-03-30 23:35:18,630 [Foo1_cl.py:Foo1() --> Foo1_cl.py:foo(‘foo1’)] use CL in foo1
是不是清楚多了呢?调用链非常清楚,也知道foo的参数是什么,相比logging的日志,大家可以比较一下。

CLog是怎么工作的
不知道有多少人读过《Python源码分析》,Python在执行代码的时候,有一个栈帧frame的概念,我们把它理解成一张纸片,而运行一段Python代码,就是用线把一串纸片串起来执行。CLog就是调用了Python自带的inspect模块,来获取到执行时代码的frame,从中找到我们需要的信息,我们还是看example.py,这次我把CLog模块的代码改了一下,将调用write时候的frames全部打印出来:

(<frame object at 0xe0faef0>, ‘/python-chain-log/CLog.py’, 28, ‘get_meta_data’, [’ frames = inspect.stack()\n’], 0)
(<frame object at 0xe0fad00>, ‘/python-chain-log/CLog.py’, 46, ‘write’, [’ chain = self.get_meta_data()\n’], 0)
(<frame object at 0xe0f4280>, ‘example.py’, 9, ‘foo’, [" log.write(‘hello world’)\n"], 0)
(<frame object at 0xe088400>, ‘example.py’, 13, ‘’, [" fc.foo(‘def’)\n"], 0)
从里面我们获取的信息,就能够得到CLog获取到的信息了。

您可能感兴趣的与本文相关的镜像

Python3.10

Python3.10

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

<think>我们参考上述引用内容,特别是引用[1]引用[3]中提到了日志中输出文件名行号的问题。 引用[1]提到在Qt中通过定义宏`QT_MESSAGELOGCONTEXT`来在日志中包含上下文信息(如文件名、函数名、行数)。 引用[3]则提到了在Python的logging模块中,如果进行了二次封装,可能会导致输出的文件名、行号、方法名是封装后的logger文件的信息,而不是真正的调用者信息。 用户的问题是关于Boost日志库是否能够打印日志所在文件名行数。 Boost日志库是一个功能强大的C++日志库,它支持多种日志特性,包括输出文件名行号。 在Boost日志库中,可以通过设置日志记录属性(attributes)来添加文件名行号。具体来说,可以使用`boost::log::add_file_line`属性来添加文件名行号信息。此外,还可以通过`%File%``%Line%`在格式字符串中指定输出文件名行号。 下面是一个简单的示例,展示如何在Boost日志中输出文件名行号: ```cpp #include <boost/log/trivial.hpp> #include <boost/log/expressions.hpp> #include <boost/log/utility/setup/file.hpp> #include <boost/log/utility/setup/common_attributes.hpp> #include <boost/log/sources/severity_logger.hpp> #include <boost/log/support/date_time.hpp> namespace logging = boost::log; namespace src = boost::log::sources; namespace expr = boost::log::expressions; namespace keywords = boost::log::keywords; void init_logging() { logging::add_file_log( keywords::file_name = "sample.log", keywords::format = ( expr::stream << expr::attr< unsigned int >("LineID") << ": [" << expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S") << "] [" << expr::attr< logging::trivial::severity_level >("Severity") << "] " << expr::smessage // 添加文件名行号 << " [" << expr::attr<std::string>("File") << ":" << expr::attr<int>("Line") << "]" ) ); logging::add_common_attributes(); // 添加公共属性,包括LineID, TimeStamp, ProcessID, ThreadID // 注意:默认的公共属性不包含文件名行号,需要我们在记录日志时添加 } int main() { init_logging(); // 使用BOOST_LOG_TRIVIAL宏,它会自动添加文件名行号 BOOST_LOG_TRIVIAL(info) << "This is an info log message"; // 或者使用带属性的日志记录 src::severity_logger< logging::trivial::severity_level > lg; BOOST_LOG_SEV(lg, logging::trivial::info) << "Another info log message with file and line"; // 但是上面的方式不会自动添加文件名行号,除非我们在日志记录时手动添加 // 因此,通常我们使用宏来自动添加这些属性 return 0; } ``` 注意:使用`BOOST_LOG_TRIVIAL`宏会自动添加文件名行号。但是,如果你使用`severity_logger`等日志记录器,并且想要在每条日志中自动包含文件名行号,则需要通过宏来包装日志记录语句,或者设置日志记录器自动添加这些属性。 Boost日志库提供了一个`BOOST_LOG`宏,它可以自动记录文件名、行号函数名。例如: ```cpp #include <boost/log/sources/record_ostream.hpp> #include <boost/log/attributes/value_extraction.hpp> // 定义一个宏,用于简化日志记录 #define LOG(lg, sev) \ BOOST_LOG_SEV(lg, sev) << "[" << __FILE__ << ":" << __LINE__ << "] " // 使用示例 src::severity_logger< logging::trivial::severity_level > lg; LOG(lg, logging::trivial::info) << "This message will include file and line"; ``` 但是,Boost日志库的文档中其实已经提供了更方便的宏`BOOST_LOG`,它来自动添加这些属性。不过,默认情况下,`BOOST_LOG`宏可能不会包含文件名行号,除非你使用了特定的属性设置。 实际上,Boost日志库有一个属性`boost::log::attributes::mutable_constant`可以用来在每次记录日志时设置当前的文件名行号,但这通常由宏来完成。 最常用的方法是使用Boost提供的宏,如`BOOST_LOG_TRIVIAL`,它已经自动包含了文件名、行号函数名,或者使用`BOOST_LOG_CUSTOM`等宏来定制。 总结:Boost日志库能够打印日志所在文件名行数,通常通过使用其提供的宏(如`BOOST_LOG_TRIVIAL`)可以自动添加这些信息。如果需要更复杂的控制,可以自定义宏或设置日志格式来包含这些属性。 注意:在设置日志格式时,使用`%File%``%Line%`占位符可以输出文件名行号。 因此,用户问题的答案是肯定的。 §§相关问题§§ 1. 如何在Boost日志库中自定义日志格式以包含文件名行号? 2. Boost日志库中除了文件名行号,还能记录哪些上下文信息? 3. 如何避免在二次封装Boost日志库时丢失原始调用点的信息(似于引用[3]中Python日志封装的问题)? </think> 是的,Boost日志库能够打印日志所在文件名行数。以下是具体实现方式技术要点: ### 实现方法 1. **使用`BOOST_LOG_TRIVIAL`宏** 在日志语句中直接使用宏,会自动记录文件名行号: ```cpp BOOST_LOG_TRIVIAL(info) << "This is a log message"; // 输出示例: [info] main.cpp:15 This is a log message ``` 2. **自定义格式添加位置信息** 在日志格式字符串中使用占位符 `%File%` `%Line%`: ```cpp logging::formatter fmt = expr::stream << "[" << expr::attr< std::string >("File") << ":" << expr::attr< int >("Line") << "] " << expr::smessage; sink->set_formatter(fmt); ``` 3. **获取调用点信息** 通过 `BOOST_LOG_STREAM_WITH_PARAMS` 宏确保记录真实调用位置: ```cpp #define MY_LOG(lvl) BOOST_LOG_STREAM_WITH_PARAMS( \ (boost::log::trivial::logger::get()), \ (boost::log::keywords::severity = lvl) \ (boost::log::keywords::file = __FILE__) \ (boost::log::keywords::line = __LINE__)) MY_LOG(boost::log::trivial::info) << "Accurate location"; ``` ### 注意事项 1. **避免二次封装问题** 若自定义封装日志接口,必须传递 `__FILE__` `__LINE__` 宏(似引用[3]的Python问题): ```cpp // 正确封装方式 #define LOG_INFO(msg) \ BOOST_LOG_TRIVIAL(info) << __FILE__ << ":" << __LINE__ << " " << msg ``` 2. **性能优化** - 在Release版本中默认不包含位置信息(需显式启用) - 可通过 `BOOST_LOG_DONT_USE_FILE_AND_LINE` 宏禁用该功能提升性能 ### 对比其他框架 - Qt需定义 `QT_MESSAGELOGCONTEXT` 才支持位置信息(引用[1]) - spdlog默认包含文件名行号(引用[2]) - Python需用 `%(filename)s` `%(lineno)d` 格式(引用[3]) > 结论:Boost日志通过宏机制原生支持文件名行号输出,是C++日志系统中的标准功能[^1][^2][^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值