stderr 和 rotate log

日志是程序的基本功能。有开源的log4j 到 c++的 log4cpp。 很多公司的项目也有自己的日志库。

log4cpp 这类日志库,实现了分离的线程和自己的缓存,这样可以最大化的减小 写日志模块被阻塞,这在一些不能阻塞的场景下是有意义。

但是我们的应用很少需要这么高的要求,只要能打印日志,并控制大小就可以。

所以我先实现了个轮转的日志。 就是打印满一个文件,就换新文件,并给老文件编号. 比如  my.log; my.log.1 ;      my.log.2

rotate_log.hpp

#ifndef ROTATE_LOG_INC
#define ROTATE_LOG_INC
#include
<sys/types.h>
#include
<unistd.h>
#include
<stdio.h>
#include
<sstream>
#include
<sys/types.h>
#include
<sys/stat.h>
#include
<fcntl.h>


class RotateLog {
public:
enum {
DEBUG
=0,
INFO,
WARN,
ERROR,
ALERT,
};

~RotateLog() {
RotateLog::close();
}

int init(char const* basename, int file_size, int file_num, int level = RotateLog::DEBUG) {
basename_
= basename;
file_size_
= file_size;
file_num_
= file_num;
lock_file_
= basename;
lock_file_
+=".lock";
level_
= level;
return reopen();
}

int set_level(int level) {
assert(level
>0);
level_
= level;
}

int get_level() const {
return level_;
}

int close() {
if (file_) {
fclose(file_);
file_
=NULL;
}
return 0;
}

int reopen() {
this->close();
FILE
*file = fopen(basename_, "a");
if (file == NULL) {
return -__LINE__;
}
setvbuf(file, NULL, _IONBF,
0);
file_
= file;
return 0;
}

bool is_too_big() {
struct stat st;

int ret = stat(basename_, &st);
if (ret) {
return true;
}

off_t len
= st.st_size;

if (len >= file_size_) {
return true;
}
return false;
}


int check() {
if (is_too_big()) {
manage_log();
reopen();
}
return 0;
}

int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len) {
if(fd < 0) {
return -1;
}
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
return fcntl(fd, cmd, &lock);
}

int manage_log() {
int fd = open(lock_file_.c_str(), O_CREAT|O_WRONLY, 0664);
if (fd<0) {
shift_file(basename_, file_num_);
return 0;
}

if (lock_reg(fd, F_SETLK, F_WRLCK, 0, SEEK_SET, 0) >= 0) {
shift_file(basename_, file_num_);
lock_reg(fd, F_SETLK, F_UNLCK,
0, SEEK_SET, 0);
}
return 0;
}



int shift_file(char const *basename, int num) {
char log_file[1024];
snprintf(log_file,
sizeof(log_file)-1, "%s.%d", basename, num-1);
int ret = 0;
if (remove(log_file) < 0 ) {
ret
= -__LINE__;
}
char log_file_old[1024];
char *src=log_file_old;
char *dst=log_file;
char *tmp = NULL;
for (int i = num - 2; i >= 1; i--) {
snprintf(src,
sizeof(log_file) -1, "%s.%d", basename, i);
if (rename(src,dst) < 0 ) {
ret
= -__LINE__-i*1000000;
}
tmp
= dst;
dst
= src;
src
= tmp;
}
rename(basename, dst);
return ret;
}

int file_num_;
char const * basename_;
int file_size_;
FILE
* file_;
std::
string lock_file_;
int level_;
};

