Log4cxx日志库的使用

简介

在代码量比较小的程序里追踪bug可以直接进行断点调试;但对于较大的软件系统这通常是一个低效的办法,尤其是软件系统包含UI交互的时候,断点常常使得UI卡死,使得追踪bug变得难以进行;另一种情形则是在多线程或者多进程的应用场景里,断点也很难发挥作用;一般书上讲printf是最好的调试方法,通过在关键的地方使用printf可以把软件运行的内部状态暴露出来,从而定位可能存在的问题,但带UI的程序通常是不能显示printf输出的,对于所有这些情况,日志都是更好的选择.

除了上边提到的部分场景没有日志几乎就没办法定位,使用日志还有额外的好处就是能够定制软件输出的信息;从输出信息的格式[输出日志的时间戳、文件名及行号、进程ID等]和为每一个模块指定单独的日志输出位置[输出到console,文件,数据库,发送到指定邮箱等];不仅是可以定制,而且是在不改变软件代码的情况下通过修改日志库的配置文件做到这些.无需再次编译代码.

这里介绍的日志库是用于c++程序的log4cxx,这个库还有java版本的log4j,和c#版本的log4net, java和c#的不太了解就不多说.

Log4cxx的配置文件

Log4cxx的配置文件是一个xml文件或者properties属性文件,properties文件的相关设置网上有很多讲的,但我觉得这种格式不够清晰,更倾向于使用xml,典型的xml配置文件由两部分组成,一是logger节点,用来对应代码中的模块;一是appender,用来指定日志输出的格式和输出位置;一般还会有一个root节点,可以作为默认的logger来使用;

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<!-- Note that this file is read by the sdk every 60 seconds -->
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

  <!--
  %m 输出代码中指定的消息
  %p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
  %r 输出自应用启动到输出该log信息耗费的毫秒数
  %c 输出所属的类目,通常就是所在类的全名
  %t 输出产生该日志事件的线程名
  %n 输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n”
  %d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出2008年11月14日 15:16:17,890
  %l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。

  Log4j提供的appender有以下几种:
  org.apache.log4j.ConsoleAppender 控制台
  org.apache.log4j.FileAppender 文件
  org.apache.log4j.DailyRollingFileAppender 每天产生一个日志文件
  org.apache.log4j.RollingFileAppender 文件大小到达指定尺寸的时候产生一个新的文件
  org.apache.log4j.WriterAppender 将日志信息以流格式发送到任意指定的地方

  Log4j提供的Layout有以下几种:
  org.apache.log4j.HTMLLayout 以HTML表格形式布局
  org.apache.log4j.PatternLayout 可以灵活地指定布局模式
  org.apache.log4j.SimpleLayout 包含日志信息的级别和信息字符串
  org.apache.log4j.TTCCLayout 包含日志产生的时间、线程、类别等等信息
  -->

  <!-- 性能统计日志输出 -->
  <appender name="PerformanceAppender" additivity="false" class="org.apache.log4j.RollingFileAppender">
    <!-- The file to log Performance calls -->
    <param name="file" value="d:/log/performance.log" />
    <param name="append" value="true" />
    <param name="BufferedIO" value="true"/>
    <param name="maxFileSize" value="10000KB" />
    <param name="maxBackupIndex" value="1" />
    <layout class="org.apache.log4j.PatternLayout">
      <!-- The log message pattern -->
      <param name="ConversionPattern" value="%5p %d{ISO8601} [%t] [%c] %m%n"/>
    </layout>
  </appender>

    <!-- 性能统计日志输出 -->
  <appender name="defaultAppender" additivity="false" class="org.apache.log4j.RollingFileAppender">
    <!-- The file to log Performance calls -->
    <param name="file" value="d:/log/alg.log" />
    <param name="append" value="true" />
    <param name="BufferedIO" value="true"/>
    <param name="maxFileSize" value="10000KB" />
    <param name="maxBackupIndex" value="1" />
    <layout class="org.apache.log4j.PatternLayout">
      <!-- The log message pattern -->
      <param name="ConversionPattern" value="%5p %d{ISO8601} [%F, %L] %m%n"/>
    </layout>
  </appender>

  <!-- 命令行日志输出 -->
  <appender name="Console" additivity="false" class="org.apache.log4j.ConsoleAppender">
    <!-- Logs to Console -->
    <layout class="org.apache.log4j.PatternLayout">
      <!-- The log message pattern -->
      <param name="ConversionPattern" value="%5p %d{ISO8601} [%F, %L] %m%n"/>
    </layout>
  </appender>

  <logger name="Performance_logger" additivity="false">
    <!-- set logger setting -->
    <!-- 设置级别 -->
    <level value="all"/>

    <!-- 设置日志输出-->
    <!-- 输出到命令行 -->
    <!--<appender-ref ref="Console" />-->
    <!--输出到默认日志文件 -->
    <appender-ref ref="defaultAppender" />
    <!--根据需要可单独输出到特定文件 -->
    <!--<appender-ref ref="PerformanceAppender" />-->
  </logger>

  <root>
    <priority value="all" />
    <appender-ref ref="defaultAppender" />
    <!-- <appender-ref ref="Console" /> -->
  </root>
