Home | 简体中文 | 繁体中文 | 杂文 | Github | 知乎专栏 | 51CTO学院 | CSDN程序员研修院 | OSChina 博客 | 腾讯云社区 | 阿里云栖社区 | Facebook | Linkedin | Youtube | 打赏(Donations) | About
知乎专栏多维度架构

2.5. 多维度架构之日志

传统做法是文件型日志,分布在各个服务器上。在大规模部署服务器后代来很多不便,增加很多管理成本,所有我们需要集中管理服务器产生的所有日志,我们叫他日志中心服务器

2.5.1. 一次切割日志引发的血案

很多应用程序会产生日志,有些程序已经实现了日志切割,一般是每天一个文件。但有时这个切割并不能满足我们的需求,例如我们需要颗粒度更细的切割。

切割日志的目的是什么?

  • 日志尺寸过大
  • 便于分析
  • 切割后归档,或者导入日志平台

切割日志基本两种方法:

  • 手工
  • shell
  • 工具,例如logrotate,传统的cronolog

日志切割方案网上有很多,很多运维也是参考这些方案进行配置,网上的例子不完全都是对的,可能你用了很多年配置方案是错误的。 没有出现故障是侥幸,因为笔者15年前就在此处栽过,由于日志太大,我便清空了日志,以为程序仍然会继续写入,最后直到服务器崩溃。 最近发现很多新手再谈cronolog,我便想起当年发生的故障,有必要跟大家分享。

首先日志是可以切割的,网上的例子理论上也是可行,但我们不能不求甚解,稀里糊涂的用下去。

我们首先了解一下日志是怎么产生的,那种日志可以切割,那些日志不能切割,为什么不能切割,如果需要切割日志怎么处理?

2.5.1.1. 日志是怎么产生的

日志生命周期,创建/打开日志文件,追加日志记录,关闭日志文件。请看下面伪代码。

		
main (){ 
	f = open(/tmp/prog.log) ...
	...
	f.append('DEBUG .............') 
	...
	f.append('INFO .............') 
	...
	f.append('WARN .............') 
	f.close()
}		
		
			

这个程序是顺序运行,每次运行都会经历,打开日志文件,追加日志记录,关闭日志文件,一个日志生命周期结束。 在完成日志生命周后,你就可以切割日志了。因为f.close()后日志文件已经被释放。

再看下面的程序

		
main (){ 
	f = open(/tmp/prog.log) 
	loop{ 
		... ... 
		f.append('DEBUG .............') 
		... 
		f.append('INFO .............') 
		... 
		f.append('WARN .............') 
		if(quit){ 
			break 
		} 
	} 
	f.close()
}		
		
			

这个程序就不同了,程序运行,打开日志文件,然后进入无穷循环,期间不断写入日志,接到退出命令才会关闭日志。 那么这个程序你就不能随便切割日志。你一旦修改了日志文件,程序将不能在写入日志到文件中。 这个程序切割日志的过程是这样的

		
split loop { prog run prog quit && mv /tmp/prog.log /tmp/prog.2016-05-05.log }		
		
			

再看下面的程序

		
main (){ 
	loop{ 
		f = open(/tmp/prog.log) 
		loop{ 
			...
			...
			f.append('DEBUG .............') 
			...
			f.append('INFO .............') 
			...
			f.append('WARN .............') 
			if(reload){ 
				break 
			} 
		} 
		f.close() 
	}
}		
		
			

这个程序多了一层循环,并加入了重载功能。这个程序怎样切割日志呢:

		
split loop { 
	prog run
	mv /tmp/prog.log /tmp/prog.YYYY-MM-DD.log
	prog reload 
}		
		
			

		
main (){ 
	loop{ 
		f = open(/tmp/prog.YYYY-MM-DD.log) 
		loop{ 
			...
			...
			f.append('DEBUG .............') 
			...
			f.append('INFO .............') 
			...
			f.append('WARN .............') 
			if(reload){ 
				break 
			} 
		} 
		f.close() 
	}
}			
		
			

