Linux基础组件之日志框架

日志写入逻辑

相关IO接口函数

fwrite()和fread()

函数原型:

#include <stdio.h>

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

描述:
函数fread()从stream指向的流中读取nmemb数据项,每个数据项的长度为size字节,并将它们存储在ptr指定的位置。

函数fwrite()将nmemb数据项写入stream指向的流,每个数据项的长度为size字节,并从ptr给定的位置获取它们。

有关非锁定副本,请参见unlocked_stdio()。

返回值:
成功时,fread()和fwrite()返回读取或写入的项数。此数字等于仅当size为1时传输的字节数。如果发生错误或到达文件结尾,则返回值为短项目计数(或零)。

fread()无法区分文件结尾和错误,调用方必须使用feof()和ferror()来确定发生了哪一个错误。

fclose()

关闭流,函数原型为:

#include <stdio.h>

int fclose(FILE *stream);

描述:
刷新stream指向的流(使用fflush()写入任何缓冲的输出数据),并关闭底层文件描述符。

如果stream参数是非法指针,或者是已经传递给前一次调用fclose()的描述符,则fclose()的行为是未定义的。

返回值:
成功完成后返回0。否则,将返回EOF,并设置errno以指示错误。无论哪种情况,对流的任何进一步访问(包括对fclose()的另一个调用)都会导致未定义的行为。

错误

EBADF:stream底层的文件描述符无效。

fclose()函数也可能失败,并为例程close()、write()或fflush()指定的任何错误设置errno。

注意:
请注意,fclose()只刷新C库提供的用户空间缓冲区。为了确保数据物理存储在磁盘上,还必须刷新内核缓冲区,例如,使用sync()或fsync()。

fflush()

刷新一个流,函数原型:

#include <stdio.h>

int fflush(FILE *stream);

描述:
对于输出流,fflush()通过流的底层写函数强制写入给定输出或更新stream的所有用户空间缓冲数据。

对于与可查找文件(例如,磁盘文件,但不是管道或终端)关联的输入流,fflush()将丢弃从基础文件提取但应用程序尚未使用的任何缓冲数据。

流的打开状态不受影响。

如果stream参数为NULL,则fflush()将刷新所有打开的输出流。

有关非锁定副本,请参见unlocked_stdio()。

返回值:

成功完成后返回0。否则,将返回EOF,并设置errno以指示错误。

错误:

EBADF:stream不是开放流,或者不开放用于写入。

函数fflush()也可能失败,并为为write()指定的任何错误设置errno。

fileno()

检查和重置流状态。函数原型:

#include <stdio.h>

void clearerr(FILE *stream);

int feof(FILE *stream);

int ferror(FILE *stream);

int fileno(FILE *stream);

//Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
//fileno(): _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE

描述:
函数clearer()清除stream指向的流的文件结尾和错误指示符。

函数feof()测试stream指向的流的文件结束指示符,如果设置了该指示符,则返回非零。文件结束指示符只能由函数clearer()清除。

函数ferror()测试stream指向的流的错误指示符,如果设置了该指示符,则返回非零。错误指示器只能通过clearer()函数重置。

函数fileno()检查参数stream并返回其整数描述符。

错误:
这些函数不应失败,也不应设置外部变量errno。(但是,如果fileno()检测到其参数不是有效的流,则必须返回-1并将errno设置为EBADF。)

fsync()

将处于核心状态的文件与存储设备同步。函数原型:

#include <unistd.h>

int fsync(int fd);

int fdatasync(int fd);

//Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
//fsync(): _BSD_SOURCE || _XOPEN_SOURCE || /* since glibc 2.8: */ _POSIX_C_SOURCE >= 200112L
//fdatasync(): _POSIX_C_SOURCE >= 199309L || _XOPEN_SOURCE >= 500

描述:

fsync()将文件描述符fd引用的文件的所有修改的核心数据(即修改的缓冲区缓存页)传输(“刷新”)到磁盘设备(或其他永久存储设备),以便即使在系统崩溃或重新启动后也可以检索到所有更改的信息。这包括写入或刷新磁盘缓存(如果存在)。调用将阻塞,直到设备报告传输已完成。它还刷新与文件关联的元数据信息(请参阅stat() )。

调用fsync()并不一定确保包含该文件的目录中的条目也已到达磁盘。为此,还需要在目录的文件描述符上有fsync()。

