若能避开猛烈的欢喜,自然也不会有悲痛的来袭。

揭秘Linux日志分析利器 - 全面透析journalctl

目录 展开

一、前言

journalctl是一个功能强大的命令行工具,用于查看和管理系统日志,可以深入了解系统的运行状况、故障信息和关键事件。它也是systemd日志记录器(systemd-journald)的一部分,systemd-journald是systemd守护进程的一个组件,负责收集、存储和检索系统日志,甚至可以将journalctl的日志上报给ELK。

本文将介绍journalctl的基本概念、用法和常见的使用场景。将详细讨论如何使用journalctl来查看和过滤日志消息,以及如何通过搜索和格式化选项来定位特定的日志内容。此外还将探讨如何使用journalctl来追踪实时日志并进行分页浏览,以便及时监控系统的运行状态。

二、过滤选项及其作用

不指定来源日志来源选项默认会显示用户可以看到的所有日志记录。

1.指定日志来源(–system,–user)

–system,显示来自系统服务和内核的日志;

–user,显示来自当前用户可以看到的日志。

2.指定时间范围查找(-S,–since,-U,–until)

-S为从某个时间开始,-U为截止到某个时间。

时间格式为标准的年月日时分秒(YYYY-MM-DD HH:MM:SS):“2023-05-27 18:00:00”。

如果不指定,则假定为从"00:00:00"开始,同时,还支持使用字符串的模式,比如"yesterday"、"today"、"tomorrow"分别表示昨天、今天、明天(当前时间的后一天),详细用法可通过man systemd.time查阅。

比如,显示从昨天到现在的日志:

journalctl -S "yesterday"

显示指定时间点到现在的日志:

journalctl -S "2023-05-21 18:00:00"

指定时间范围内的日志,比如查找从5月21到昨天的sshd服务日志:

journalctl -S "2023-05-21 18:00:00" -U yesterday -u sshd # -u 后面接服务名

查找2023年1月份的prometheus服务的日志:

journalctl -u prometheus -S "2023-01-01 00:00:00" -U "2023-01-31 23:59:59"

3.查询特定引导ID的日志(-b,–boot)

显示来自特定启动时的日志。

-0或者为空表示本次系统的日志:

 journalctl -b -0 # 显示本次系统启动时的日志

-1 表示上一次的系统启动的日志:

journalctl -b -1

-2就是上两次,顺序依次类推,那么同理,指定-b后,可以查找特定服务在上次启动后产生的日志,比如显示prometheus服务在上次系统启动后产生的日志可以是:

journalctl -b -1 -u prometheus

–list-boots可显示所有boot id列表:

journalctl --list-boots

4.查询指定服务的日志(-u, –unit)

-u将指定systemd unit服务单元。

查找sshd服务的日志:

journalctl -u sshd

查询grafana服务的日志,并指定时间范围为今天到现在:

journalctl -u grafana --since "yesterday"

5.查找用户级别的服务日志(–user-unit)

这部分服务在systemd的user unit下管理,可通过systemctl –user list-units来展示用户级别下单元服务。

不指定这个–user-unit参数也没问题,默认是系统级别的日志,都会展示出来。

查找dbus服务日志:

journalctl --user-unit=dbus.socket

6.查找特定标识符的日志(-t, –identifier)

如果是自己写的程序,这个标识符是可自定义的,比如下面这段代码:

import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler())
logger.setSyslogIdent("app")

假设这个程序服务名为"my-test-app",但最后一条设置的日志标识符是"app",那么通过-t参数查找标识符时应该指定"app",而不是"my-test-app"。

默认情况下,日志标识符大多数都是程序名本身,比如查找标识符为prometheus的日志:

journalctl -t 'prometheus'

-u指定prometheus服务是有区别的,-u会记录整个服务生命周期开始到结束产生的日志,而-t只查找指定标识符产生的日志。

7.查找特定优先级的日志(-p, –priority)

-p可以精准的将各个优先级日志分门别类筛选出来,按消息优先级或优先级范围过滤输出。

取一个单一的数字或文本日志级别(即在0/"emerg "和7/"debug "之间),或一个数字/文本日志级别的范围,形式为FROM..TO,比如0..3表示取0到3级的日志。

日志等级一共分为如下8个级别:

数值优先级参数值描述示例
0EmergencyemergSystem is unusable(系统不可用)Severe Kernel BUG, systemd dumped core.This level should not be used by applications.
1AlertalertShould be corrected immediately(应立即纠正)Vital subsystem goes out of work. Data loss.
kernel: BUG: unable to handle kernel paging request at ffffc90403238ffc
2CriticalcritCritical conditions(危机状态)Crashes, coredumps. Like familiar flash:
systemd-coredump[25319]: Process 25310 (plugin-containe) of user 1000 dumped core
Failure in the system primary application, like X11.
3ErrorerrError conditions(错误状态)Not fatal error reported:
kernel: usb 1-3: 3:1: cannot get freq at ep 0x84,
systemd[1]: Failed unmounting /var.,
libvirtd[1720]: internal error: Failed to initialize a valid firewall backend.
4WarningwarningWarning conditions(警告状态)A non-root file system has only 1GB free.
org.freedesktop. Notifications[1860]: (process:5999): Gtk-WARNING **: Locale not supported by C library. Using the fallback 'C' locale.
5NoticenoticeNormal but significant condition(正常但值得注意的情况)systemd[1]: var.mount: Directory /var to mount over is not empty, mounting anyway. gcr-prompter[4997]: Gtk: GtkDialog mapped without a transient parent. This is discouraged.
6InformationalinfoNormal operational messages that require no action(无需任何操作的正常信息)lvm[585]: 7 logical volume(s) in volume group "archvg" now active.
7Debugdebugdebug-level messages(debug调试级别)kdeinit5[1900]: powerdevil: Scheduling inhibition from ":1.14" "firefox" with cookie 13 and reason "screen"

日志等级详细可参考 RFC 5424 6.2.1

查找sshd服务Error级别的日志:

journalctl -p 3 -u sshd

查找Emergency级别的所有日志:

journalctl -p emerg

查找fail2ban服务0(Emergency)到5(Notice)等级的日志:

journalctl -p 0..5 -u fail2ban

查找上一次系统启动,标识符为"kernel"的0到2级以及第4级的日志:

journalctl -b -1 -p 0..2 -p 4 -t 'kernel'

8.按照日志设备进行过滤和查询(–facility)

这里的设备是指生成日志消息的系统组件或服务。

通过journalctl –facility=help可以看到当前有哪些设备。

常用设备解读:

  • kernel:内核产生的日志消息。
  • user:与用户操作和登录相关的日志消息。
  • mail:与邮件系统相关的日志消息。
  • auth:与身份验证和授权相关的日志消息。
  • syslog:由 syslog 守护程序生成的日志消息。
  • lpr:与打印系统相关的日志消息。
  • news:与新闻服务器相关的日志消息。
  • uucp:与 UUCP(Unix to Unix Copy)系统相关的日志消息。
  • cron:与定时任务(cron)相关的日志消息。
  • authpriv:与身份验证和授权的私有信息相关的日志消息。
  • ftp:与文件传输协议(FTP)服务器相关的日志消息。
  • ntp:与网络时间协议(NTP)服务器相关的日志消息。

比如,我想知道上一次启动内核产生的日志:

journalctl -b -1 --facility=kern

又或者想知道身份验证和授权相关的日志,并且debug级别输出:

journalctl --facility=auth -p 7

可以看到很多sshd登录失败的日志,此系统被ssh暴力穷举过,但显然都失败了,如果有安全防护需求,可以参考我写的fail2ban配置说明

也可以通过authpriv查找与身份验证和授权的私有信息相关的日志消息:

journalctl --facility=authpriv -p 7

会显示系统内部一些私有日志信息,比如pam模块的验证日志。

从输出可以看到,在用户名这一层已经校验未找到,不会继续往下校验密码字段了。

9.使用正则表达式过滤日志(-g,–grep)

作用域是MESSAGE字段的内容,支持Perl正则,可以通过man pcre2pattern来查看具体语法。

同时,如果写的表达式都是小写,那就不区分大小写,如果包含大写就会区分大小写,如果不想区分大小写可以使用–case-sensitive=false参数来生效,比如下面的几种情况:

  • –grep "abc",是不区分大小写的;
  • –grep "Abc",区分大小写,只过滤匹配Abc的日志;
  • –grep "Abc" –case-sensitive=false,仍然不区分大小写。