如果你是程序猿,这个程序可以优化一下,日志文件名自动产生日期 /tmp/prog.YYYY-MM-DD.log 在reload时候重新创建或打开日志。

最操蛋写法,很多初学者会这么干,

		
log(type, msg){ 
	f = open(/tmp/prog.YYYY-MM-DD.log) 
	f.append(type, msg) 
	f.close() 
} 

main(){ ...
	...
	log('INFO','..............') ...
	...
	log('DEBUG','..............') ...
	... 
}		
		
			

这种代码的适应性非常强,写一个日志函数,但牺牲了IO性能,如果频繁打开/关闭文件同时进行写IO操作,这样的程序很难实现高并发。 所以很多高并发的程序,只会打开一次日志文件(追加模式),不会再运行期间关闭日志文件,直到进程发出退出信号。

2.5.1.2. 让我们看个究竟

我们手工模拟一次日志分割的过程,首先开启三个Shell终端。

第一种情况,日志文件被重命名
			
终端一,模拟打开日志文件

[root@www.netkiller.cn ~]# cat > /tmp/test.log
终端二,重命名文件

[root@www.netkiller.cn ~]# mv /tmp/test.log /tmp/test.2016.05.05.log
终端一,输入一些内容然后按下Ctrl+D 保存文件

[root@www.netkiller.cn ~]# cat > /tmp/test.log
Helloworld
Ctrl + D[root@www.netkiller.cn ~]# cat /tmp/test.log
cat: /tmp/test.log: No such file or directory			
			
				
第二种情况,日志文件被删除
			
终端一,模拟打开日志文件

[root@www.netkiller.cn ~]# cat > /tmp/test.log
终端二,使用lsof查看文件的打开情况

[root@www.netkiller.cn ~]# lsof | grep /tmp/test.log
cat       20032           root    1w      REG              253,1          0     288466 /tmp/test.log
终端三,删除日志文件

[root@www.netkiller.cn ~]# rm -rf /tmp/test.log
终端二,查看日志的状态,你能看到 deleted

[root@www.netkiller.cn ~]# lsof | grep /tmp/test.log
cat        5269           root    1w      REG              253,1          0     277445 /tmp/test.log (deleted)
终端一,回到终端一种,继续写入一些内容并保存,然后查看日志文件是否有日志记录被写入

[root@www.netkiller.cn ~]# cat > /tmp/test.log
Helloworld
^D[root@www.netkiller.cn ~]# cat /tmp/test.log
cat: /tmp/test.log: No such file or directory
经过上面两个实验,你应该明白了在日志打开期间对日志文件做重命名或者删除都会造成日志记录的写入失败。			
			
				
第三种情况,日志没有被删除,也没有被重命名,而是被其他程序做了修改
			
第一步,终端窗口一中创建一个文件,文件写入一些字符串,这里写入 “one”,然后查看是否成功写入。

[root@www.netkiller.cn ~]# echo one > /tmp/test.log[root@www.netkiller.cn ~]# cat /tmp/test.log
one
上面我们可以看到/tmp/test.log文件成功写入一个字符串”one”

第二步,开始追加一些字符串

[root@www.netkiller.cn ~]# cat > /tmp/test.log
two
先不要保存(不要发出^D)

第三部,在终端二窗口中清空这个文件

[root@www.netkiller.cn ~]# > /tmp/test.log [root@www.netkiller.cn ~]# cat /tmp/test.log
通过cat查看/tmp/test.log文件,什么也没也表示操作成功。

第四步,完成字符串追加,使用Ctrl+D保存文件,最后使用cat /tmp/test.log 查看内容。

[root@www.netkiller.cn ~]# cat > /tmp/test.log
two[root@www.netkiller.cn ~]# cat /tmp/test.log
你会发现/tmp/test.log文件中没有写入任何内容。这表示在日志的访问期间,如果其他程序修改了该日志文件,原来的程序将无法再写入日志。