fdatasync()类似于fsync(),但不会刷新修改后的元数据,除非需要该元数据才能正确处理后续数据检索。例如,对st_atime或st_mtime的更改(分别是上次访问的时间和上次修改的时间;请参阅stat() )不需要刷新,因为它们对于正确处理后续读取的数据来说是不必要的。另一方面,更改文件大小(st_size,如ftruncate() 所做的那样)将需要刷新元数据。

fdatasync()的目的是减少不需要与磁盘同步所有元数据的应用程序的磁盘活动。

返回值:
成功时,这些系统调用返回零。出错时,返回-1,并适当设置errno。

错误:

标识含义
EBADFfd不是有效的打开文件描述符。
EIO同步期间发生错误。
EROFS、EINVALfd被绑定到一个不支持同步的特殊文件。

setvbuf()

流缓冲操作,函数原型:

#include <stdio.h>

void setbuf(FILE *stream, char *buf);

void setbuffer(FILE *stream, char *buf, size_t size);

void setlinebuf(FILE *stream);

int setvbuf(FILE *stream, char *buf, int mode, size_t size);

//Feature Test Macro Requirements for glibc (see feature_test_macros(7)):
//setbuffer(), setlinebuf(): _BSD_SOURCE

描述:
可用的三种缓冲类型是无缓冲、块缓冲和线缓冲。当输出流未缓冲时,信息在写入目标文件或终端时立即显示;当它被块缓冲时,许多字符被保存并写入一个块;当它是行缓冲字符时,将一直保存到输出换行符或从连接到终端设备的任何流(通常是stdin)读取输入为止。功能fflush() 可用于提前推出挡块。

通常,所有文件都是块缓冲的。如果流引用终端(如stdout通常所做的那样),那么它是行缓冲的。默认情况下,标准错误流stderr总是无缓冲的。

setvbuf() 函数可用于任何开放流以更改其缓冲区。mo5de参数必须是以下三个宏之一:

标识含义
_IONBF无缓冲
_IOLBF缓存线
_IOFBF完全缓冲

除非缓冲文件外,buf参数应指向长度至少为字节大小的缓冲区;将使用此缓冲区代替当前缓冲区。如果参数buf为NULL,则仅影响模式;下一次读或写操作将分配一个新的缓冲区。setvbuf()函数只能在打开流之后和对其执行任何其他操作之前使用。

其他三个调用实际上只是setvbuf() 调用的别名。setbuf() 函数与下列调用完全等效:

setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);

setbuffer() 函数也是和setbuf() 相同的,只是缓冲区的大小取决于调用方,而不是由默认的BUFSIZ决定。

setlinebuf() 函数与以下调用完全等效:

setvbuf(stream, NULL, _IOLBF, 0);

返回值:
函数setvbuf()成功时返回0。失败时返回非零(模式无效或无法满足请求)。它可能会在失败时设置errno。
其他函数不返回值。

fwrite与write的关系

fwrite()是C语言中的文件流,应用层的库接口,而write()是系统接口,fwrite()最终会调用write()将数据写入磁盘。

先看看一个简单的示例,分析fwrite()和write()。

#include <iostream>
#include <sstream>
#include <stdio.h>
#include <unistd.h>

using namespace std;

int main()
{
	FILE *file = fopen("fwrite_test.log", "wt");

	printf("BUFSIZ: %d\n", BUFSIZ);

	for (int i = 0; i < 10; i++)
	{
		ostringstream oss;
		oss << "No." << i << "ROOT ERROR Message!\n";
		fwrite(oss.str().c_str(), 1, oss.str().size(), file);
	}

	fclose(file);

	return 0;
}

用gdb调试的结果为:

GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./fwrite_test...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x400d2a
(gdb) r
Starting program: /mnt/hgfs/sourcecode_learning/fwrite_test 

Breakpoint 1, 0x0000000000400d2a in main ()
(gdb) b write
Breakpoint 2 at 0x7ffff755a0f0: file ../sysdeps/unix/sysv/linux/write.c, line 27.
(gdb) c
Continuing.