#define ROTATE_LOG(log, level, format, ...) \
do {\
if(RotateLog::level>=log.level_ ) { \
log.check(); \
fprintf(log.file_,
"[%s][%s][%d]: " format "\n", \
#level, __FILE__,
int(__LINE__), ##__VA_ARGS__);\
}\
}
while(0)

标准错误日志

    换个思路, unix程序有标准的输入,输出和错误输出。 其实错误输出可以当成是日志的功能。所以我实现了个标准错误输出的日志库

   stderr_log.hpp

#ifndef STDERR_LOG_INC
#define STDERR_LOG_INC
#include
<stdio.h>
#include
<unistd.h>

namespace stderr_log {

enum {
DEBUG
=0,
INFO,
WARN,
ERROR,
ALERT
};

int __attribute__((weak)) g_log_level = 0 ;
int __attribute__((weak)) g_log_pid = getpid();

inline
void set_level(int level) {
g_log_level
= level;
}

inline
int get_level() {
return g_log_level;
}


inline
void set_pid(pid_t pid) {
g_log_pid
= pid;
}

#define STDERR_LOG(level, format, ...) \
do {\
if(stderr_log::level>=stderr_log::g_log_level) { \
fprintf(stderr,
"[%s][%d][%s][%d]: " format "\n",\
               #level, stderr_log::g_log_pid, \
               __FILE__, int(__LINE__), ##__VA_ARGS__);\
}\
}
while(0)

}

#endif

为了把标准错误输出重定向到文件,并轮转,我实现了轮转库

stderr_log.rotate.hpp

#ifndef STDERR_LOG_ROTATE_INC
#define STDERR_LOG_ROTATE_INC
#include
"stderr_log.hpp"
#include
<sys/types.h>
#include
<unistd.h>
#include
<stdio.h>
#include
<sstream>
#include
<sys/types.h>
#include
<sys/stat.h>
#include
<fcntl.h>

namespace stderr_log_rotate {
char const * basename_;
int file_size_;
int log_fd_;
int file_num_;
std::
string lock_file_;

int reopen() {
int fd = open(basename_, O_CREAT|O_APPEND|O_WRONLY, 0664);
if (fd < 0) {
return -__LINE__;
}
if (log_fd_ != dup2(fd, log_fd_)) {
return -__LINE__;
}
return 0;
}

int init( char const* basename, int file_size, int file_num) {
basename_
= basename;
file_size_
= file_size;
log_fd_
= 2;
file_num_
= file_num;
lock_file_
= basename_;
lock_file_
+=".lock";
return reopen();
}


bool is_too_big() {
off_t len
= lseek(log_fd_, 0, SEEK_END);
if (len < 0) {
return false;
}

if (len >= file_size_) {
return true;
}
return false;
}

int shift_file(char const *basename, int num) {
char log_file[1024];
snprintf(log_file,
sizeof(log_file)-1, "%s.%d", basename, num-1);
int ret = 0;
if (remove(log_file) < 0 ) {
ret
= -__LINE__;
}

char log_file_old[1024];
char *src=log_file_old;
char *dst=log_file;
char *tmp = NULL;
for (int i = num - 2; i >= 1; i--) {
snprintf(src,
sizeof(log_file) -1, "%s.%d", basename, i);
if (rename(src,dst) < 0 ) {
ret
= -__LINE__-i*1000000;
}
tmp
= dst;
dst
= src;
src
= tmp;
}
rename(basename, dst);
return ret;
}

int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len) {
if(fd < 0) {
return -1;
}
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
return fcntl(fd, cmd, &lock);
}

int manage_log() {
int fd = open(lock_file_.c_str(), O_CREAT|O_WRONLY, 0664);
if (fd<0) {
shift_file(basename_, file_num_);
}

if (lock_reg(fd, F_SETLK, F_WRLCK, 0, SEEK_SET, 0) >= 0) {
shift_file(basename_, file_num_);
lock_reg(fd, F_SETLK, F_UNLCK,
0, SEEK_SET, 0);
}
return 0;
}

int check() {
if (is_too_big()) {
manage_log();
reopen();
return 1;
}
return 0;
}

}

#endif /* ----- #ifndef STDERR_LOG_ROTATE_INC ----- */

程序什么时候检测日志是否需要轮转,这依赖于具体的应用,所以要坚持一个原则, 策略于机制分离。 像我的cgi程序,就是在处理每次请求前检测。

posted @ 2011-05-13 14:29  napoleon_liu  阅读(825)  评论(0编辑  收藏  举报