让我们再来一次,看个究竟

终端一,创建并追加字符串到日志文件中

# echo one > /tmp/test.log# cat /tmp/test.logone# cat >> /tmp/test.logtwo
记得不要保存

终端二,使用lsof查看文件的打开情况

# lsof | grep /tmp/test.logcat       22631           root    1w      REG              253,1          0     277445 /tmp/test.log
终端三,开启另一个程序追加字符串到日志文件中

# cat >> /tmp/test.log three
先不要保存(不要发出^D)

终端二,查看文件的打开情况

# lsof | grep /tmp/test.logcat       22631           root    1w      REG              253,1          0     277445 /tmp/test.log
cat       23350           root    1w      REG              253,1          0     277445 /tmp/test.log
终端三,保存three字符串

# cat >> /tmp/test.log three
^D# cat /tmp/test.log three
回到终端一,继续保存内容

# cat > /tmp/test.logtwo
^D# cat /tmp/test.logtwo
e
出现新的行情况了,two报道最上面去了,这是因为打开文件默认文件指针是页首,它不知道最后一次文件写入的位置。

你可以反复实验,结果相同。

# cat /tmp/test.logtwo
e
four
five
我为什么没有使用 echo “five” » /tmp/test.log 这种方式追加呢?因为 cat 重定向后只要不发出^D就不会保存文件,而echo是打开文件,获取文件尾部位置,然后追加,最后关闭文件。
			
			
				

2.5.1.3. 经典案例分析

我们以 Nginx 为例

		
[root@www.netkiller.cn ~]# cat /etc/logrotate.d/nginx
/var/log/nginx/*.log {        daily
        missingok
        rotate 52
        compress
        delaycompress
        notifempty
        create 640 nginx adm
        sharedscripts
        postrotate                [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`        endscript}
		
		
			

nginx 日志切割后会运行 kill -USR1 这个让nginx 重新创建日志文件或者夺回日志文件的操作权。

2.5.1.4. 怎样监控日志

那么怎样监控日志被删除,写入权被其他程序夺取?对于程序猿一定很关注这个问题。下面我们讲解怎么监控日志。

Linux 系统可以使用 inotify 开发包来监控文件的状态变化,包括开打,写入,修改权限等等。你需要启动一个进程或者线程监控日志文件的变化,以便随时reload 你的主程序。

		
prog { sign = null
	logfile = /var/log/your.log
	
	thread monitor { 
		inotify logfile { sign = reload } 
	} 
	thread worker { 
		loop{ 
			f = open(logfile) 
			loop{ 
				f.append(....) 
				if(sign == reload) { break }
			} 
			f.close() 
		} 
	} 
	main(){ 
		monitor.start() 
		worker.start() 
	}
}		
		
			

不知你是否看懂,简单的说就是两个并行运行的程序,一个负责日志监控,一个负责干活,一旦日志发生变化就通知主程序 reload。 至于使用进程还是线程去实现,取决于你熟悉那种语言或者你擅长的技术。

2.5.1.5. 总结

小小的日志文件有如此大的学问,目前很多应用程序写的比较健壮,能够判断出当前日志被删除,改写。程序运行中能够在创建丢失的日志文件,当日志被其他程序改写后,能够夺回写入权。 但这样的程序会影响程序并发性能,鱼和熊掌不能兼得。看了这篇文章我想你应该对日志有了全面了解,也会在接下来的工作中谨慎处理日志。

2.5.2. 日志归档与数据挖掘

2.5.2.1. 什么日志归档

归档,是指将日志整理完毕且有保存价值的文件,经系统整理交日志服务器保存的过程。

2.5.2.2. 为什么要做日志归档

  • 随时调出历史日志查询。
  • 通过日志做数据挖掘,挖掘有价值的数据。
  • 查看应用程序的工作状态

2.5.2.3. 何时做日志归档

日志归档应该是企业规定的一项制度(“归档制度”),系统建设之初就应该考虑到日志归档问题。如果你的企业没有这项工作或制度,在看完本文后建议你立即实施。

2.5.2.4. 归档日志放在哪里

简单的可以采用单节点服务器加备份方案。

随着日志规模扩大,未来必须采用分布式文件系统,甚至涉及到远程异地容灾。

2.5.2.5. 谁去做日志归档

我的答案是日志归档自动化,人工检查或抽检。

2.5.2.6. 怎样做日志归档

将所有服务器的日志都汇总到一处,有几种方法

日志归档常用方法:

  • ftp 定是下载, 这种做法适合小文件且日志量不大,定是下载到指定服务器,缺点是重复传输,实时性差。
  • rsyslog 一类的程序,比较通用,但扩展不便。
  • rsync 定是同步,适合打文件同步,好于FTP,实时性差。
  • logstash, filebates, flume 等等。
系统日志
rsyslog/syslog-ng 实现日志集中管理
应用程序日志

应用程序中没有比较大量记录日志,当开启debug模式时才记录大量日志。

但是很多国内开发太过于依赖日志,导致日记非常臃肿

程序日志解决方案,请看软件架构相关章节

日志格式转换

首先我来介绍一种简单的方案

我用D语言写了一个程序将 WEB 日志正则分解然后通过管道传递给数据库处理程序

将日志放入数据库

将WEB服务器日志通过管道处理然后写入数据库

处理程序源码

				
$ vim match.d
import std.regex;
import std.stdio;
import std.string;
import std.array;

void main()
{
    // nginx
	//auto r = regex(`^(\S+) (\S+) (\S+) \[(.+)\] "([^"]+)" ([0-9]{3}) ([0-9]+) "([^"]+)" "([^"]+)" "([^"]+)"`);

	// apache2
	auto r = regex(`^(\S+) (\S+) (\S+) \[(.+)\] "([^"]+)" ([0-9]{3}) ([0-9]+) "([^"]+)" "([^"]+)"`);

	foreach(line; stdin.byLine)
	{

		foreach(m; match(line, r)){
			//writeln(m.hit);
			auto c = m.captures;
			c.popFront();
			//writeln(c);
			auto value = join(c, "\",\"");
			auto sql = format("insert into log(remote_addr,unknow,remote_user,time_local,request,status,body_bytes_sent,http_referer,http_user_agent,http_x_forwarded_for) value(\"%s\");", value );
			writeln(sql);
		}
	}
}
				
					

编译

$ dmd match.d
$ strip match

$ ls
match  match.d  match.o
				

简单用法

$ cat access.log | ./match
				

高级用法

				
$ cat access.log | match | mysql -hlocalhost -ulog -p123456 logging
				
					

实时处理日志,首先创建一个管道,寻该日志文件写入管道中。

cat  管道名 | match | mysql -hlocalhost -ulog -p123456 logging
				

这样就可以实现实时日志插入。

[提示]提示

上面程序稍加修改即可实现Hbase, Hypertable 本版

Apache Pipe

Apache 日志管道过滤 CustomLog "| /srv/match >> /tmp/access.log" combined

				
<VirtualHost *:80>
        ServerAdmin webmaster@localhost

        #DocumentRoot /var/www
        DocumentRoot /www
        <Directory />
                Options FollowSymLinks
                AllowOverride None
        </Directory>
        #<Directory /var/www/>
        <Directory /www/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
                Order allow,deny
                allow from all
        </Directory>

        ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
        <Directory "/usr/lib/cgi-bin">
                AllowOverride None
                Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
                Order allow,deny
                Allow from all
        </Directory>

        ErrorLog ${APACHE_LOG_DIR}/error.log

        # Possible values include: debug, info, notice, warn, error, crit,
        # alert, emerg.
        LogLevel warn

        #CustomLog ${APACHE_LOG_DIR}/access.log combined
        CustomLog "| /srv/match >> /tmp/access.log" combined

    Alias /doc/ "/usr/share/doc/"
    <Directory "/usr/share/doc/">
        Options Indexes MultiViews FollowSymLinks
        AllowOverride None
        Order deny,allow
        Deny from all
        Allow from 127.0.0.0/255.0.0.0 ::1/128
    </Directory>

</VirtualHost>
				
					

经过管道转换过的日志效果

				
$ tail /tmp/access.log
insert into log(remote_addr,unknow,remote_user,time_local,request,status,body_bytes_sent,http_referer,http_user_agent,http_x_forwarded_for) value("192.168.6.30","-","-","21/Mar/2013:16:11:00 +0800","GET / HTTP/1.1","304","208","-","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22");
insert into log(remote_addr,unknow,remote_user,time_local,request,status,body_bytes_sent,http_referer,http_user_agent,http_x_forwarded_for) value("192.168.6.30","-","-","21/Mar/2013:16:11:00 +0800","GET /favicon.ico HTTP/1.1","404","501","-","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22");
insert into log(remote_addr,unknow,remote_user,time_local,request,status,body_bytes_sent,http_referer,http_user_agent,http_x_forwarded_for) value("192.168.6.30","-","-","21/Mar/2013:16:11:00 +0800","GET / HTTP/1.1","304","208","-","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22");
				
					
Log format

通过定义LogFormat可以直接输出SQL形式的日志

Apache

LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
				

Nginx

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
				

但对于系统管理员使用grep,awk,sed,sort,uniq分析时造成一定的麻烦。所以我建议仍然采用正则分解

产生有规则日志格式,Apache:

LogFormat \
        "\"%h\",%{%Y%m%d%H%M%S}t,%>s,\"%b\",\"%{Content-Type}o\",  \
        \"%U\",\"%{Referer}i\",\"%{User-Agent}i\""
				

将access.log文件导入到mysql中

LOAD DATA INFILE '/local/access_log' INTO TABLE tbl_name
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' ESCAPED BY '\\'
				
日志导入到 MongoDB
# rpm -Uvh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
# yum install mongodb
				

D语言日志处理程序

				
import std.regex;
//import std.range;
import std.stdio;
import std.string;
import std.array;

void main()
{
	// nginx
	auto r = regex(`^(\S+) (\S+) (\S+) \[(.+)\] "([^"]+)" ([0-9]{3}) ([0-9]+) "([^"]+)" "([^"]+)" "([^"]+)"`);
	// apache2
	//auto r = regex(`^(\S+) (\S+) (\S+) \[(.+)\] "([^"]+)" ([0-9]{3}) ([0-9]+) "([^"]+)" "([^"]+)"`);
	foreach(line; stdin.byLine)
	{
		//writeln(line);
		//auto m = match(line, r);
		foreach(m; match(line, r)){
			//writeln(m.hit);
			auto c = m.captures;
			c.popFront();
			//writeln(c);
			/*
			SQL
			auto value = join(c, "\",\"");
			auto sql = format("insert into log(remote_addr,unknow,remote_user,time_local,request,status,body_bytes_sent,http_referer,http_user_agent,http_x_forwarded_for) value(\"%s\");", value );
			writeln(sql);
			*/
			// MongoDB
			string bson = format("db.logging.access.save({
						'remote_addr': '%s',
						'remote_user': '%s',
						'time_local': '%s',
						'request': '%s',
						'status': '%s',
						'body_bytes_sent':'%s',
						'http_referer': '%s',
						'http_user_agent': '%s',
						'http_x_forwarded_for': '%s'
						})",
						c[0],c[2],c[3],c[4],c[5],c[6],c[7],c[8],c[9]
						);
			writeln(bson);

		}
	}

}
				
					