Breakpoint 2, __GI___libc_write (fd=1, buf=0x6150a0, nbytes=13) at ../sysdeps/unix/sysv/linux/write.c:27
27	../sysdeps/unix/sysv/linux/write.c: 没有那个文件或目录.
(gdb) bt
#0  __GI___libc_write (fd=1, buf=0x6150a0, nbytes=13) at ../sysdeps/unix/sysv/linux/write.c:27
#1  0x00007ffff74d515d in _IO_new_file_write (f=0x7ffff7836760 <_IO_2_1_stdout_>, data=0x6150a0, n=13) at fileops.c:1203
#2  0x00007ffff74d6f01 in new_do_write (to_do=13, data=0x6150a0 "BUFSIZ: 8192\n", fp=0x7ffff7836760 <_IO_2_1_stdout_>) at fileops.c:457
#3  _IO_new_do_write (fp=0x7ffff7836760 <_IO_2_1_stdout_>, data=0x6150a0 "BUFSIZ: 8192\n", to_do=13) at fileops.c:433
#4  0x00007ffff74d598d in _IO_new_file_xsputn (f=0x7ffff7836760 <_IO_2_1_stdout_>, data=<optimized out>, n=1) at fileops.c:1266
#5  0x00007ffff74a597a in _IO_vfprintf_internal (s=0x7ffff7836760 <_IO_2_1_stdout_>, format=0x400fd8 "BUFSIZ: %d\n", ap=ap@entry=0x7fffffffe180) at vfprintf.c:1674
#6  0x00007ffff74aeee6 in __printf (format=<optimized out>) at printf.c:33
#7  0x0000000000400d6b in main ()
(gdb) c
Continuing.
BUFSIZ: 8192

Breakpoint 2, __GI___libc_write (fd=3, buf=0x615720, nbytes=240) at ../sysdeps/unix/sysv/linux/write.c:27
27	in ../sysdeps/unix/sysv/linux/write.c
(gdb) bt
#0  __GI___libc_write (fd=3, buf=0x615720, nbytes=240) at ../sysdeps/unix/sysv/linux/write.c:27
#1  0x00007ffff74d515d in _IO_new_file_write (f=0x614e70, data=0x615720, n=240) at fileops.c:1203
#2  0x00007ffff74d6f01 in new_do_write (to_do=240, data=0x615720 "No.0ROOT ERROR Message!\nNo.1ROOT ERROR Message!\nNo.2ROOT ERROR Message!\nNo.3ROOT ERROR Message!\nNo.4ROOT ERROR Message!\nNo.5ROOT ERROR Message!\nNo.6ROOT ERROR Message!\nNo.7ROOT ERROR Message!\nNo.8ROOT"..., fp=0x614e70) at fileops.c:457
#3  _IO_new_do_write (fp=fp@entry=0x614e70, data=0x615720 "No.0ROOT ERROR Message!\nNo.1ROOT ERROR Message!\nNo.2ROOT ERROR Message!\nNo.3ROOT ERROR Message!\nNo.4ROOT ERROR Message!\nNo.5ROOT ERROR Message!\nNo.6ROOT ERROR Message!\nNo.7ROOT ERROR Message!\nNo.8ROOT"..., to_do=240) at fileops.c:433
#4  0x00007ffff74d62b0 in _IO_new_file_close_it (fp=fp@entry=0x614e70) at fileops.c:136
#5  0x00007ffff74c8337 in _IO_new_fclose (fp=0x614e70) at iofclose.c:53
#6  0x0000000000400e7f in main ()

可以看到,这是这个fwrite还没有调用write(),write()的调用分别在printf()和fclose()中调用的。
我们把fwrite()的执行次数由10次变为10000次,再来gdb调试看看。

#include <iostream>
#include <sstream>
#include <stdio.h>
#include <unistd.h>

using namespace std;

int main()
{
	FILE *file = fopen("fwrite_test.log", "wt");

	//printf("BUFSIZ: %d\n", BUFSIZ);

	for (int i = 0; i < 10000; i++)
	{
		ostringstream oss;
		oss << "No." << i << "ROOT ERROR Message!\n";
		fwrite(oss.str().c_str(), 1, oss.str().size(), file);
	}

	fclose(file);

	return 0;
}

gdb结果如下:

GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./fwrite_test...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x400cea
(gdb) r
Starting program: /mnt/hgfs/sourcecode_learning/fwrite_test 

Breakpoint 1, 0x0000000000400cea in main ()
(gdb) b write
Breakpoint 2 at 0x7ffff755a0f0: file ../sysdeps/unix/sysv/linux/write.c, line 27.
(gdb) c
Continuing.

