项目日志
stdin/stdout/stderr
在通常情况下,Linux/UNIX每个程序在开始运行的时刻,都会打开3个已经打开的stream. 分别用来输入,输出,打印诊断和错误信息。通常他们会被连接到用户终端。这3个句柄的类型为指向FILE的指针。可以被fprintf、fread等函数使用,他们在程序开始启动后,stdin, stdout, and stderr 的文件描述符是 0, 1和2,其它的文件描述符则排在其后。
Linux的本质就是一切皆文件,输入输出设备也是以文件形式存在和管理的。
注意:stderr是不缓存的,stdout则进行行间缓存。接下来我们看下行间缓存的效果,请参考以下代码:
#include "stdio.h"
#include <unistd.h>
int main(int argc, char** argv)
{
for(int i = 0; i < 5; i++)
{
fprintf(stdout, "This is stdout[%d]", i);
sleep(1);
//5秒后,全部直接打印
}
sleep(1);
for(int i = 0; i < 5; i++)
{
fprintf(stderr, "This is stderr[%d]", i);
sleep(1);
//每隔一秒打印一次
}
return 0;
}
加上换行符后:
#include "stdio.h"
#include <unistd.h>
int main(int argc, char** argv)
{
for(int i = 0; i < 5; i++)
{
fprintf(stdout, "This is stdout[%d]\n", i);
sleep(1);
}
sleep(1);
for(int i = 0; i < 5; i++)
{
fprintf(stderr, "This is stderr[%d]\n", i);
sleep(1);
}
return 0;
}
上面两端代码就是在打印的时候加上了"\n"而已,但是效果完全不一样。
思考:很多时候我们会用printf打印信息来调试程序,但是如果终端关掉了,那怎么显示printf的调试信息呢?
重定向
将日志不写控制台,写入文件中
#include <stdio.h>
int main(int argc, char** argv)
{
printf("welcome to qiniu!\n");
fprintf(stdout, "I am martin!\n");
perror("are you all ready?\n");
fprintf(stderr, "Martin always stay with you!\n");
return 0;
}
编译好后(gcc -o test test.c),我们试试下面不同的运行方式: ./test > test.txt
标准输出重定向到文件
./test 1 > testout.txt ./test 2 > testerr.txt
标准输出和标准出错重定向到文件
./test > test.txt 2>&1
当然除了使用“>”重定向外,我们还可以试下“>>”,>>是以附加的方式(尾部追加)重定向到文件中,另外我们还可以在代码中实现重定向,
比如:
#include <stdio.h>
int main(void)
{
FILE *out = freopen("stdout.txt", "w", stdout);
printf("%s\n", "hello verybody!!!");
return 0;
}
总的来说,stdin,stdout和stderr还是和终端有密切关系,通常在生产环境时,会将这3个流重定向到其它文件。
如果我们实在要用printf或者fprintf去生成日志的话,最好还是加上这些信息
__FILE__ __LINE__ __FUNCTION__, __DATE__, __TIME__
当然我们一定要明白,printf设计到文件,这会引起IO中断,因此执行printf比一般的指令的效率要低很多。
Log4cpp
在上文,我们使用重定向去输入日志到日志文件中,但是这样一种处理方式,还是有很多不足,比如没有办法输出到多个文件,没有办法设置日志文件大小,不太方便设置日志的级别,如果想远程输出到日志服务器怎么办呢?等等这些问题怎么解决呢?
log4cpp就提供了很多的功能,帮助我们应用程序更方便地记录日志,OK,我们先来看一个简单的实现吧:
step 1 : 安装log4cpp
log4cpp的官网是:http://log4cpp.sourceforge.net/
wget https://nchc.dl.sourceforge.net/project/log4cpp/log4cpp-1.1.x%20%28new%29/log4cpp-1.1/log4cpp-1.1.3.tar.gz
tar xzvf log4cpp-1.1.3.tar.gz
cd log4cpp-1.1.3
./configure --prefix=自己想安装到的 绝对路径/home/.../...
make
make install
step 2 : 包含头文件
#include <log4cpp/Category.hh>
#include <log4cpp/FileAppender.hh>
#include <log4cpp/PatternLayout.hh>
#include <log4cpp/OstreamAppender.hh>
step 3 : 初始化日志输出的目的地(appenders)
// 输出到std::cout "root" : 用户身份
log4cpp::Appender *appender = new log4cpp::OstreamAppender("root", &std::cout);
// 输出到log文件
//log4cpp::Appender *appender = new log4cpp::FileAppender("root", "test.log");
appender有以下这些:
log4cpp::FileAppender // 输出到文件
log4cpp::RollingFileAppender // 输出到回卷文件,即当文件到达某个大小后回卷
log4cpp::OstreamAppender // 输出到一个ostream类
log4cpp::RemoteSyslogAppender // 输出到远程syslog服务器
log4cpp::StringQueueAppender // 内存队列
log4cpp::SyslogAppender // 本地syslog
log4cpp::Win32DebugAppender // 发送到缺省系统调试器
log4cpp::NTEventLogAppender // 发送到win 事件日志
上文,我们说过日志输出到终端或者文件中实际上是很慢的,会引起IO中断,所以我们可以输出到内存里StringQueueAppender,然后从StringQueueAppender输出到其它地方,这样我们的线程执行是比较高效的。
step 4 : 设置日志输出的格式
log4cpp::PatternLayout *patternLayout = new log4cpp::PatternLayout();
patternLayout->setConversionPattern("%d [%p] - %m%n");
appender->setLayout(patternLayout);
日志输出格式控制有:
PatternLayout supports following set of format characters:
%% - a single percent sign //转义 输出一个百分号
%c - the category //日志类别 区分不同的模块
%d - the date\n Date format: The date format character may be followed by a date format specifier enclosed between braces. For example, %d{%\H:%M:%S,%l} or %d{%\d %m %Y %H:%\M:%S,%l}. If no date format specifier is given then the following format is used: "Wed Jan 02 02:03:55 1980". The date format specifier admits the same syntax as the ANSI C function strftime, with 1 addition. The addition is the specifier %l for milliseconds, padded with zeros to make 3 digits.
//时间 (时分秒)
%m - the message //消息
%n - the platform specific line separator //换行(平台特定的线分隔符)
%p - the priority //优先级
%r - milliseconds since this layout was created. //该布局创建以来的毫秒数。
%R - seconds since Jan 1, 1970 //时间戳
%u - clock ticks since process start //自进程启动以来,消耗的时钟
%x - the NDC
%t - thread name //线程名
By default, ConversionPattern for PatternLayout is set to "%m%n". //默认
step 4 : 设置类别输出的(category)和日志优先级(priority)
#类别设置在文件中,从配置文件中读
log4cpp::Category &root = log4cpp::Category::getRoot();
root.setPriority(log4cpp::Priority::NOTICE); #优先级
root.addAppender(appender);
日志的级别总共有: NOTSET < DEBUG < INFO < NOTICE < WARN < ERROR < CRIT < ALERT < FATAL = EMERG。日志级别的意思是低于该级别的日志不会被记录。
step 5 : 定义一个宏
#define LOG(__level) log4cpp::Category::getRoot() << \
log4cpp::Priority::__level << __FILE__ << " " << __LINE__ << ": "
当然也可以使用Category定义的函数:
/**
* Log a message with the specified priority.
* @param priority The priority of this log message.
* @param stringFormat Format specifier for the string to write
* in the log file.
* @param ... The arguments for stringFormat
**/
virtual void log(Priority::Value priority, const char* stringFormat,
...) throw();
/**
* Log a message with the specified priority.
* @param priority The priority of this log message.
* @param message string to write in the log file
**/
virtual void log(Priority::Value priority,
const std::string& message) throw();
void debug(const char* stringFormat, ...) throw();
void debug(const std::string& message) throw();
void info(const char* stringFormat, ...) throw();
...
step 6 : 使用宏定义记录日志
LOG(DEBUG) << "i am happy.";
LOG(INFO) << "oh, you happy, we happy.";
LOG(NOTICE)<< "please do not contact me. ";
LOG(WARN) << "i am very busy now.";
LOG(ERROR) << "oh, what happed?";
当然我们在使用过程中,可以封装一个单例
在实际工程上应用,我们是使用日志配置文件去控制日志记录的。
工程应用:
以下是个人的一个项目中使用的log4cpp示例:
log.conf
#定义Root category的属性
log4cpp.rootCategory=DEBUG, RootLog #级别大于等于DEBUG的都会输出
#定义RootLog属性
log4cpp.appender.RootLog=RollingFileAppender #滚动输出
log4cpp.appender.RootLog.layout=PatternLayout
#log4cpp.appender.RootLog.layout.ConversionPattern=%d{% m-%d %H:%M:%S %l} [%t][%p]%m%n
log4cpp.appender.RootLog.layout.ConversionPattern=%d{\%\m-%d %H:%M:%S %l} [%t][%p]%m%n
log4cpp.appender.RootLog.fileName=/var/log/shared_bike.log #输出的文件名
log4cpp.appender.RootLog.maxFileSize=268435456 #256MB
log4cpp.appender.RootLog.fileNamePattern=shared_bike%i.log
log4cpp.appender.RootLog.maxBackupIndex=256 #大小
Logger.h
#ifndef __LOGGER_H__
#define __LOGGER_H__
#include <string>
#include <log4cpp/Category.hh>
//单例
class Logger
{
public:
bool init(const std::string& log_conf_file);
static Logger* instance()
{
return &instance_;
}
log4cpp::Category* GetHadle() //获取
{
return category_;
}
protected:
static Logger instance_;
log4cpp::Category* category_;
};
#define LOG_INFO Logger::instance()->GetHadle()->info
#define LOG_DEBUG Logger::instance()->GetHadle()->debug
#define LOG_ERROR Logger::instance()->GetHadle()->error
#define LOG_WARN Logger::instance()->GetHadle()->warn
#endif
Logger.cpp
#include "Logger.h"
#include <iostream>
#include <log4cpp/FileAppender.hh>
#include <log4cpp/OstreamAppender.hh>
#include <log4cpp/PatternLayout.hh>
#include <log4cpp/RemoteSyslogAppender.hh>
#include <log4cpp/PropertyConfigurator.hh>
Logger Logger::instance_;
bool Logger::init(const std::string& log_conf_file)
{
try
{
log4cpp::PropertyConfigurator::configure(log_conf_file);
}
catch(log4cpp::ConfigureFailure& f)
{
std::cerr << "load log config file" << log_conf_file.c_str() \
<< "failed with result : " << f.what() << std::endl;
return false;
}
category_ = &log4cpp::Category::getRoot();
return true;
}
src/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
#工程名
PROJECT(shared_bike)
#将指定目录添加到编译器的头文件搜索路径之下
INCLUDE_DIRECTORIES(../third/include)
INCLUDE_DIRECTORIES(./common)
#将指定目录添加到需要链接的库文件目录之下
LINK_DIRECTORIES(../third/lib/iniparser)
LINK_DIRECTORIES(../third/lib/log4cpp)
LINK_DIRECTORIES(./common)
#搜集所有在指定路径下的源文件, 将输出结果列表储存在指定的变量中
#内置变量: CMAKE_SOURCE_DIR 定义了顶级CMakeLists.txt 所在的文件夹, PROJECT_SOURCE_DIR 定义了包含最近的project()命令的CMakeLists.txt 所在的文件夹
aux_source_directory(${PROJECT_SOURCE_DIR} SOURCE_FILES)
#使用给定的源文件,为工程引入一个可执行文件
ADD_EXECUTABLE(shared_bike ${SOURCE_FILES})
SET(CMAKE_CXX_FIAGS "${CMAKE_CXX_FIAGS} -rdynamic -Wall -m64 -pipe -std=c++0x -lrt -Wno-reorder -Wdeprecated-declarations")
TARGET_LINK_LIBRARIES(shared_bike iniparser)
#TARGET_LINK_LIBRARIES(shared_bike log4cpp)
target_link_libraries(shared_bike liblog4cpp.a)
TARGET_LINK_LIBRARIES(shared_bike pthread)
TARGET_LINK_LIBRARIES(shared_bike common)
#增加子目录
ADD_SUBDIRECTORY(common)
SET(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR})
#安装
INSTALL(TARGETS shared_bike DESTINATION bin)
src/common/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
#搜集所有在指定路径下的源文件, 将输出结果列表储存在指定的变量中
aux_source_directory(. SOURCE_COMMON_FILES)
#add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
#(自定义的库名 ${由这些文件生成})
#构建库供他人模块使用
ADD_LIBRARY(common ${SOURCE_COMMON_FILES})
#用来显示的定义变量
SET(CMAKE_CXX_FIAGS "${CMAKE_CXX_FIAGS} -rdynamic -Wall -m64 -pipe -std=c++0x -lrt -Wno-reorder -Wdeprecated-declarations")
#将指定目录添加到编译器的头文件搜索路径之下
INCLUDE_DIRECTORIES(../../third/include)
#将指定目录添加到需要链接的库文件目录之下
LINK_DIRECTORIES(../../third/lib/iniparser)
LINK_DIRECTORIES(../../third/lib/log4cpp)
#将目标文件与库文件进行链接
TARGET_LINK_LIBRARIES(common iniparser)
TARGET_LINK_LIBRARIES(common log4cpp)
TARGET_LINK_LIBRARIES(common pthread)
TARGET_LINK_LIBRARIES(common dl)
输出对照:
时/分/秒/毫秒 [线程名] [优先级]
这里可以自定义: