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);
}
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)
log4cpp::Appender *appender = new log4cpp::OstreamAppender("root", &std::cout);
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定义的函数:
virtual void log(Priority::Value priority, const char* stringFormat,
...) throw();
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
log4cpp.rootCategory=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.fileName=/var/log/shared_bike.log
log4cpp.appender.RootLog.maxFileSize=268435456
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)
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 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(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)
输出对照:
时/分/秒/毫秒 [线程名] [优先级]
这里可以自定义:
|