</log4j:configuration>

这个例子里包含两个logger: root 和 Performance_logger, 三个appender: Console\ defaultAppender\ PerformanceAppender;

Root作为默认logger保留,虽然我一般不使用它; Performance_logger对应我想在程序中输出的性能统计信息;对于我的代码里的需要统计性能的地方,我都是用Performance_logger,这样,通过为它指定一个appender,我们可以将性能统计信息汇集在同一个日志文件里;其他的用于调试bug的日志输出信息可以通过添加更多的logger来进行精细的控制;三个appender分别对应输出日志到控制台(Console),默认的输出(defaultAppender);单独为性能统计输出准备的PerformanceAppender;这样一来在需要的时候我们只需要在配置文件里把Performance_logger 的appender-ref属性改为Console,就可以让日志输出到控制台而无需修改任何软件相关的代码;经过了测试阶段可能不需要再输出任何性能统计信息,那么我们可以把Performance_logger的Level属性改为Fatal,那么就不会有任何日志信息输出了;{我们需要在代码里输出日志信息时指定相应的Level,如果日志信息的Level高于配置文件里设定的Level,则该日志信息会被输出};

如果我们有很多模块在同一个软件系统里使用,他们都需要使用日志,那么我们在配置文件里为每一个模块添加对应的logger和appender就可以实现对不同的模块输出的日志信息进行精确的控制了.具体的控制在于每一个logger有name属性;在代码中输出日志信息时可以

以如下方式以getlogger指定logger以进行日志输出控制:

LoggerPtr Perf_logger (Logger::getLogger("Performance_logger "));
log4cxx::logstream logstream(Perf_logger, Level::getInfo());
logstream << Level::getTrace() << "<log message>"<< LOG4CXX_ENDMSG;

或者偷懒的办法:

LoggerPtr Perf_logger (Logger:: getRootLogger ());
log4cxx::logstream logstream(Perf_logger, Level::getInfo());
logstream << Level::getTrace() << "<log message>"<< LOG4CXX_ENDMSG;

偷懒时使用的logger对应xml文件的root节点;使用默认日志输出的坏处在于无法通过修改配置文件来隔离日志输出了;比如软件系统有A,B,C三个模块都使用getRootLogger得到的logger进行日志输出;那么在发现问题的时候就只能在同一个日志文件里去分析了,这个文件是包含了三个模块的所有日志输出,不能进行有效的日志隔离;如果我们在配置文件为我们的模块准备了相应的logger,并在代码里按logger名指定输出;那么需要时我们可以通过设置相应的appender让各个模块输出到不同的日志文件以便于进行单独的分析;如果软件运行稳定,我们也可以让各个logger都输出到同一个默认的appender,避免产生太多临时文件.

前文提到日志可以输出到数据库或者自动发送到指定的email,这需要将log4cxx与相应的邮件发送库和数据库的驱动一起编译,我未作尝试,就不多说.

在代码里输出日志信息

为了在代码里输出信息,首先应该用配置文件初始化日志库,这需要调用log4cxx的

log4cxx::xml::DOMConfigurator::configure(config);

接口,指定xml配置文件的路径即可.需要注意的是,由多个模块构建的应用程序,初始化一次即可.