Breakpoint 2, __GI___libc_write (fd=3, buf=0x615310, nbytes=1024) at ../sysdeps/unix/sysv/linux/write.c:27
27	../sysdeps/unix/sysv/linux/write.c: 没有那个文件或目录.
(gdb) bt
#0  __GI___libc_write (fd=3, buf=0x615310, nbytes=1024) at ../sysdeps/unix/sysv/linux/write.c:27
#1  0x00007ffff74d515d in _IO_new_file_write (f=0x614e70, data=0x615310, n=1024) at fileops.c:1203
#2  0x00007ffff74d6f01 in new_do_write (to_do=1024, data=0x615310 "No.0ROOT ERROR Message!\nNo.1ROOT ERROR Message!\nNo.2ROOT ERROR Message!\nNo.3ROOT ERROR Message!\nNo.4ROOT ERROR Message!\nNo.5ROOT ERROR Message!\nNo.6ROOT ERROR Message!\nNo.7ROOT ERROR Message!\nNo.8ROOT"..., fp=0x614e70) at fileops.c:457
#3  _IO_new_do_write (fp=0x614e70, data=0x615310 "No.0ROOT ERROR Message!\nNo.1ROOT ERROR Message!\nNo.2ROOT ERROR Message!\nNo.3ROOT ERROR Message!\nNo.4ROOT ERROR Message!\nNo.5ROOT ERROR Message!\nNo.6ROOT ERROR Message!\nNo.7ROOT ERROR Message!\nNo.8ROOT"..., to_do=1024) at fileops.c:433
#4  0x00007ffff74d598d in _IO_new_file_xsputn (f=0x614e70, data=<optimized out>, n=25) at fileops.c:1266
#5  0x00007ffff74c9927 in __GI__IO_fwrite (buf=0x6152e0, size=1, count=25, fp=0x614e70) at iofwrite.c:39
#6  0x0000000000400de6 in main ()

此时可以看到,write()被fwrite()调用的。

这说明,在c语言的FILE文件库中有缓冲区。

fwrite()写入流程

用户程序 用户缓存区setbuf() 内核缓存区 物理设备 fwrite() 数据超出缓冲区时 fflush()或write() fsync() stdout stdin 页面宽度 read() fread() 用户程序 用户缓存区setbuf() 内核缓存区 物理设备

调用fwrite时,如果用户缓冲区满了会调用fflush()或write()将数据写入内核缓冲区;内核调用fsync()才真正把数据写入磁盘中。所以,在调用fsync()之前,数据还没有写入磁盘的。如果要及时或实时将数据写入磁盘中,可以自己调用fsync()函数。

setbuf()可以设置用户缓冲区的大小。
fflush()时触发write(),不是触发fsync(),这个需要注意。
c库缓冲-----fflush---------〉内核缓冲--------fsync-----〉磁盘

调用
写入
调用
写入
C库缓存
fflush()
内核缓存
fsync()
磁盘

可以通过一个例子来证明:

#include <iostream>
#include <sstream>
#include <stdio.h>
#include <unistd.h>

using namespace std;

int main()
{
	FILE *file = fopen("fwrite_test.log", "wt");

	//printf("BUFSIZ: %d\n", BUFSIZ);

	for (int i = 0; i < 10; i++)
	{
		ostringstream oss;
		oss << "No." << i << "ROOT ERROR Message!\n";
		fwrite(oss.str().c_str(), 1, oss.str().size(), file);
		fflush(file);
	}

	fclose(file);

	return 0;
}

使用gdb进行断点调试的结果是:

GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./fwrite_test...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x400d2a
(gdb) r
Starting program: /mnt/hgfs/sourcecode_learning/fwrite_test 

Breakpoint 1, 0x0000000000400d2a in main ()
(gdb) b write
Breakpoint 2 at 0x7ffff755a0f0: file ../sysdeps/unix/sysv/linux/write.c, line 27.
(gdb) c
Continuing.

Breakpoint 2, __GI___libc_write (fd=3, buf=0x615310, nbytes=24) at ../sysdeps/unix/sysv/linux/write.c:27
27	../sysdeps/unix/sysv/linux/write.c: 没有那个文件或目录.
(gdb) bt
#0  __GI___libc_write (fd=3, buf=0x615310, nbytes=24) at ../sysdeps/unix/sysv/linux/write.c:27
#1  0x00007ffff74d515d in _IO_new_file_write (f=0x614e70, data=0x615310, n=24) at fileops.c:1203
#2  0x00007ffff74d6f01 in new_do_write (to_do=24, data=0x615310 "No.0ROOT ERROR Message!\n", fp=0x614e70) at fileops.c:457
#3  _IO_new_do_write (fp=0x614e70, data=0x615310 "No.0ROOT ERROR Message!\n", to_do=24) at fileops.c:433
#4  0x00007ffff74d4728 in _IO_new_file_sync (fp=0x614e70) at fileops.c:813
#5  0x00007ffff74c882d in __GI__IO_fflush (fp=0x614e70) at iofflush.c:40
#6  0x0000000000400e50 in main ()
(gdb) b fsync
Breakpoint 3 at 0x7ffff7560ee0: file ../sysdeps/unix/sysv/linux/fsync.c, line 27.
(gdb) c
Continuing.

