#9 Extensible Log Format

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.

然后这个字段的声明又有一套规则,可以参考

#8 Common Log Format (通用日志格式)

2024-08-12
  1. NCSA HTTPd (Apache HTTP Server 前身) 定义的一个标准 Web 服务器日志格式。
  2. 格式: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
  3. 如果哪一个字段没有值,就用 - 代替。
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}

#7 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

参考资料与拓展阅读

#5 Graylog Search 规则

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,括号等逻辑运算符可用。

转义符:

& | : \ / + - ! ( ) { } [ ] ^ " ~ * ?

参考资料与拓展阅读

#4 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

#3 logging 增加 TRACE 级别日志

2020-05-21

logging 内部的服务级别:

DEBUG       10
INFO        20
WARNING     30
ERROR       40
CRITICAL    50

根据使用习惯,INFO 是重要信息,DEBUG 是普通信息。线上也是开到 DEBUG 级别。
然后调试信息也是通过 DEBUG 服务打印,然后通过 conf.DEBUG_MODE 来区分是不是要打印这种 DEBUG 级别的调试信息。

觉得不甚方便,想了一下,有两种思路:

  1. 将普通信息也通过 INFO 日志打印,在日志内容中插入部分标识来表示是重要信息,比如 “&NOTICE”。
  2. 增加一个 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:跟踪

#2 Logrotate

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`/

参考资料与拓展阅读