如果代码里写了输出日志却没有正常初始化日志库,那么就会出现这样一个错误;这个错误导致所有与该logger相关的日志信息无法输出,但不会影响程序运行;

在准备好了配置文件并正确初始化日志库后,可以在代码里输出任意想输出的信息了,一般输出的步骤是:获取对应模块的logger,然后使用log4cxx对应的四个宏对该logger进行日志输出:

LoggerPtr logger = Logger::getLogger("<logger-name>");
LOG4CXX_INFO(logger, ("<info-message>"));
LOG4CXX_DEBUG(logger, "<DEBUG-message>");
LOG4CXX_WARN(logger, "<WARN-message>");
LOG4CXX_ERROR(logger, "<ERROR-message>");

我个人常使用另一个方式输出日志,获得logger之后,用该logger初始化logstream,然后使用logstream来输出日志信息,效果是一样的:

log4cxx::logstream logstream(logger, Level::getInfo());
logstream << Level::getTrace() << "<trace-message>"<< LOG4CXX_ENDMSG;

前文提到如果软件系统由多个模块构成,那么只需要初始化一次;所以日志初始化的过程不应该在模块里完成,而应该由调用模块的主程序来完成日志的初始化;那么暴露一个初始化的接口给主程序主用就是很有必要的,dll库模块可能给c调用,也可能给其他语言如c#调用,所以我是会把原始的接口藏起来做一个c的接口来完成日志初始化,如后文里的loginit所示,更具体点说是我把这个接口和log4cxx库编译在一起了,从库里导出一个初始化的接口,其他接口维持不变即可(如果用dependency walk查看log4cxx库会发现所有接口都是c++的导出接口).

其他问题

日志的级别如何定? 应该在什么地方输出日志?

这个问题只能依靠个人经验了,遇到的bug多了就自然会知道;一般的做法就是函数入口和退出的地方都打日志,函数的参数和返回值可以记日志,如果发生崩溃能大致定位出在哪个函数里出的问题,通过分析函数输入参数值和代码逻辑应该能对定位出bug起到较大的帮助.

Log4cxx库在哪里? 怎么编译?

出门右拐找google/baidu/bing都可以

// 我编译到log4cxx里的日志初始化代码// loginit.h
#ifndef _zstang_loginit_h_
#define _zstang_loginit_h_

#ifdef _WIN32
#ifdef LOG_EXPORT
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif
#else
#define DLL_EXPORT
#endif

#define CALL_CONVENTION _cdecl

#ifdef __cplusplus
extern "C"{
#endif
///<--------------------------------------------------------------->
///< 日志初始化接口,参数为log4cxx日志库配置文件 log4cxx.xml的路径 >
///<--------------------------------------------------------------->
void DLL_EXPORT CALL_CONVENTION init_log(char* config_path);

#ifdef __cplusplus
};
#endif

#endif

// loginit.cpp
#include "logInit.h"
#include <string>
using namespace std;

#include "../src/main/include/log4cxx/xml/domconfigurator.h"

void DLL_EXPORT CALL_CONVENTION init_log(char* config_path)
{
    string config=string(config_path);
    log4cxx::xml::DOMConfigurator::configure(config);
}
时间: 2024-12-25 14:49:40

Log4cxx日志库的使用的相关文章

log4cxx日志库RedHat下安装

今天领导交给我一个任务:把log4cxx库在Redhat系统上面安装起来 首先,我得到信息,安装这个库一共需要三个软件 apr-1.4.6.tar.gz apr-util-1.4.1.tar.gz apache-log4cxx-0.10.0.tar.gz 安装顺序是从上到下的,为什么,因为后者的安装依赖前者. 我整理了一下思路,有如下几个问题: 1.安装log4cxx库到哪个目录? 2.安装好之后怎么用? 我首先想到的是网上查资料,结果很多结果都显示: 1.针对问题1,99%的都安装在/usr/

Python日志库的用法