编译日志处理程序

dmd mlog.d
				

用法

cat /var/log/nginx/access.log | mlog | mongo 192.169.0.5/logging -uxxx -pxxx
				

处理压错过的日志

# zcat /var/log/nginx/*.access.log-*.gz | /srv/mlog | mongo 192.168.6.1/logging -uneo -pchen
				

实时采集日志

tail -f /var/log/nginx/access.log | mlog | mongo 192.169.0.5/logging -uxxx -pxxx
				
日志中心方案

上面的方案虽然简单,但太依赖系统管理员,需要配置很多服务器,每种应用软件产生的日志都不同,所以很复杂。如果中途出现故障,将会丢失一部日志。

于是我又回到了起点,所有日志存放在自己的服务器上,定时将他们同步到日志服务器,这样解决了日志归档。远程收集日志,通过UDP协议推送汇总到日志中心,这样解决了日志实时监控、抓取等等对实时性要求较高的需求。

为此我用了两三天写了一个软件,下载地址:https://github.com/netkiller/logging

这种方案并不是最佳的,只是比较适合我的场景,而且我仅用了两三天就完成了软件的开发。后面我会进一步扩展,增加消息队列传送日志的功能。

软件安装
$ git clone https://github.com/netkiller/logging.git
$ cd logging
$ python3 setup.py sdist
$ python3 setup.py install
				
