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程序,就是在处理每次请求前检测。