Breakpoint 2, __GI___libc_write (fd=3, buf=0x615310, nbytes=24) at ../sysdeps/unix/sysv/linux/write.c:27
27	in ../sysdeps/unix/sysv/linux/write.c
(gdb) disable 2
(gdb) c
Continuing.
[Inferior 1 (process 6926) exited normally]
(gdb) 

从结果上看到,fflush()只触发了write(),给fsync()设置的断点一直没有触发到。要想实时或及时把数据刷到磁盘中,可以自己调用fsync()函数。如下所示:

int fd = fileno(file);//把文件流指针转换为文件描述符
fsync(fd);

小结

(1)fwrite() 有缓存,write() 没有缓存。
(2)write() 是系统调用,每次需要将数据写到磁盘,写的大小是要求的大小,依然设计频繁的用户态和内核态切换。
(3)fwrite() 是库函数,每次将数据写入到缓冲区,等缓冲区满了再一次写入磁盘;或者使用fflush冲洗缓冲区。从而减少系统调用,减少内核态和用户态的切换。
(4)fflush()是把C库中的缓冲调用write() 函数写到磁盘(其实是写到内核的缓冲区)。
(5)fsync() 是把内核缓冲刷到磁盘上。
flush

log4cpp日志框架

log4cpp是个基于LGPL的开源项⽬,移植⾃Java的⽇志处理跟踪项⽬log4j,并保持了API上的⼀致。其类 似的⽀持库还包括Java(log4j),C++(log4cpp、log4cplus),C(log4c),python(log4p)等。

priority filter
priority filter
priority filter
Layout
Layout
Layout
Message
Category
Appender 1
Appender 2
Appender n
Output
Output
Output

Log4cpp中最重要概念有Category(种类)、Appender(附加器)、Layout(布局)、Priority(优先级)、NDC(嵌套的诊断上下⽂)。

Log4cpp中的Category使其支持每个模块都有独立的日志的输出。Layout是格式化输出。
Log4cpp支持树状功能。也就是说一个根节点下的所有子节点模块的输出都打印到根节点模块中的输出文件里。
Log4cpp适合使用在客户端,不适合使用在服务器。Log4cpp不是批量写入模式,而是直接调用write() 进行实时写入,这样的性能就不会非常高。但是可以借鉴它的一些成熟的理念。

log4cpp下载编译

(1)下载。

wget https://sourceforge.net/projects/log4cpp/files/latest/download

(2)解压。

tar zxf log4cpp-1.1.3.tar.gz

(3)编译。

cd log4cpp 
./configure 
make 
make check 
sudo make install 
sudo ldconfig

默认安装路径:
头⽂件:/usr/local/include/log4cpp
库⽂件: /usr/local/lib/

log4cpp日志级别

enum LogLevel { 
	TRACE, 
	DEBUG, 
	INFO, 
	WARN, 
	ERROR, 
	FATAL, 
	NUM_LOG_LEVELS, 
};

在使⽤⽇志库的时候要注意不同⽇志库的级别有差异,⽐如有些⽇志库 DEBUG和INFO的级别是反过来 的。

log4cpp日志格式化

一般日志输出时会携带一些关注的信息,比如

20210410 14:18:15.299684Z 30836 INFO NO.1506710 Root Error Message! - log_test.cpp:17

格式包含:年月日 时分秒 微妙 时区 日志级别 日志内容 文件名 行号。

比如Log4cpp支持的转义定义:
◼ %% - 转义字符’%’
◼ %c - Category
◼ %d - 日期;日期可以进一步设置格式,用花括号包围,例如%d{%H:%M:%S,%l}。日期的格式符号与ANSI C函数strftime中的一致。但增加了一个格式符号%l,表示毫秒,占三个十进制位。
◼ %m - 消息
◼ %n - 换行符;会根据平台的不同而不同,但对用户透明。
◼ %p - 优先级
◼ %r - 自从layout被创建后的毫秒数
◼ %R - 从1970年1月1日开始到目前为止的秒数
◼ %u - 进程开始到目前为止的时钟周期数
◼ %x - NDC
◼ %t - 线程id

log4cpp日志Loyout