节点推送端

安装启动脚本

CentOS

# cp logging/init.d/ulog /etc/init.d			
				

Ubuntu

$ sudo cp init.d/ulog /etc/init.d/	

$ service ulog 
Usage: /etc/init.d/ulog {start|stop|status|restart}			
				

配置脚本,打开 /etc/init.d/ulog 文件

配置日志中心的IP地址

HOST=xxx.xxx.xxx.xxx
				

然后配置端口与采集那些日志

				
	done << EOF
1213 /var/log/nginx/access.log
1214 /tmp/test.log
1215 /tmp/$(date +"%Y-%m-%d.%H:%M:%S").log
EOF
				
					

格式为

Port | Logfile
------------------------------
1213 /var/log/nginx/access.log
1214 /tmp/test.log
1215 /tmp/$(date +"%Y-%m-%d.%H:%M:%S").log
				

1213 目的端口号(日志中心端口)后面是你需要监控的日志,如果日志每日产生一个文件写法类似 /tmp/$(date +"%Y-%m-%d.%H:%M:%S").log

[提示]提示
每日产生一个新日志文件需要定时重启 ulog 方法是 /etc/init.d/ulog restart

配置完成后启动推送程序

# service ulog start
				

查看状态

$ service ulog status
13865 pts/16   S      0:00 /usr/bin/python3 /usr/local/bin/rlog -d -H 127.0.0.1 -p 1213 /var/log/nginx/access.log				
				

停止推送

# service ulog stop				
				
日志收集端
# cp logging/init.d/ucollection /etc/init.d

# /etc/init.d/ucollection 
Usage: /etc/init.d/ucollection {start|stop|status|restart}
				

配置接收端口与保存文件,打开 /etc/init.d/ucollection 文件,看到下面段落

				
done << EOF
1213 /tmp/nginx/access.log
1214 /tmp/test/test.log
1215 /tmp/app/$(date +"%Y-%m-%d.%H:%M:%S").log
1216 /tmp/db/$(date +"%Y-%m-%d")/mysql.log
1217 /tmp/cache/$(date +"%Y")/$(date +"%m")/$(date +"%d")/cache.log
EOF
				
					

格式如下,表示接收来自1213端口的数据,并保存到/tmp/nginx/access.log文件中。

Port | Logfile
1213 /tmp/nginx/access.log
				

如果需要分割日志配置如下

1217 /tmp/cache/$(date +"%Y")/$(date +"%m")/$(date +"%d")/cache.log
				

上面配置日志文件将会产生在下面的目录中

$ find /tmp/cache/
/tmp/cache/
/tmp/cache/2014
/tmp/cache/2014/12
/tmp/cache/2014/12/16
/tmp/cache/2014/12/16/cache.log
				
