日志
2024-08-25
https://en.wikipedia.org/wiki/Extended_Log_Format
相比 Common Log Format (通用日志格式),ELF 是名副其实的可拓展:在头部声明了版本,以及字段。
例如:
#Version: 1.0
#Date: 12-Jan-1996 00:00:00
#Fields: time cs-method cs-uri
00:34:23 GET /foo/bar.html
12:21:16 GET /foo/bar.html
12:45:52 GET /foo/bar.html
12:57:34 GET /foo/bar.html
- Version:
<integer>.<integer>
The version of the extended log file format used. This draft defines version 1.0.
- Fields:
[<specifier>...]
Specifies the fields recorded in the log.
- Software: string
Identifies the software which generated the log.
- Start-Date:
<date> <time>
The date and time at which the log was started.
- End-Date:
<date> <time>
The date and time at which the log was finished.
- Date:
<date> <time>
The date and time at which the entry was added.
- Remark:
<text>
Comment information. Data recorded in this field should be ignored by analysis tools.
然后这个字段的声明又有一套规则,可以参考
日志
2024-08-12
- NCSA HTTPd (Apache HTTP Server 前身) 定义的一个标准 Web 服务器日志格式。
- 格式:
host ident authuser date request status bytes
例如:127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326
- 如果哪一个字段没有值,就用
-
代替。
import re
from datetime import datetime
RE_CLF = re.compile(r'(\S+) (\S+) (\S+) \[(.*?)\] "(.*?)" (\d{3}) (\d+|-)')
def parse_clf(log_line):
match = RE_CLF.match(log_line)
if not match:
raise ValueError('Log line does not match CLF format')
ip_address = match.group(1)
identity = match.group(2)
user = match.group(3)
time_str = match.group(4)
request_line = match.group(5)
status_code = int(match.group(6))
size = match.group(7)
time_format = '%d/%b/%Y:%H:%M:%S %z'
timestamp = datetime.strptime(time_str, time_format)
size = int(size) if size != '-' else None
return {
'host': ip_address,
'ident': identity,
'authuser': user,
'date': timestamp,
'request': request_line,
'status': status_code,
'bytes': size,
}
log_example = '127.0.0.1 user-identifier frank [10/Oct/2000:13:55:36 -0700] 'GET /apache_pb.gif HTTP/1.0' 200 2326'
parsed_log = parse_clf(log_example)
print(parsed_log)
# {'host': '127.0.0.1', 'ident': 'user-identifier', 'authuser': 'frank',
# 'date': datetime.datetime(2000, 10, 10, 13, 55, 36, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200))),
# 'request': 'GET /apache_pb.gif HTTP/1.0', 'status': 200, 'bytes': 2326}
日志
2022-10-07
简单的日志系统
我还没有用过 ELK 这样的系统(只实验性使用过 Graylog),使用过这些日志管理方案:
- 日志就通过文件存放在服务器上,然后登录服务器进行日志文件分析,排查问题。
- 再进一步就是,将日志文件定期 rsync 到一台服务器上,方便日志管理与日志搜索。
-
服务直接写 syslog,然后配置好 rsyslog 同步就行。
graph LR
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(应用程序):::process -->|写入日志| B(syslog 接口):::process
B -->|发送日志消息| C(本地 rsyslog 守护进程):::process
C -->|存储本地日志| D(本地日志文件):::process
C -->|配置转发规则| E{是否转发到远程?}:::process
E -->|是| F(通过 UDP/TCP 发送):::process
F -->|接收日志消息| G(远程 rsyslog 守护进程):::process
G -->|存储远程日志| H(远程日志文件):::process
E -->|否| D
-
不要忘了,日志写入数据库在某些场景下也是一个可选方案,比如内部管理系统的登录日志、操作日志等。
理论上日志可以直接写入远程日志系统,但是我想应该不会有线上服务这样做。网络稳定性问题(可能丢失日志、服务阻塞)、性能消耗、增加系统复杂性等。
syslog 是系统提供的日志接口,rsyslog 提供了 TCP 日志可靠传输、本地日志队列功能,而且是 Linux 世界广泛采用的基础服务,我觉得可以接受。
日志系统
-
ELK / EFK:
Elasticsearch # 搜索引擎
Logstash # 日志采集、过滤、预处理
Kabana # 数据可视化
Filebeat # Logstash 替代方案,更加轻量级
graph LR
Logs --> Filebeat --> ES
Logs --> Logstash --> ES
-
FELK:Filebeat 将日志采集到 Logstash,处理之后导入 ES
graph LR
Logs --> Filebeat --> Logstash --> ES
-
FELK + Kafka
Filebeat 将日志采集到 Kafka,再由 Logstash 从 Kafka 读取日志,处理完成之后导入 ES
graph LR
Logs --> Filebeat --> Kafka --> Logstash --> ES
-
Graylog:
Filebeat
Graylog Sidebar
Graylog
Elasticsearch
MongoDB
-
LPG:
Loki # 搜索引擎
Promtail # 日志采集、过滤、预处理
Grafana # 数据可视化
设计
graph TD
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A(服务):::process -->|生成日志| B(rsyslog):::process
B --> C(Kafka):::process
C --> D(ELK):::process
- 服务写 syslog,通过 rsyslog 同步到
接收,解析,处理,转换,格式化
架构 日志 Loki
2021-12-05
Loki
Grafana 公司出品的一个日志系统。才出来没两年,是一个相对较年轻的项目,不过已经有一定知名度了。
业界最为知名的日志系统是 ELK,它对日志做全文索引,搜索起来最快、最灵活,同时大量索引导致存储成本相对较高。
Loki 则将日志分成时间戳、标签、正文三部分,标签就是索引,存储在
Promtail
Grafana
Grafana 是一个数据面板,常用于监控系统。它本身不会收集和存储数据,而是通过接入其他数据源来实现。
通过内置的插件,Loki 可以支持各种关系型数据库和时序数据库(Zabbix 一般配套使用 MySQL 做存储,Prometheus 本身就可以认为是一个时序数据库),也支持 Loki,Elasticsearch 这样的数据源。
实验
Install Loki & Promtail
# 获取最新版本号
# LOKI_VERSION=$(curl -s https://api.github.com/repos/grafana/loki/releases/latest | jq -r .tag_name)
LOKI_VERSION=$(curl -s https://api.github.com/repos/grafana/loki/releases/latest | grep -Po '"tag_name": "\Kv[0-9.]+')
# 下载 loki & promtail
curl -O -L "https://github.com/grafana/loki/releases/download/${LOKI_VERSION}/loki-linux-amd64.zip"
curl -O -L "https://github.com/grafana/loki/releases/download/${LOKI_VERSION}/promtail-linux-amd64.zip"
# loki : 18M -> 57M
# promtail: 21M -> 74M
# 解压 & 设置
unzip loki-linux-amd64.zip promtail-linux-amd64.zip
sudo mv -n loki-linux-amd64 /usr/local/bin/loki
sudo mv -n promtail-linux-amd64 /usr/local/bin/promtail
# chmod a+x /usr/local/bin/{loki,promtail} # already 755
# 下载配置文件
sudo -E wget -qO /etc/loki.config.yaml "https://raw.githubusercontent.com/grafana/loki/${LOKI_VERSION}/cmd/loki/loki-local-config.yaml"
sudo -E wget -qO /etc/promtail.config.yaml "https://raw.githubusercontent.com/grafana/loki/${LOKI_VERSION}/clients/cmd/promtail/promtail-local-config.yaml"
ls -l /etc/{loki,promtail}.config.yaml
# 启动 loki
loki -config.file /etc/loki.config.yaml
# 在另一个终端查看
browse http://localhost:3100/metrics
# 启动 promtail
Install Grafana
Install on Debian or Ubuntu
sudo apt-get install -y apt-transport-https software-properties-common wget
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
# Bate 版本
# echo "deb https://packages.grafana.com/oss/deb beta main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
sudo apt-get update
sudo apt-get install -y grafana
# 无法创建主目录"/usr/share/grafana"
# sudo systemctl daemon-reload
# sudo systemctl enable grafana-server
sudo systemctl start grafana-server
browse http://localhost:3000
参考资料与拓展阅读
Python logging 日志
2021-02-08
:) 本文正在编辑中,暂时不提供浏览...
日志 Graylog
2021-01-06
ssh // 包含 ssh
ssh login // 包含 ssh 或者 login
"ssh login" // 包含 ssh login
// 正则匹配
/ethernet[0-9]+/
type:(ssh OR login) // 字段 type 包含 ssh 或者 login
// 必须 (不) 包含字段
_exists_:type
NOT _exists_:type
// 支持 `*`, `?` 两种通配符
source:xxx?yyy
source:xxx*yyy
// PS: 默认配置,通配符不能放在最前面,避免内存消耗太多
// 可以这样开启:
// allow_leading_wildcard_searches = true
// 模糊匹配 ~
ssh logni~ // 可以搜索到 ssh login
"foo bar"~5 // Damerau–Levenshtein distance
// 范围
http_response_code:[500 TO 504]
http_response_code:{400 TO 404}
bytes:{0 TO 64]
http_response_code:[0 TO 64}
// 大小
http_response_code:>400
http_response_code:<400
http_response_code:>=400
http_response_code:<=400
http_response_code:(>=400 AND <500)
// 时间范围
timestamp:["2019-07-23 09:53:08.175" TO "2019-07-23 09:53:08.575"]
otherDate:[now-5d TO now-4d]
此外,还有 AND,OR,NOT,括号等逻辑运算符可用。
转义符:
& | : \ / + - ! ( ) { } [ ] ^ " ~ * ?
参考资料与拓展阅读
Linux 日志
2020-06-23
查看这个进程打开了哪写文件
-> % ps -ef | grep rsyslogd | grep -Fv grep
syslog 1068 1 0 1月06 ? 00:00:03 /usr/sbin/rsyslogd -n -iNONE
-> % sudo ls -l /proc/1068/fd
总计 0
lr-x------ 1 root root 64 1月 8 16:54 0 -> /dev/null
l-wx------ 1 root root 64 1月 8 16:54 1 -> /dev/null
l-wx------ 1 root root 64 1月 8 16:54 10 -> /var/log/kern.log
l-wx------ 1 root root 64 1月 8 16:54 11 -> /var/log/auth.log
l-wx------ 1 root root 64 1月 8 16:54 2 -> /dev/null
lrwx------ 1 root root 64 1月 6 09:46 3 -> 'socket:[13299]'
lr-x------ 1 root root 64 1月 8 16:54 4 -> /dev/urandom
lrwx------ 1 root root 64 1月 8 16:54 5 -> 'socket:[27825]'
lrwx------ 1 root root 64 1月 8 16:54 6 -> 'socket:[27831]'
lr-x------ 1 root root 64 1月 8 16:54 7 -> /proc/kmsg
lrwx------ 1 root root 64 1月 8 16:54 8 -> 'socket:[22468]'
l-wx------ 1 root root 64 1月 8 16:54 9 -> /var/log/syslog
查看哪些进程打开了这个文件
-> % sudo find /proc/*/fd -ls | grep /var/log/syslog
2551982 0 l-wx------ 1 root root 64 1月 8 16:50 /proc/1068/fd/9 -> /var/log/syslog
-> % ps -fq 1068
UID PID PPID C STIME TTY TIME CMD
syslog 1068 1 0 1月06 ? 00:00:03 /usr/sbin/rsyslogd -n -iNONE
Python 日志 logging
2020-05-21
logging 内部的服务级别:
DEBUG 10
INFO 20
WARNING 30
ERROR 40
CRITICAL 50
根据使用习惯,INFO 是重要信息,DEBUG 是普通信息。线上也是开到 DEBUG 级别。
然后调试信息也是通过 DEBUG 服务打印,然后通过 conf.DEBUG_MODE
来区分是不是要打印这种 DEBUG 级别的调试信息。
觉得不甚方便,想了一下,有两种思路:
- 将普通信息也通过 INFO 日志打印,在日志内容中插入部分标识来表示是重要信息,比如 “&NOTICE”。
- 增加一个 TRACE 级别日志。
方案一感觉相对合理一些,但是对于已有项目还是方案二好。
实现
def trace(self, message, *args, **kwargs):
if self.isEnabledFor(TRACE):
self._log(TRACE, message, args, **kwargs)
TRACE = logging.TRACE = 5
logging.addLevelName(TRACE, 'TRACE')
logging.Logger.trace = trace
参考:syslog 日志级别
- EMERG:系统不可用
- ALERT:需要立即采取行动
- CRIT:关键错误
- ERR:一般错误
- WARNING:警告
- NOTICE:一般通知
- INFO:信息性消息
- DEBUG:调试级别的消息
参考:nodejs winston 日志级别
- error:错误
- warn:警告
- info:一般信息
- http:HTTP 请求
- verbose:详细信息
- debug:调试信息
- silly:非常详细的调试信息
参考:java log4j 日志级别
- FATAL:致命
- ERROR:错误
- WARN:警告
- INFO:信息
- DEBUG:调试
- TRACE:跟踪
日志 Linux
2018-05-03
配置
# cat /etc/logrotate.conf
weekly
su root adm
rotate 4
create
#dateext
#compress
include /etc/logrotate.d
以 Nginx 配置为例:
# cat /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily # 按日切割
missingok # 如果文件不存在,则不创建
rotate 14 # 最多保留 14 个日志文件
compress # 压缩
delaycompress # 延迟压缩
notifempty # 如果文件为空,则不创建
create 0640 www-data adm
# 可能一次切割多个日志,
# 但是后面遇到的每个脚本都只执行一次,
# 在所有日志切割之前或之后
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate
invoke-rc.d nginx rotate >/dev/null 2>&1
endscript
}
其他常用选项:
dateext
部分日志需要添加日期后缀
lastaction/endscript
最后执行的指令,很有用,比如最后将日志备份到某些地方
比如:
rsync -au *.gz 192.168.64.234:/backup/nginx-logs/`hostname -s`/
参考资料与拓展阅读
Python logging 日志
2014-07-16
一个小问题。