在实际场景中,我们往往需要定期清理日志文件。最近我在调试过程中,需要打印的日志比较多,甚至导致每调试完几次就要动手清理一次日志文件,这让人很不爽。于是上网搜有没有相关工具可以辅助我清理日志文件,然而找到的都是要币的,而我没有... ...
于是只好自己动手写了个日志文件清理工具了。
本工具使用inotify监控指定路径,用timerfd进行延时,使用用epoll驱动事件,没有任何主动轮询操作,故效率极高,占用cpu极低。封装到了类里,可集成至项目中。
支持动态添加指定路径,不支持删除指定路径
想学习这三者的也可以看看下面的代码。
本代码免费,拿完别忘了回来点个赞~
FileCleaner.h
//
// 一个定期删除制定路径下文件的工具
// 文件在创建后的指定时间后会被删除(暂不支持文件夹)
// 若指定的路径下的文件读写需要管理员权限,使用管理员权限执行。
// Created by lijiahao on 19-6-26.
// email: 1281253728@qq.com
//
#ifndef FILECLEANER_FILECLEANER_H
#define FILECLEANER_FILECLEANER_H
#include <iostream>
#include <stdio.h>
#include <errno.h>
#include <sys/inotify.h>
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#include <string.h>
#include <unistd.h>
#include <map>
#include <sys/epoll.h>
#include <vector>
#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
struct myevent_s {
int fd;
int event_type;
char buf[256];
long delay_seconds;
int wd;
};
class FileCleaner {
public:
FileCleaner();
~FileCleaner();
bool AddWatchBySeconds(const char* path, int seconds);
bool AddWatchByMinutes(const char* path, int minutes);
bool AddWatchByHours(const char* path, int minutes);
bool AddWatchByDays(const char* path, int minutes);
protected:
bool path_check(const char *path);
static void *WatchThread(void *arg);
void handle_read(myevent_s* event);
private:
int wakeup_fd;
int close_fd;
int epoll_fd;
myevent_s* wakeup_event;
std::vector<myevent_s*> watch_events;
bool m_bExit;
};
#endif //FILECLEANER_FILECLEANER_H
FileCleaner.cpp
//
// Created by lijiahao on 19-6-26.
// email: 1281253728@qq.com
//
#include "FileCleaner.h"
#include <pthread.h>
using namespace std;
#define WAKEUP 2
#define TIMER_EVENT 10
#define INOTIFY_EVENT 100
FileCleaner::FileCleaner() {
m_bExit = false;
//初始化
wakeup_fd = eventfd(0, EFD_NONBLOCK);//用来唤醒epoll
if(wakeup_fd == -1){
printf("create fd failed\n");
exit(-1);
}
close_fd = eventfd(0, EFD_NONBLOCK);//用来结束对象
if(close_fd == -1){
printf("create fd failed\n");
exit(-1);
}
epoll_fd = epoll_create1(0); //创建epoll实例
if (epoll_fd == -1) {
printf("create epoll fail \r\n");
close(epoll_fd);
close(wakeup_fd);
exit(-1);
}
wakeup_event = new myevent_s;
memset(wakeup_event, 0, sizeof(myevent_s));
wakeup_event->fd = wakeup_fd;
wakeup_event->event_type = WAKEUP;
struct epoll_event ev = {0};
ev.events = EPOLLIN;
ev.data.ptr = wakeup_event;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, wakeup_fd, &ev); //添加到epoll监听队列中
pthread_t th;
pthread_create(&th, NULL, WatchThread, this);
}
FileCleaner::~FileCleaner() {
m_bExit = true;
uint64_t umsg = WAKEUP;
int nret = write(wakeup_fd, &umsg, sizeof(uint64_t));
umsg = 8;
//usleep(100);//如果析构异常,尝试添加这句
myevent_s *myeventS;
while(!watch_events.empty()){
myeventS = watch_events.back();
inotify_rm_watch(myeventS->fd, myeventS->wd);
close(myeventS->fd);
delete(myeventS);
watch_events.pop_back();
}
read(close_fd, &umsg, sizeof(uint64_t));
printf("File Cleaner exit: %d", umsg);
close(wakeup_fd);
close(epoll_fd);
close(close_fd);
}
bool FileCleaner::AddWatchBySeconds(const char *path, int seconds) {
if(!path_check(path)) return false;
int watch_fd = inotify_init();
if ( watch_fd < 0 ) {
perror( "inotify_init" );
return false;
}
int wd = inotify_add_watch( watch_fd, path, IN_CREATE | IN_DELETE );
myevent_s *watch_event = new myevent_s;
watch_events.push_back(watch_event);
memset(watch_event, 0, sizeof(watch_event));
watch_event->fd = watch_fd;
watch_event->wd = wd;
watch_event->delay_seconds = seconds;
watch_event->event_type = INOTIFY_EVENT;
memcpy(watch_event->buf, path, strlen(path));
struct epoll_event ev = {0};
ev.events = EPOLLIN; //监听inotify读事件,描述符可读。
ev.data.ptr=watch_event;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, watch_fd, &ev); //添加到epoll监听队列中
uint64_t umsg = 1;
int nret = write(wakeup_fd, &umsg, sizeof(uint64_t));
if (nret!= sizeof(uint64_t))
{
printf("write failed\n");
return -1;
}
return true;
}
bool FileCleaner::AddWatchByMinutes(const char *path, int minutes) {
return AddWatchBySeconds(path, minutes * 60);
}
bool FileCleaner::AddWatchByHours(const char *path, int hours) {
return AddWatchByMinutes(path, hours * 60);
}
bool FileCleaner::AddWatchByDays(const char *path, int days) {
return AddWatchByHours(path, days * 24);
}
void *FileCleaner::WatchThread(void *arg) {
FileCleaner *pThis = (FileCleaner*)arg;
int ret;
int nready;
uint64_t value;
myevent_s *event;
struct epoll_event *evptr = (struct epoll_event *)calloc(1024, sizeof(struct epoll_event));
if (evptr == NULL) {
printf("epoll event calloc fail\n");
delete(pThis);
//return NULL;
}
while(!pThis->m_bExit)
{
nready = epoll_wait(pThis->epoll_fd, evptr, 1, -1); //阻塞监听,直到有事件发生
if(nready == -1)
{
if(errno != EINTR) {
perror("error epoll !");
delete(pThis);
break;
}
//printf("Interrupted system call\n");
continue;
}else if(nready == 0){
printf("timeout!\n");
continue;
}
int i = 0;
for(; i < nready; ++i){
if(evptr[i].events & EPOLLIN){
event = (myevent_s*)(evptr[i].data.ptr);
switch (event->event_type){//如果是唤醒信号,则读,并continue
case WAKEUP:
printf("detect WAKEUP signal\n");
ret = read(event->fd, &value, sizeof(uint64_t));
if (sizeof(uint64_t) != ret) {
perror("read error");
break;
}
break;
case TIMER_EVENT://如果定时器到时
printf("detect TIMER_EVENT signal\n");
ret = read(event->fd, &value, sizeof(uint64_t));
close(event->fd);
if (sizeof(uint64_t) != ret) {
perror("read error");
break;
}
if( remove(event->buf) == 0 )
printf("remove:[%s] success!\n", event->buf);
else{
printf("remove: [%s] failed\n", event->buf);
perror("failed to remove the file");
}
delete event;
break;
case INOTIFY_EVENT://如果是inotify事件
printf("detect INOTIFY_EVENT signal\n");
pThis->handle_read((myevent_s*)evptr->data.ptr);
break;
}
}
}
}
uint64_t umsg = 666666;
write(pThis->close_fd, &umsg, sizeof(uint64_t));
printf("WorkThread Exit\n");
}
void FileCleaner::handle_read(myevent_s* event)
{
char buffer[BUF_LEN] = { 0 };
int fd = event->fd;
char *file_path = event->buf;
int check_seconds = event->delay_seconds;
int length = read( fd, buffer, BUF_LEN );
if ( length < 0 ) {
perror( "read" );
}
int i = 0;
time_t now;
time (&now);
while ( i < length ) {
struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
if ( event->len ) {
if ( event->mask & IN_CREATE ) {
if ( event->mask & IN_ISDIR ) {
printf( "Directory [ %s/%s ] was created.\n", file_path, event->name );
}
else {
printf( "File [ %s/%s ] was created.\n", file_path, event->name );
struct itimerspec time_intv = {0}; //用来存储时间
time_intv.it_value.tv_sec = check_seconds; //设定2s超时
time_intv.it_value.tv_nsec = 0;
int tfd = timerfd_create(CLOCK_MONOTONIC, 0); //创建定时器
if(tfd == -1) {
printf("create timer fd fail \r\n");
return ;
}
timerfd_settime(tfd, 0, &time_intv, NULL);//启动定时器
myevent_s *timer_event = new myevent_s;
memset(timer_event, 0, sizeof(timer_event));
timer_event->fd = tfd;
timer_event->event_type = TIMER_EVENT;
sprintf(timer_event->buf, "%s/%s", file_path, event->name);
//memcpy(timer_event->buf, file_path, strlen(file_path));
//timer_event.
struct epoll_event ev = {0};
ev.events = EPOLLIN; //监听定时器读事件,定时器描述符可读。
ev.data.ptr=timer_event;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, tfd, &ev); //添加到epoll监听队列中
uint64_t umsg = 1;
int nret = write(wakeup_fd, &umsg, sizeof(uint64_t));
if (nret!= sizeof(uint64_t))
{
printf("write failed\n");
return ;
}
}
}
else if ( event->mask & IN_DELETE ) {
if ( event->mask & IN_ISDIR ) {
printf( "Directory [ %s/%s ] has been removed.\n", file_path, event->name );
}
else {
printf( "File [ %s/%s ] has been removed.\n", file_path, event->name );
}
}
else if ( event->mask & IN_MODIFY ) {
if ( event->mask & IN_ISDIR ) {
printf( "Directory [ %s/%s ] was modified.\n", file_path,event->name );
}
else {
printf( "File [ %s/%s ] was modified.\n", file_path, event->name);
}
}
}
i += EVENT_SIZE + event->len;
}
}
bool FileCleaner::path_check(const char *path) {
if( (access(path, F_OK )) != -1 ) {
printf( "Path [ %s ] exists\n", path);
/* Check for write permission */
if( (access(path, W_OK )) != -1 ) {
printf( "Path [ %s ] has write permission\n", path);
}
else {
printf( "Path [ %s ] has not write permission\n", path);
}
/* Check for read permission */
if( (access(path, R_OK )) != -1 ) {
printf( "Path [ %s ] has read permission\n", path);
}
else {
printf( "Path [ %s ] has not read permission\n", path);
}
/* Check for execute permission */
if( (access(path, X_OK )) != -1 ) {
printf( "Path [ %s ] has execute permission\n", path);
}
else {
printf( "Path [ %s ] has not execute permission\n", path);
}
}
else {
printf( "Path [ %s ] don't exists\n", path);
return false;
}
return true;
}
实例 main.cpp
#include "FileCleaner.h"
using namespace std;
int main( int argc, char **argv )
{
FileCleaner *F = new FileCleaner;
string a;
//方法1 直接代码中指定路径
F->AddWatchBySeconds("./logs", 5);
while(1){//方法2 在控制台输入路径
cin >> a;
bool res = F->AddWatchBySeconds(a.c_str(), 5);
if(res)
cout << "AddWatchBySeconds succeed" << endl;
else
cout << "AddWatchBySeconds failed" << endl;
}
delete F;
return 0;
}
其中有一个小问题:析构函数中,如果
write(wakeup_fd, &umsg, sizeof(uint64_t));
之后不调用usleep, 直接接下面语句的话,
read(close_fd, &umsg, sizeof(uint64_t));
read函数会立即返回,读到的值就是写入wakeup_fd的值,但是这明明是两个不同的描述符啊,close_fd是工作线程在退出时会write的,我在中间加了usleep(100)后便能正常析构。目前没找到原因。
。。。。。。。。。。。。。。。
刚刚试了下,那个问题又没有出现了。。。。。
。。。。。
。。。。