[提示]提示
同样,如果分割日志需要重启收集端程序。

启动收集端

# service ulog start	
				

停止程序

# service ulog stop			
				

查看状态

$ init.d/ucollection status
12429 pts/16   S      0:00 /usr/bin/python3 /usr/local/bin/collection -d -p 1213 -l /tmp/nginx/access.log
12432 pts/16   S      0:00 /usr/bin/python3 /usr/local/bin/collection -d -p 1214 -l /tmp/test/test.log
12435 pts/16   S      0:00 /usr/bin/python3 /usr/local/bin/collection -d -p 1215 -l /tmp/app/2014-12-16.09:55:15.log
12438 pts/16   S      0:00 /usr/bin/python3 /usr/local/bin/collection -d -p 1216 -l /tmp/db/2014-12-16/mysql.log
12441 pts/16   S      0:00 /usr/bin/python3 /usr/local/bin/collection -d -p 1217 -l /tmp/cache/2014/12/16/cache.log
				
日志监控

监控来自1217宽口的数据

$ collection -p 1213

192.168.6.20 - - [16/Dec/2014:15:06:23 +0800] "GET /journal/log.html HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36"
192.168.6.20 - - [16/Dec/2014:15:06:23 +0800] "GET /journal/docbook.css HTTP/1.1" 304 0 "http://192.168.6.2/journal/log.html" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36"
192.168.6.20 - - [16/Dec/2014:15:06:23 +0800] "GET /journal/journal.css HTTP/1.1" 304 0 "http://192.168.6.2/journal/log.html" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36"
192.168.6.20 - - [16/Dec/2014:15:06:23 +0800] "GET /images/by-nc-sa.png HTTP/1.1" 304 0 "http://192.168.6.2/journal/log.html" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36"
192.168.6.20 - - [16/Dec/2014:15:06:23 +0800] "GET /js/q.js HTTP/1.1" 304 0 "http://192.168.6.2/journal/log.html" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36"
				

启动后实时将最新日志传送过来

2.5.3. 日志中心规划

2.5.3.1. 目前的方案

目前方案可能满足基本的日志需求

spring 容器 –> /mnt/logs/project 产生日志文件 –> filebeat 将 /mnt/logs/project 挂载到 /logs 作为 input 输出 –> logstash -> elasticsearch –> kibana

2.5.3.2. 优化后方案

logback 自带 logstash 发送功能,不需要再经过 filebeat 一层转发。

日志分级

目前所有日志输出在一个文件中,不区分类别,混在一起。

日志分级之后主要有三类:

  1. 系统类日志:操作系统产生的/var/log

  2. 应用服务器类日志:nginx、spring 包括开发过程中用户调试产生的日志……

  3. 业务日志:用户产生的日志,例如下单、充值……

业务日志应用案例举例:用户充值 100 元,会产生业务日志

2022-07-30 11:23:00 用户 AAAA 充值 100,账户余额 50 元,充值后 150元
2022-07-30 11:23:00 用户 AAAA 充值 30,账户余额 150 元,充值后 180元

当系统收到黑客攻击,用户余额被恶意更改,此时就可能通过日志核查用户充值情况。业务日志输出要每笔清晰干净,不能与开发调试信息混在一起。

日志查询

日志提供两种日志查询方案:

  1. 文件方案

  2. kibana 方案

工作中观察到并不是所有人都习惯或喜欢使用 kibana,更多的人更习惯使用 tail -f , grep 等 linux 观察日志,所以我们会提供两种查询方式。

所有的日志都会同步到指定的日志中心服务器上。

日志归档

有关部门会要求公司日志必须保留一定时间,我们自己也需要。

生产环境日志保留一个月,对于冷日志,失去时效的日志,几乎不会在看的日志,每天同步到办公室的备份服务器上,压缩打包存放到 NAS 中。

当需时,重新解压展开。

日志监控

与监控系统联动,在日志中埋点,分析日志埋点,通过仪表板,反应监控状态。