过滤sshd服务错误用户名密码的日志:

journalctl -u sshd --grep 'Failed password'

过滤优先级为0-3并且包含关键词"invalid"、"timed out"、"not"的日志:

journalctl -b -p 0..3 -g "invalid|timed out|not"

过滤prometheus服务的master节点的错误日志:

journalctl -u prometheus.service --grep '(?i)Web master node.*error'

这里用到了(?i),正则里面的不区分大小写的作用,不要误以为上面的大小写逻辑有问题。

10.显示内核日志(-k, –dmesg)

此参数将只显示内核级别的日志:

journalctl -k

仔细看和dmesg打印的日志结果是一样的,但机制来源不一样,journalctl -k会从systemd-journald服务收集的日志中过滤出内核日志,而dmesg是直接访问内核缓存区(kernel ring buffer)并把日志输出出来。

三、输出选项详解

1.控制日志输出格式(-o, –output)

格式 含义
short 默认,产生的输出与传统的syslog文件的格式基本相同,每条日志显示一行。
short-full 和short非常相似,但显示的是–since=和–until=选项接受的格式的时间戳,与短输出模式下显示的时间戳信息不同,该模式在输出中包括工作日、年份和时区信息。
short-iso 和short非常相似,但显示的是ISO 8601标准的时间戳(YYYY-MM-DDThh:mm:ss)。
short-iso-precise 如同short-iso,但包括完整的微秒级精度。
short-monotonic 单调递增时间,时间格式为相对时间。
short-delta 与short-monotonic一样,但包括与前一条的时间差,不可靠的时间差会用 "*"来标记。
short-unix 显示的是自1970年1月1日UTC以来的秒数,即UNIX时间戳,精度为微秒。
verbose 显示所有字段的完整结构的条目项目。
export 将日志序列化为适合备份和网络传输的二进制(但主要是基于文本的)流。要将二进制流导入到 journald格式使用man systemd-journal-remote查看用法。
json json格式输出,可通过man Journal JSON Format查看用法。
json-pretty 将条目格式化为JSON数据结构,但将其格式化为多行,以便使其更易读。
json-sse 将条目格式化为JSON数据结构,但将其包装成适合服务器发送事件的格式。
json-seq 将条目格式化为JSON数据结构,但前缀为ASCII记录分隔符(0x1E),后缀为ASCII换行符(0x0A),符合"application/json-seq"。
cat 生成一个非常简洁的输出,只显示每个日志条目的实际信息,没有元数据,甚至没有时间戳。如果与–output-fields选项结合使用,将为每条日志记录输出指定的字段。
with-unit 与short-full类似,但在单元和用户单元名称前加上前缀,而不是传统的syslog标识符。在使用模板化实例时很有用,因为它将在单元名称中包括参数名称。

1)short

默认情况下输出格式,比如输出今天的sshd服务日志:

journalctl -u sshd -S today -o short

2)short-full

short的基础上补全年份、时区信息,和–since=和–until=选项的时间戳适配:

journalctl -u sshd -S today -o short-full

3)short-iso

ISO 8601标准时间戳(YYYY-MM-DDThh:mm:ss)显示:

journalctl -u sshd -S today -o short-iso

4)short-iso-precise

short-iso基础上加上完整的微秒级精度:

journalctl -u sshd -S today -o short-iso-precise

5)short-monotonic

单调递增时间,时间格式为相对时间:

journalctl -u sshd -S today -o short-monotonic

6)short-delta

short-monotonic一样,但包括与前一条的时间差,不可靠的时间差会用 ***** 来标记:

journalctl -u sshd -S today -o short-delta

7)short-unix

显示的是自1970年1月1日UTC以来的秒数,即UNIX时间戳,精度为微秒:

journalctl -u sshd -S today -o short-unix

8)verbose

显示所有字段的完整结构的条目项目:

journalctl -u sshd -S today -o verbose

字段含义可通过man 7 systemd.journal-fields来查看。

9)export

将日志序列化为适合备份和网络传输的二进制(但主要是基于文本的)流:

journalctl -u sshd -S today -o export

10)json

json格式输出:

journalctl -u sshd -S today -o json

11)json-pretty

将条目格式化为JSON数据结构,但将其格式化为多行,以便使其更易读。