日志不管对于开发或者运维都是一项非常重要的东西,它可以用来排错,解决故障,统计分析等. 本文介绍python中的日志库的用法. 日志库:import logging 要用日志需要先定义以下东西: 获取日志名,比如 logging.getLogger(__name__) 定义Handler,比如 logging.FileHandler('/var/log/messages') 设置级别,比如 fh.setLevel(logging.DEBUG) 定义格式,比如 formatter = loggin

爆料喽!!!开源日志库Logger的使用秘籍

导读 日志对于开发来说是非常重要的,不管是调试数据查看.bug问题追踪定位.数据信息收集统计,日常工作运行维护等等,都大量的使用到.今天介绍著名开源日志库Logger的使用,库的地址:https://github.com/orhanobut/logger 在Android Studio中的gradle中加入,就可以引用依赖logger库: dependencies { compile 'com.orhanobut:logger:1.15' } Logger库能提供的功能: 线程的信息 类的信息

C++的开源跨平台日志库glog学习研究(一)

作为C++领域中为数不多的好用.高效的.跨平台的日志工具,Google的开源日志库glog也算是凤毛麟角了.glog 是一个C++实现的应用级日志记录框架,提供了C++风格的流操作. 恰巧趁着五一我也学习研究了这个glog库,写个总结如下.走过路过的的各位牛人.高手可以忽略这篇文章了. 从code.google.com下载源码(在这里),在Visual Studio 2010中打开工程,如下: 可见只有四个工程,其中libglog和libglog_static分别是Windows下的动态库和静态

C++的开源跨平台日志库glog学习研究(二)--宏的使用

上一篇从整个工程上简单分析了glog,请看C++的开源跨平台日志库glog学习研究(一),这一篇对glog的实现代码入手,比如在其源码中以宏的使用最为广泛,接下来就先对各种宏的使用做一简单分析. 1. 日志输出宏 这里我们以一条最简单的日至输出为例说明: LOG(WARNING) << "This is a warning message"; 这里LOG是一个宏,其定义如下(logging.h line 487): #define LOG(severity) COMPACT

Android 开源日志库 Logger 使用教程

转载请注明出处: http://blog.csdn.net/like_program/article/details/52986553 1.Logger 是什么 在我们日常的开发中,肯定是少不了要和 Log 打交道,回想一下我们是怎么使用 Log 的:先定义一个静态常量 TAG,TAG 的值通常是当前类的类名,然后在需要打印 Log 的地方,调用 Log.d(TAG, "要打印的内容").每次新写一个类,都要写一个 TAG,这也就算了,最苦逼的是,项目一上线,还要手动去把每个 Log

POCO日志库使用示例

版权所有,转载时请注明作者和出处 http://blog.csdn.net/jmppok/article/details/25598483 Poco是一个开源的C++库,各方面功能比较全面,包括日志\多线程\文件系统\定时器\网络\配之文件等,同时使用也十分简单. 本文对其中的日志模块进行了简单的试用. 1.使用示例 Poco::AutoPtr<Poco::Util::PropertyFileConfiguration> pConf = new Poco::Util::PropertyFile

C++的开源跨平台日志库glog学习研究(三)--杂项

在前面对glog分别做了两次学习,请看C++的开源跨平台日志库glog学习研究(一).C++的开源跨平台日志库glog学习研究(二)--宏的使用,这篇再做个扫尾工作,算是基本完成了. 编译期断言 动态断言在调试过程中是一个很重要的手段,而且我们使用的也比较多.相应的,静态断言,也即是编译期断言随着模板编程.元编程的发展,也表现出了动态断言所没有的优势:在编译期完成断言检查,而不是等到运行时! 比如在glog的源码中,有如下代码(logging.h line 908): 1 template <b

C基础 多用户分级日志库 sclog

引言 - sclog 总的设计思路 sclog在之前已经内置到simplec 简易c开发框架中一个日志库. 最近对其重新设计了一下. 减少了对外暴露的接口. 也是C开发中一个轮子. 比较简单, 非常适合学习理解,最后自己写一个自己喜欢的日志库. 首先分析分级设计的总的思路. 主要是围绕上面思路设计. 分6个等级. 2中类型的日志文件. sc.log 普通文件, 什么信息都接受, sc.log.wf只接受异常信息. 需要紧急处理的. 继续说明日志消息体的设计思路 到这里设计的总思路已经清楚了. 后