log4cpp支持的日志布局有:

(1)BasicLayout::format。
基本布局。它会为你添加“时间”、“优先级”、“种类”、“NDC”。相当于PatternLayout格式化为:“%R %p %c %x: %m%n”。

(2) PassThroughLayout::format。
直通布局。顾名思义,这个就是没有布局的“布局”,你让它写什么它就写什么,它不会为你添加任何东西,连换行符都懒得为你加。但是,它支持自定义的布局,我们可以继承他实现自定义的日志格式。

(3) PatternLayout::format log4cpp。
格式化布局,支持用户配置日志格式。它的使用方式类似C语言中的printf,使用格式化它符串来描述输出格式;目前支持的转义定义。

(4) SimpleLayout::format 。
简单布局。比BasicLayout还简单的日志格式输出,它只会为你添加“优先级”的输出。相当于PatternLayout格式化为:“%p:
%m%n”。

log4cpp日志输出

日志有不同的输出方式,以log4cpp为例:
(1)日志输出到控制台。ConsoleAppender。
(2)日志输出到本地文件。FileAppender,值得注意的是,log4cpp使用write()来输出到文件,这种方式的性能不会太高i,因为要频繁的切换用户态和内核态。这是一个可以优化的地方。
(3)日志通过网络传输到远程服务器。RemoteSyslogAppender。

log4cpp日志回滚

日志库一般都具备如下功能:
(1)本地日志支持最大文件限制。
(2)当本地日志达到最大文件限制的时候新建一个文件。
(3)每天至少一个文件。

比如log4cpp里的RollingFileAppender()。

RollingFileAppender(
	const std::string& name,
	const std::string& filename,
	size_t maxFileSize=10*1024*1024//日志文件支持最大的大小
	unsigned int maxBackupIndex=1,//最大生成文件数量
	bool append=true,// 是否以追加的方式写入
	mode_t mode = 00644
);

log4cpp日志配置

成熟的日志文件要支持配置文件,通过修改配置文件来改变日志输出方式而不需要修改代码。
比如log4cpp日志配置:

# a simple test config
#定义了3个category sub1, sub2, sub1.sub2
# category 有两个参数 日志级别,Appender
log4cpp.rootCategory=DEBUG, rootAppender
# log4cpp.category.sub1设置日志级别默认和root一致, Appender为A1
log4cpp.category.sub1=,A1
# log4cpp.category.sub2设置为INFO,Appender默认使用root的
log4cpp.category.sub2=INFO
#log4cpp.category.sub1.sub2=ERROR, A2
log4cpp.category.sub1.sub2=, A2
# 设置sub1.sub2 的additivity属性,该属性默认值为true
# 如果值为true,则该Category的Appender包含了父Category的Appender, 即是日志也从root的appender输出
# 如果值为false,则该Category的Appender取代了父Category的Appender
log4cpp.additivity.sub1=false
# sub1.sub2的日志也从sub1的appender输出
log4cpp.additivity.sub1.sub2=true 
#定义rootAppender类型和layout属性
log4cpp.appender.rootAppender=org.apache.log4cpp.ConsoleAppender
log4cpp.appender.rootAppender.layout=org.apache.log4cpp.BasicLayout
#定义A1的属性
log4cpp.appender.A1=org.apache.log4cpp.FileAppender
log4cpp.appender.A1.fileName=A1.log
log4cpp.appender.A1.layout=org.apache.log4cpp.SimpleLayout
#定义A2的属性
log4cpp.appender.A2=org.apache.log4cpp.ConsoleAppender
log4cpp.appender.A2.layout=org.apache.log4cpp.PatternLayout
#log4cpp.appender.A2.layout.ConversionPattern=The message '%m' at time 
# log4cpp.appender.A2.layout.ConversionPattern=%d %m %n
# %d 时间戳 %t 线程名 %x NDC  %p 优先级 %m log message 内容  %n 回车换行
log4cpp.appender.A2.layout.ConversionPattern=%d %p %x - %m%n

总结

fwrite()是有缓存去,write()没有缓存,fflush()把用户层的缓存刷新到内核,fsync()将内核的缓存刷新到磁盘中。

批量写入数据,少调用write()的接口,也少调用fsync()接口,因为系统调用的性能相对比较差。

通过剖析log4cpp日志库的日志框架,可以清晰一个成熟的、完善的日志框架应该支持:
(1)日志级别。
(2)日志格式化。
(3)日志输出方式。
(4)日志回滚。
(5)日志配置文件。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion Long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值