比如查询上次启动时优先级为2(Critical)的错误日志,json-pretty格式输出:

journalctl -b -1 -p 2 -o json-pretty

类似于将json输出递交给jq命令格式化输出一遍。

MESSAGE长度过大,在终端展示不全时,可以加上–all参数并重定向写入到文件,查看文件的方式来看完整的MESSAGE字段信息,比如:

journalctl -b -1 -p 2 -o json-pretty --all > boot-1.json

12)json-sse

将条目格式化为JSON数据结构,但将其包装成适合服务器发送事件的格式:

journalctl -u sshd -S today -o json-sse

13)json-seq

将条目格式化为JSON数据结构,但前缀为ASCII记录分隔符(0x1E),后缀为ASCII换行符(0x0A),符合"application/json-seq"格式标准:

journalctl -u sshd -S today -o json-seq

14)cat

生成一个非常简洁的输出,只显示每个日志条目的实际信息,没有元数据,甚至没有时间戳:

journalctl -u sshd -S today -o cat

15)with-unit

short-full类似,但在单元和用户单元名称前加上前缀,而不是传统的syslog标识符:

journalctl -u sshd -S today -o with-unit

short-full类似,但在单元和用户单元名称前加上前缀,而不是传统的syslog标识符。在使用模板化实例时很有用,因为它将在单元名称中展示参数名称。

2.指定输出的字段列表(–output-fields)

逗号分隔的字段列表,这些字段应该包括在输出中。这只对通常会显示所有字段的输出模式有影响(verboseexportjsonjson-prettyjson-ssejson-seq);同时,"CURSOR"、"REALTIME_TIMESTAMP"、"__MONOTONIC_TIMESTAMP"和"_BOOT_ID"字段是固定输出的。

字段含义可通过man 7 systemd.journal-fields来查看。

比如输出字段指定为SYSLOG_IDENTIFIER,其它默认字段也会被强制打印出来:

journalctl -b -1 -p 0 -o json-pretty --output-fields=SYSLOG_IDENTIFIER

同时注意,如果本身日志里面没有这个字段,不能凭空生成出来,指定的–output-fields也是要在日志记录的范围内的,比如下面这条json-pretty的输出:

$ journalctl -b -1 -p 2 -o json-pretty
{
        "_SOURCE_MONOTONIC_TIMESTAMP" : "508393877655",
        "_RUNTIME_SCOPE" : "system",
        "PRIORITY" : "0",
        "__REALTIME_TIMESTAMP" : "1683721044744751",
        "__CURSOR" : "s=9de5c4069e3b4a5182f62465ddd61d5b;i=1727e9;b=1ab5a0b32c7249ad9eace156dfa47211;m=765ec30bfa;t=5fb55dac5da2f;x=b56b6b890bea3378",
        "MESSAGE" : "watchdog: BUG: soft lockup - CPU#11 stuck for 28s! [swapper/11:0]",
        "__MONOTONIC_TIMESTAMP" : "508395981818",
        "_BOOT_ID" : "1ab5a0b32c7249ad9eace156dfa47211",
        "SYSLOG_FACILITY" : "0",
        "_TRANSPORT" : "kernel",
        "_HOSTNAME" : "arch",
        "_MACHINE_ID" : "b8fd7a061aca4427b8f4f27e474d4989",
        "SYSLOG_IDENTIFIER" : "kernel"
}

json-pretty默认行为是打印所有字段,如果字段内容为空就不打印,很显然这条日志只有上面这些字段有内容,那么–output-fields的取值也只能从上面这些字段中取,不能凭空产生。

比如基于上面这条日志,除了固定的几个字段我不能控制输出,我只想输出MESSAGE_HOSTNAME__MACHINE_IDSYSLOG_FACILITY字段的内容,并且以json-prettyjson-sse格式输出:

journalctl -b -1 -p 2 -o json-pretty --output-fields=MESSAGE,_HOSTNAME,__MACHINE_ID,SYSLOG_FACILITY
journalctl -b -1 -p 2 -o json-sse --output-fields=MESSAGE,_HOSTNAME,__MACHINE_ID,SYSLOG_FACILITY

3.通过指定字段列表筛选日志

如果你已经确定了想要的字段的对应日志条目,那么可以通过指定字段的值来过滤匹配。

