一、 什么是日志文件?
在linux下有很多日志文件,他们是重要的系统信息文件,记录了许多重要的系统事件,比如:用户登录信息、系统启动信息、系统安全信息、服务信息等,日志文件对于诊断和解决系统中的问题很有帮助,因为在Linux系统中运行的程序通常会把系统消息和错误消息写入相应的日志文件,这样系统一旦出现问题就会"有据可查”。此外,当主机遭受攻击时,日志文件还可以帮助寻找攻击者留下的痕迹。
二、日志文件有哪些?
1、内核及系统日志:
这种日志数据由系统服务rsyslog统一管理,根据其主配置文件/etc/rsyslog.conf中的设置决定将内核消息及各种系统程序消息记录到什么位置
2、用户日志:
这种日志数据用于记录Linux系统用户登录及退出系统的相关信息,包括用户名、登录的终端、登录时间、来源主机、正在使用的进程操作等
3、程序日志:
有些应用程序会选择由自己独立管理一份日志文件(而不是交给rsyslog服务管理),用于记录本程序运行过程中的各种事件信息
三、linux系统下常见的日志文件
/var/log/inessages 记录Linux内核消息及各种应用程序的公共日志信息,包括启动、I/O错误、网络错误、程序故障等。对于未使用独立日志文件的应用程序或服务,一般都可以从该日志文件中获得相关的事件记录信息
/var/log/cron 记录crondi计划任务产生的事件信息
/var/log/dmesg 记录Linux系统在引导过程中的各种事件信息
/var/log/maillog 记录进入或发出系统的电子邮件活动
/var/log/lastlog 记录每个用户最近的登录事件
/var/log/secure 记录用户认证相关的安全事件信息
/var/log/wtmp 记录每个用户登录、注销及系统启动和停机事件
/var/log/btmp 记录失败的、错误的登录尝试及验证事件
日志文件的消息等级
等级 等级信息 效果
0 EMERG (紧急) 会导致主机系统不可用的情况
1 ALERT (警告) 必须马上采取措施解决的问题
2 CRIT (严重) 比较严重的情况
3 ERR(错误) 运行出现错误
4 WARNING (提醒) 可能影响系统功能 ,需要提醒用户的重要事件
5 NOTICE (注意) 不会影响正常功能,但是需要注意的事件
6 INFO(信息) 一般信息
7 DEBUG (调试) 程序或系统调试信息等
四、为什么要实现自己的日志文件呢?
因为linux系统本身和大部分服务器程序的日志文件默认都放在根目录/var/log/下
所以很多情况下普通用户是查看不了日志文件的,必须使用root用户才可以查看,但是往往不是每个用户都可以使用root用户的,所以自己编写一个日志文件是很有必要的,这样用户自己就可以在自己的主目录下随意查看日志信息,以便调试。
日志文件的结构
1、要实现系统下的日志文件功能
2、要有日志文件回滚功能,不然会撑爆文件
3、记录的信息显示出程序名称、文件名、行号、时间、调试信息
具体功能
注意是实现了将调式信息打印到标准输出,或者打印到自定义的日志文件中,用户可以随时查看看,并且可以随意选择信息级别,还有非常重要的就是日志回滚功能,用户自己定义日志文件回滚的大小,具体实现过程请查看代码。
五、主要用到的函数
1、strcasecmp() — 字符串比较函数(忽略大小写)
头文件: #include <string.h>
int strcasecmp(const char *s1, const char *s2);
int strncasecmp(const char *s1, const char *s2, size_t n);
strncasecmp比较的是s1和s2的前n个字符
如果第一个字符串大于第二个字符串,则返回大于0的数字
如果第一个字符串小于第二个字符串,则返回小于0的数字
如果第一个字符串等于第二个字符串,则返回0 (按ASCII值相减)
2、fopen() — 打开相应的文件
所谓“打开”是指为文件建立相应的信息区和文件缓冲区。
ANSI C规定了用标准输入输出函数fopen来实现打开文件——fopen(文件名,使用文件方式)
如:fopen("a1","r");
fopen函数的返回值是指向a1文件的指针变量(即a1文件信息区的起始地址)。通常将fopen函数的返回值赋给一个指向文件的指针变量。如
FILE *fp;
fp=fopen("a1","r");
这样fp就和文件a1相联系了,或者说fp指向了文件。
使用文件的方式:
r:只读方式,文件必须存在
w:只写方式,若文件存在,则原有内容会被清除;若文件不存在,则会建立文件
a:追加方式打开只写文件,只允许进行写操作,若文件存在,则添加的内容放在文件末尾;若不存在,则建立文件
+:可读可写
b:以二进制方式打开文件
t:以文本方式打开文件(默认方式下以文本方式打开文件)
3、ftell() — 用来取文件当前位置
其一般形式为:
long n;
n = ftell(fp);
它的返回值时一个长整型,表示当前的读写位置(从文件开始处到现在的字节数),调用正确返回当前读写位置,错误返回-1L.
4、fseek() — 修改文件偏移量
(文件类型指针,位移量,起始点)
fseek(fp,100L,0); 将文件位置标记向前移到离开头100个字节处
fseek(fp,50L,1); 将文件位置标记向前移到离当前位置50个字节处
fseek(fp,-10L,2); 将文件位置标记从末尾处向后退10个字节
5、rewind() — 关于文件位置标记的函数
rewind函数使文件位置标记指向文件开头,无返回值。
rewind(fp)
6、truncate() — 用来修改文件的大小
truncate ftruncate函数
作用:都可以用来修改文件的大小,但前者第一个参数是文件的路径名(指向文件的指针),后者第一个参数为为指向文件的文件描述符。
头文件:#include <unistd.h>
int truncate(const char *path, off_t length);
eg:truncate("./test.txt", 500);
函数说明:
truncate()会将参数path指定的文件大小改为参数length指定的大小。 如果原来的文件大小比参数length大,则超过的部分会被删除(如需清空文件,length设置为0)
返回值:
执行成功则返回0, 失败返回-1, 错误原因存于errno
int ftruncate(int fd,off_t length);
1 out = open("./test.txt", O_RDWR | O_CREAT, S_IRUSR);
2 ftruncate(out, 500);
3 close(out);
函数说明:
ftruncate()会将参数fd指定的文件大小改为参数length指定的大小。
参数fd为已打开的文件描述词,而且必须是以写入模式打开的文件。
如果原来的文件大小比参数length大,则超过的部分会被删去。(如需清空文件,length设置为0)
返回值:
执行成功则返回0,失败返回-1,错误原因存于errno。
7、va_start() — 用于获取函数参数列表中可变参数的首指针(获取函数可变参数列表)
va_list args;
va_start(args, format); /* 在栈中浏览并得到相关参数 */
...
va_end(args);
va_list表示可变参数列表类型,实际上就是一个char指针
va_start(args, format)
* 输出参数ap(类型为va_list): 用于保存函数参数列表中可变参数的首指针(即,可变参数列表)
* 输入参数A: 为函数参数列表中最后一个固定参数
va_end()
用于结束对可变参数的处理。实际上,va_end被定义为空.它只是为实现与va_start配对(实现代码对称和"代码自注释"功能)
8、vsprintf() — 将格式化的数据输入到指定的字符串中
此函数的功能和sprintf差不多,但是 sprintf() 只能原始的为它输入所有的参数而不能以传参的方式给它。
9、fflush() — 刷新缓冲区
fflush()⽤于清空⽂件缓冲区,如果⽂件是以写的⽅式打开 的,则把缓冲区内容写⼊⽂件。其原型为:
int fflush(FILE* stream);
【参数】stream为⽂件指针。
【返回值】成功返回0,失败返回EOF,错误代码存于errno 中。指定的流没有缓冲区或者只读打开时也返回0值。
六、代码如下
logger.c
/*********************************************************************************
* Copyright: (C) 2022 Li Rongquan<2962837290@qq.com>
* All rights reserved.
*
* Filename: logger.c
* Description: This file is log files record information.
*
* Version: 1.0.0(04/17/2022)
* Author: Li Rongquan <2962837290@qq.com>
* ChangeLog: 1, Release initial version on "04/17/2022 09:56:29 AM"
*
********************************************************************************/
#include "logger.h"
/*
* Program name variable is provided by the libc
*/
extern const char* __progname;
#define PROGRAM_NAME __progname
/*
* Logger internal sctructure
*/
typedef struct logger_s {
FILE *fp;
int loglevel;
int use_stdout;
int log_rollback_size;
} logger_t;
static struct logger_s g_logger;
static const char* LOG_LEVELS[] = {
LOG_STRING_ERROR,
LOG_STRING_WARN,
LOG_STRING_INFO,
LOG_STRING_DEBUG
};
/*
* initial logger system
*/
int logger_init(char *filename, int loglevel)
{
logger_term();
g_logger.log_rollback_size = LOG_ROLLBACK_SZIE;
g_logger.loglevel = loglevel>LOG_LEVEL_MAX ? LOG_LEVEL_MAX : loglevel;
/* filename is NULL or match "stdout" will use standard output */
if( !filename || !strcasecmp(filename, "stdout"))
{
g_logger.use_stdout = 1;
g_logger.fp = stderr;
}
else
{
g_logger.use_stdout = 0;
g_logger.fp = fopen(filename, "a");
if( !g_logger.fp )
{
fprintf(stderr, "Failed to open file '%s': %s", filename, strerror(errno));
return -1;
}
}
if(check_and_rollback(filename) < 0)
{
log_error("check_and_rollback() error!!!\n");
return -2;
}
return 0;
}
/*
* terminate logger system
*/
void logger_term(void)
{
if( !g_logger.fp )
{
return ;
}
if( !g_logger.use_stdout )
{
fclose(g_logger.fp);
}
g_logger.use_stdout = 0;
g_logger.fp = NULL;
return ;
}
int check_and_rollback(char *log_name)
{
if (g_logger.log_rollback_size != LOG_ROLLBACK_NONE)
{
long _curOffset = ftell(g_logger.fp); /* 读取日志文件的字节数 */
if ((_curOffset != -1) && (_curOffset >= g_logger.log_rollback_size))
{
char cmd[512];
snprintf(cmd, sizeof(cmd), "cp -f %s %s.roll", log_name, log_name);
system(cmd);
if (-1 == fseek(g_logger.fp, 0L, SEEK_SET))
{
fprintf(g_logger.fp, "log rollback fseek failed \n");
return -1;
}
rewind(g_logger.fp); /* 使文件位置标记指向文件开头 */
truncate(log_name, 0); /* 将log_name所指向的日志文件清空 */
log_info("Already rollback");
}
}
return 0;
}
/*
* Logging functions
*/
void log_generic(const int level, char *file, int line, const char* format, va_list args)
{
char message[256];
struct tm* current_tm;
time_t time_now;
vsprintf(message, format, args); /* 将格式化数据输入到messaage中,和sprintf差不多 */
time(&time_now); /* 获取时间秒为单位 */
current_tm = localtime(&time_now); /* 将time_now转换为格式化时间字符串(tm结构)*/
int res = fprintf(g_logger.fp,
"%02i:%02i:%02i -> [ %s / %s / %d line ] : [%s] %s\n"
, current_tm->tm_hour
, current_tm->tm_min
, current_tm->tm_sec
, PROGRAM_NAME
,file
,line
, LOG_LEVELS[level]
, message );
fflush(g_logger.fp); /* 如果是stderr则刷新缓冲区,打印到屏幕,如果是日志文件,则不影响 */
}
void log_line_error(char *flie, int line, char *format, ...)
{
va_list args;
va_start(args, format); /* 在栈中浏览并得到相关参数 */
log_generic(LOG_LEVEL_ERROR, flie, line, format, args);
va_end(args);
}
void log_line_warn(char *flie, int line, char *format, ...)
{
if (g_logger.loglevel < LOG_LEVEL_WARN) {
return;
}
va_list args;
va_start(args, format);
log_generic(LOG_LEVEL_WARN, flie, line, format, args);
va_end(args);
}
void log_line_info(char *flie, int line, char *format, ...)
{
if (g_logger.loglevel < LOG_LEVEL_INFO) {
return;
}
va_list args;
va_start(args, format);
log_generic(LOG_LEVEL_INFO, flie, line, format, args);
va_end(args);
}
void log_line_debug(char *flie, int line, char *format, ...)
{
if (g_logger.loglevel < LOG_LEVEL_DEBUG) {
return;
}
va_list args;
va_start(args, format);
log_generic(LOG_LEVEL_DEBUG, flie, line, format, args);
va_end(args);
}
logger.h
/********************************************************************************
* Copyright: (C) 2022 Li Rongquan<2962837290@qq.com>
* All rights reserved.
*
* Filename: logger.h
* Description: This head file logger.c header file.
*
* Version: 1.0.0(04/16/2022)
* Author: Li Rongquan <2962837290@qq.com>
* ChangeLog: 1, Release initial version on "04/16/2022 09:33:38 PM"
*
********************************************************************************/
#ifndef _LOGGER_H_
#define _LOGGER_H_
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdarg.h>
#include <string.h>
/*
* logger level
*/
enum
{
LOG_LEVEL_ERROR,
LOG_LEVEL_WARN,
LOG_LEVEL_INFO,
LOG_LEVEL_DEBUG,
LOG_LEVEL_MAX,
};
/*
* logger prefix string for different logging levels
*/
#define LOG_STRING_ERROR "ERROR"
#define LOG_STRING_WARN "WARN "
#define LOG_STRING_INFO "INFO "
#define LOG_STRING_DEBUG "DEBUG"
#define LOG_ROLLBACK_SZIE 1024*10 /* log file reaches 1024*100 bytes rollback */
#define LOG_ROLLBACK_NONE 0 /* set rollback size to 0 will not rollback */
#define log_error(format, ...) log_line_error(__FILE__, __LINE__, format, ##__VA_ARGS__)
#define log_warn(format, ...) log_line_warn(__FILE__, __LINE__, format, ##__VA_ARGS__)
#define log_info(format, ...) log_line_info(__FILE__, __LINE__, format, ##__VA_ARGS__)
#define log_debug(format, ...) log_line_debug(__FILE__, __LINE__, format, ##__VA_ARGS__)
/*
* logger initial and terminate functions
*/
int logger_init(char *filename, int loglevel);
void logger_term(void);
int check_and_rollback(char *log_name);
/*
* logging methods by levels
*/
void log_line_error(char *flie, int line, char *format, ...);
void log_line_warn(char *flie, int line, char *format, ...);
void log_line_info(char *flie, int line, char *format, ...);
void log_line_debug(char *flie, int line, char *format, ...);
#endif /* ----- #ifndef _LOGGER_H_ ----- */
七、如何调用
在main函数中
初始化
if(logger_init(logfile,LOG_LEVEL_DEBUG) < 0) /* print to standard output */
{
fprintf(stderr,"initial logger file '%s' failure: %s\n",logfile,strerror(errno));
return 0;
}
logfile可以选择为自定义的日志文件名或者为标准输出“stdout”
LOG_LEVEL_DEBUG为最高的级别,比它小或者等于它的级别都可以使用了
后面就直接调用log_warn()等函数,用法和printf一样。