比如筛选sshd服务SYSLOG_PID为7052并且_SYSTEMD_INVOCATION_ID为指定字符串的日志,并通过json-pretty格式输出:

journalctl SYSLOG_PID=7052 _SYSTEMD_INVOCATION_ID=21174b37b37c4cd5b4d20c828cbffaa3 -u sshd -o json-pretty

又或者指定_BOOT_ID为某个值,筛选优先级为3的内核日志信息,short-iso格式输出:

journalctl -k _BOOT_ID=91b890e06b654b49a54257184891e744 -p 3 -o short-iso

journalctl –list-boots会列出系统已记录的所有_BOOT_ID

4.显示最近的日志的指定行数(-n, –lines)

指定此参数后,默认显示最近10行日志:

journalctl -u sshd -n

显示最近20行日志:

journalctl -u sshd -n 20

当配合–grep一起使用时,默认会有–reverse的效果,根据时间降序:

5.反向显示(-r, –reverse)

不指定时默认是根据日志先后顺序排序,指定后会将顺序反过来显示。

比如筛选prometheus服务今天最近20行的日志,从后往前排序:

journalctl -u prometheus -S today -r -n 20

再比如显示身份授权相关且等级为4(warning)到7(debug)级最近30行日志反向排序:

journalctl -p 4..7 --facility=auth -r -n 30

6.显示游标位置(–show-cursor)

显示sshd服务最近10行日志,并输出最后一行的游标位置:

journalctl -u sshd -n --show-cursor

  • s: 代表序列号(sequence),它是一个标识日志消息序列的字符串。序列号用于标记日志消息的顺序,确保它们按照正确的顺序显示。
  • i: 代表日志文件索引号(file index),它指示了包含当前日志消息的日志文件的索引位置。每个日志文件都有一个唯一的索引号。
  • b: 代表引导 ID(boot ID),它标识了启动会话(boot session)。每次系统启动都会生成一个唯一的引导 ID,用于区分不同的启动会话。
  • m: 代表日志文件位置(monotonic),它表示日志消息在日志文件中的位置。它是一个递增的数值,用于确保日志消息在日志文件中的唯一性和顺序。

7.根据游标位置过滤日志(-c, –cursor)

拿到游标位置后,可以通过指定游标位置来定位日志:

journalctl -u sshd -n --cursor="<CURSOR>"

8.显示某个光标之后的日志(–after-cursor)

显示指定光标后的日志,并显示20行,并且在最后展示光标位置:

journalctl -u sshd -n 20 --show-cursor --after-cursor="<CURSOR>"

显示某个光标位置之后的10行日志,并反向输出:

journalctl -u sshd -n -r --show-cursor --after-cursor="<CURSOR>"

9.以UTC时间格式输出(–utc)

输出prometheus服务今天最近10行的日志,并以UTC时间输出:

journalctl --utc -u prometheus -S today -n

可以看到对比正常时间刚好差8个小时时区。

10.显示相关联的消息目录信息(-x, –catalog)

systemd-journald的日志系统中,每条日志消息可以与一个或多个消息目录(message catalog)相关联。

使用 -x–catalog 选项,journalctl 命令会尝试显示与每条日志消息相关联的消息目录信息:

journalctl -u sshd -x -n

如果没有关联的日志,则还是正常显示。

11.不显示主机名字段(–no-hostname)

顾名思义,不显示主机名,但这个参数只作用于日志格式为short开头的类别。

显示最近10行sshd服务日志,并且不显示主机名:

journalctl -u sshd --no-hostname -n

默认输出格式为short,所以这个参数值是生效的。

假设使用json格式来输出:

journalctl -u sshd --no-hostname -n -o json-pretty

依然会输出主机名字段。

12.截断输出(–no-full)

此参数会禁止完整显示长日志消息的内容。当日志消息非常长时,会被截断为摘要形式,以保持输出的简洁性:

journalctl --no-full

13.完整输出所有字段(-a, –all)

完整地显示所有字段,即使它们包括不可打印的字符或非常长:

journalctl -u prometheus.service -a -n 1 -o json-pretty

14.跟踪日志条目(-f, –follow)

作用和tail命令的-f参数类似,一直跟踪日志的输出。

比如跟踪sshd服务日志输出,时间点从现在开始:

journalctl -u sshd -f -S now

测试两次失败登录一次成功登录都实时显示在了日志上。

15.显示所有已存储的输出行(–no-tail)

使用-f参数时,默认只显示后10行日志,然后继续追踪日志,而–no-tail会将所有行显示出来,再继续追踪日志:

journalctl -u sshd -f --no-tail

16.静默模式下输出(-q,–quiet)

只显示关键的日志信息,过滤掉一些额外的提示和冗余内容。

静默模式输出上一次引导时的最新10行日志:

journalctl -b -1 -n -q

四、分页控制选项详解

1.分页输出(–no-pager)

禁止把输出内容输出到一个页面中,当日志内容冗长容易被截断的时候非常有用。

分页输出最近10行prometheus的日志:

journalctl -u prometheus --no-pager -n

可以看到不加–no-pager和加此参数是有明显区别的,不加的情况下一行输出不下,直接截断不输出后面的内容,加的情况下,后面的内容会另起一行输出。

同时也可以管道给less命令来条条查看输出:

journalctl -u prometheus -n 20 --no-pager|less

2.定位到日志末尾行(-e, –pager-end)

这个选项在查看最新的日志内容时特别有用,这样可以不用手动滚动到日志末尾。

定位sshd服务末尾行日志:

journalctl -u sshd -e

不加此参数的效果,从最开始一页页翻阅:

五、命令选项

下面这些命令选项更像是一些统计汇总,和对日志集的操作查看等,默认不会展示日志记录,只统计输出。

1.输出所有字段名(-N, –fields)

打印当前在journal所有条目中使用的所有字段名:

journalctl -N

2.统计日志中指定字段的所有取值(-F, –field)

比如打印journal记录的日志中_BOOT_ID_HOSTNAMEPRIORITY字段的所有取值:

journalctl -F _BOOT_ID
journalctl -F _HOSTNAME
journalctl -F PRIORITY

3.列出当前统计的所有BOOT ID(–list-boots)

journalctl --list-boots

4.展示硬盘使用情况(–disk-usage)

显示所有日志文件的当前磁盘使用率,这显示了所有存档和活动日志文件的磁盘使用量之和:

$ journalctl --disk-usage
Archived and active journals take up 4.0G in the file system.
$

归档和活动日志共占用4G大小。

5.校验日志文件内部的一致性(–verify)

此参数会对系统日志文件进行检查,并报告任何可能的损坏或错误。它会检查日志文件的完整性、有效性以及与相关索引文件的一致性:

journalctl --verify

6.只显示头部信息(–header)

不显示日志内容,只显示日志的头部信息:

journalctl --header

默认会显示所有的journal日志文件的头部信息,可以使用–file参数指定:

journalctl --file=</path/xxx.journal> --header

7.列出可用的日志分类目录(–list-catalog)

将日志目录的消息ID以表格形式列出来,并加上其简短的描述字符串:

journalctl --list-catalog

每个目录都具有一个唯一的标识符和一个描述,用于识别和描述该目录的用途。

8.显示catalog的内容(–dump-catalog)

显示消息目录的内容,每个条目由两个破折号和ID组成的行隔开(格式与.catalog文件相同)。

journalctl --dump-catalog

当然也可以指定某个catalog:

journalctl --dump-catalog <catalog id>

9.更新catalog(–update-catalog)

更新消息目录索引。每次安装、删除或更新新的目录文件时,都需要执行这个命令,以重建二进制目录索引。

journalctl --update-catalog

10.同步未写入的日志(–sync)

要求journaldaemon进程将所有尚未写入的日志数据写入备份文件系统并同步所有日志。

journalctl --sync

六、总结

通过以上示例,journalctl可谓是非常强悍的日志查看和分析工具,不仅能对各类系统日志分门别类还支持各种格式化输出。同时还具有与其他工具的集成能力,比如与 ELK(Elasticsearch、Logstash 和 Kibana)等日志聚合和分析平台的集成,进一步扩展了日志分析的能力。

上面的参数熟练掌握后,能精准筛选出任何想要的日志,方便快速及时定位系统及服务问题。

附带PDF版本:

揭秘Linux日志分析利器 – 全面透析journalctl

赞(53) 打赏
转载请注明出处:RokasYang's Blog » 揭秘Linux日志分析利器 -

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫

微信扫一扫