最新 Linux awk 命令實(shí)戰(zhàn)教程:從日志分析到性能監(jiān)控
大家好,我是小康。上次我們一起學(xué)習(xí)了 Linux 的 sed 命令,今天要介紹的是文本處理的"瑞士軍刀" —— awk。無(wú)論是分析日志、處理數(shù)據(jù),還是提取信息,它都能幫你輕松搞定。
第一部分: 初識(shí) awk
作為一名開發(fā)老兵,我整理了這些年和 awk 打交道的心得。希望能幫你少走彎路,快速掌握這個(gè)強(qiáng)大的工具。
記得剛?cè)胄心菚?huì)兒,面對(duì)成堆的日志文件,我跟大多數(shù)新手一樣一籌莫展。直到遇到了 awk 這個(gè)老伙計(jì),才算找到了"趁手的兵器"。
今天,就讓我用一個(gè)開發(fā)工程師的視角,帶你認(rèn)識(shí)這個(gè)陪伴了我 6 年多的老朋友。
1. 第一次相遇:awk 是個(gè)什么樣的角色?
就像一個(gè)心靈手巧的老師傅,awk 最擅長(zhǎng)的就是把大段大段的文本"解剖"開來(lái),精準(zhǔn)地找出你想要的信息。它的名字來(lái)自三位創(chuàng)始人(Aho、Weinberger、Kernighan)的首字母,雖然不好念,但本事真不小。
2. 從一個(gè)真實(shí)案例開始
還記得我遇到的第一個(gè)挑戰(zhàn):leader 讓我從一個(gè)幾GB的服務(wù)日志里找出造成系統(tǒng)故障的元兇。
當(dāng)時(shí)的日志大概長(zhǎng)這樣:
2024-02-13 10:00:01 [192.168.1.100] "GET /api/users" 200 89ms
2024-02-13 10:00:02 [192.168.1.101] "POST /api/orders" 500 1230ms
2024-02-13 10:00:03 [192.168.1.102] "GET /api/products" 200 45ms
我需要:
- 找出所有響應(yīng)時(shí)間超過(guò)1秒的請(qǐng)求
- 分析高峰期的訪問(wèn)量
- ......
用 awk 的解決方案出奇簡(jiǎn)單:
# 1、找出所有響應(yīng)時(shí)間超過(guò)1秒的請(qǐng)求
awk '
{
# 提取并轉(zhuǎn)換響應(yīng)時(shí)間
time = $7 # 取最后一個(gè)字段
gsub(/ms/, "", time) # 去掉ms
time = time + 0 # 確保轉(zhuǎn)成數(shù)字
# 只打印超過(guò)1秒(1000ms)的請(qǐng)求
if(time >= 1000) {
printf "時(shí)間: %s %s\nIP: %s\n請(qǐng)求: %s %s\n響應(yīng)時(shí)間: %dms\n----------\n",
$1, $2, substr($3, 2, length($3)-2), $4, $5, time
}
}' access.log
# 輸出:
時(shí)間: 2024-02-13 10:00:02
IP: 192.168.1.101
請(qǐng)求: "POST /api/orders"
響應(yīng)時(shí)間: 1230ms
----------
# 2、分析高峰期的訪問(wèn)量
awk '
BEGIN {
print "每分鐘訪問(wèn)量統(tǒng)計(jì):"
print "-------------------"
}
{
# 提取時(shí)分
split($2, t, ":")
minute = t[1] ":" t[2] # 只取小時(shí)和分鐘
count[minute]++
}
END {
# 按時(shí)間排序輸出
n = asorti(count, sorted)
for(i=1; i<=n; i++) {
printf "%s:00 - %d次訪問(wèn)\n", sorted[i], count[sorted[i]]
}
}' access.log
# 輸出:
每分鐘訪問(wèn)量統(tǒng)計(jì):
-------------------
10:00:00 - 3次訪問(wèn)
10:01:00 - 2次訪問(wèn)
10:02:00 - 1次訪問(wèn)
第二部分 : awk 基本功
老規(guī)矩,我們先來(lái)看看最常用的 awk 基礎(chǔ)命令。這些都是我這些年解決問(wèn)題的"殺手锏",保證你學(xué)了就能用。
1. awk的基本結(jié)構(gòu)
在開始學(xué)習(xí)具體命令前,我們先來(lái)了解awk程序的基本結(jié)構(gòu):
awk 'BEGIN {動(dòng)作前}
pattern {動(dòng)作}
END {動(dòng)作后}' 文件名
就像一個(gè)完整的故事有開頭、主體和結(jié)尾,awk 也有三個(gè)主要部分:
(1) BEGIN塊:開場(chǎng)白
- 在讀取文件前執(zhí)行
- 常用來(lái)打印表頭、初始化變量
# 例如:輸出前先打印個(gè)表頭
BEGIN {print "=== 進(jìn)程列表 ==="}
(2) pattern {action}:主體部分
- pattern:匹配條件,決定要處理哪些行
- action:具體操作,決定要做什么
# 例如:找出root的進(jìn)程
$1=="root" {print $0}
(3) END塊:收尾工作
- 在處理完所有行后執(zhí)行
- 常用來(lái)輸出統(tǒng)計(jì)結(jié)果
# 例如:最后輸出總行數(shù)
END {print "共有"NR"個(gè)進(jìn)程"}
此外,awk 還提供了一些常用的內(nèi)置變量:
- $0:整行內(nèi)容
- 2..:第1、2列
- NR:當(dāng)前行號(hào)
- NF:當(dāng)前行的列數(shù)
2. 實(shí)例講解
理解了基本結(jié)構(gòu),我們來(lái)看些實(shí)際例子。假設(shè)我們有一個(gè)進(jìn)程列表 process.txt:
root 1234 5.0 2.5 mysql running
admin 2345 3.2 1.5 nginx running
root 3456 8.5 4.0 java stopped
nobody 4567 2.1 1.0 nginx running
(1) 提取特定列
# 看看誰(shuí)在運(yùn)行這些進(jìn)程
awk '{print $1}' process.txt
# 輸出:
root
admin
root
nobody
# 查看進(jìn)程名和狀態(tài)
awk '{print $5, $6}' process.txt
# 輸出:
mysql running
nginx running
java stopped
nginx running
(2) 條件過(guò)濾(最常用)
# 找出 CPU 使用率超過(guò)5%的進(jìn)程
awk '$3 > 5 {print $5 "進(jìn)程CPU使用率:", $3"%"}' process.txt
# 輸出:
java進(jìn)程CPU使用率: 8.5%
# 找出狀態(tài)為 running 的進(jìn)程
awk '$6=="running" {print $1,$5}' process.txt
# 輸出:
root mysql
admin nginx
nobody nginx
2. 實(shí)用統(tǒng)計(jì)功能
(1) 常用統(tǒng)計(jì)
# 統(tǒng)計(jì)進(jìn)程數(shù)量, NR: NR 是 awk 的一個(gè)內(nèi)置變量,表示當(dāng)前已經(jīng)處理的記錄(行)數(shù)量。
awk 'END {print "總進(jìn)程數(shù):", NR}' process.txt
# 輸出:
總進(jìn)程數(shù): 4
# 我們也可以在處理過(guò)程中看到NR的變化
awk '{print "當(dāng)前處理第" NR "行"}' process.txt
# 輸出:
當(dāng)前處理第1行
當(dāng)前處理第2行
當(dāng)前處理第3行
當(dāng)前處理第4行
# 計(jì)算所有進(jìn)程的平均CPU使用率
awk '{sum += $3} END {print "平均CPU使用率:", sum/NR"%"}' process.txt
# 輸出:
平均CPU使用率: 4.7%
(2) 分組統(tǒng)計(jì)(特別常用)
# 看看每個(gè)用戶開了多少個(gè)進(jìn)程
awk '{count[$1]++} END {
for(user in count) {
print user "的進(jìn)程數(shù):", count[user]
}
}' process.txt
# 輸出:
root的進(jìn)程數(shù): 2
admin的進(jìn)程數(shù): 1
nobody的進(jìn)程數(shù): 1
# 統(tǒng)計(jì)每種狀態(tài)的進(jìn)程數(shù)
awk '{states[$6]++} END {
for(state in states) {
print state, states[state]
}
}' process.txt
# 輸出:
running 3
stopped 1
3. 實(shí)戰(zhàn)常用技巧
(1) 匹配特定內(nèi)容
# 找出 java 相關(guān)的進(jìn)程
awk '/java/ {print $0}' process.txt # $0 代表當(dāng)前行的整行內(nèi)容
# 輸出:
root 3456 8.5 4.0 java stopped
# 找出包含特定字符的行并突出顯示重要信息
awk '/nginx/ {print "進(jìn)程ID:"$2, "內(nèi)存:"$4"%"}' process.txt
# 輸出:
進(jìn)程ID:2345 內(nèi)存:1.5%
進(jìn)程ID:4567 內(nèi)存:1.0%
(2) 多條件組合(經(jīng)常用到)
# 找出 CPU 高、狀態(tài)為 running 的進(jìn)程
awk '$3 > 3 && $6=="running" {
print "警告 -", $5, "進(jìn)程CPU使用率:", $3"%"
}' process.txt
# 輸出:
警告 - mysql 進(jìn)程CPU使用率: 5.0%
警告 - nginx 進(jìn)程CPU使用率: 3.2%
4. 小貼士
(1) 實(shí)用的判斷方法:
# 找出異常的進(jìn)程(CPU或內(nèi)存使用過(guò)高)
awk '$3 > 5 || $4 > 3 {
print $5 "進(jìn)程異常:"
print " CPU:", $3"%"
print " 內(nèi)存:", $4"%"
}' process.txt
# 輸出:
java進(jìn)程異常:
CPU: 8.5%
內(nèi)存: 4.0%
(2) 累加統(tǒng)計(jì):
bash
# 計(jì)算 nginx 進(jìn)程的總內(nèi)存占用
awk '/nginx/ {total += $4}
END {print "nginx總內(nèi)存占用:", total"%"}' process.txt
# 輸出:
nginx總內(nèi)存占用: 2.5%
記?。?/p>
- $1,$2,$3... 代表第幾列
- NR 代表當(dāng)前行號(hào)
- print 和 printf 都是打印命令
- 用 $0 可以打印整行
這些都是我平時(shí)工作中最常用的簡(jiǎn)單命令,基本夠用了。等你熟悉了這些,我們?cè)賹W(xué)更高級(jí)的用法。
第三部分: awk高級(jí)應(yīng)用指南(性能分析)
接下來(lái)我們來(lái)點(diǎn)高級(jí)的,帶大家用 awk 處理日常工作中最常見(jiàn)的幾個(gè)場(chǎng)景。每一步我們都從簡(jiǎn)單的開始,循序漸進(jìn)地掌握。
1. 基礎(chǔ)日志處理
先從一個(gè)簡(jiǎn)單的接口日志開始:
2024-02-14 10:00:01 [api=/user/login] cost=100ms status=200
2024-02-14 10:00:02 [api=/user/info] cost=50ms status=200
2024-02-14 10:00:03 [api=/user/login] cost=800ms status=500
2024-02-14 10:00:04 [api=/order/create] cost=150ms status=200
(1) 提取重要信息(簡(jiǎn)單)
# 只看接口名和響應(yīng)時(shí)間
awk '{print $3, $4}' api.log
# 輸出:
[api=/user/login] cost=100ms
[api=/user/info] cost=50ms
[api=/user/login] cost=800ms
[api=/order/create] cost=150ms
(2) 查找異常請(qǐng)求(常用)
# 找出響應(yīng)時(shí)間超過(guò)500ms的慢請(qǐng)求
awk '
{
# 提取響應(yīng)時(shí)間的數(shù)字部分
gsub(/cost=|ms/, "", $4) # 去掉"cost="和"ms"
# 如果響應(yīng)時(shí)間超過(guò)500ms
if($4 > 500) {
print "慢請(qǐng)求: " $0
}
}
' api.log
# 輸出:
慢請(qǐng)求: 2024-02-14 10:00:03 [api=/user/login] cost=800ms status=500
2. 接口性能分析
(1) 計(jì)算接口的平均響應(yīng)時(shí)間(入門級(jí))
# 計(jì)算每個(gè)接口的平均響應(yīng)時(shí)間
awk '
{
# 提取接口名稱
api=$3
# 提取響應(yīng)時(shí)間的數(shù)字部分
gsub(/.*=|ms.*/, "", $4)
# 累加響應(yīng)時(shí)間
sum[api] += $4
# 統(tǒng)計(jì)請(qǐng)求次數(shù)
count[api]++
}
END {
print "接口平均響應(yīng)時(shí)間:"
for(api in sum) {
printf "%s: %.2fms\n", api, sum[api]/count[api]
}
}' api.log
# 輸出:
接口平均響應(yīng)時(shí)間:
[api=/user/login]: 450.00ms
[api=/user/info]: 50.00ms
[api=/order/create]: 150.00ms
(2) 統(tǒng)計(jì)接口QPS(常用)
先從一個(gè)簡(jiǎn)單的接口日志開始:
2024-02-14 10:00:01 [api=/user/login] cost=100ms status=200
2024-02-14 10:00:02 [api=/user/info] cost=50ms status=200
2024-02-14 10:00:03 [api=/user/login] cost=800ms status=500
2024-02-14 10:00:04 [api=/order/create] cost=150ms status=200
# 命令:計(jì)算每秒的請(qǐng)求數(shù)(QPS)
awk '{
# 把時(shí)間列拼接起來(lái): $1是日期,$2是時(shí)間
# 例如: "2024-02-14 10:00:01"
time = $1" "$2
# substr 函數(shù)用于截取字符串
# 從拼接的時(shí)間字符串中取前19位,精確到秒
# 如: "2024-02-14 10:00:01"
second = substr(time, 1, 19)
# 用時(shí)間作為key,計(jì)數(shù)+1
count[second]++
}
END {
# 處理完所有行后,打印統(tǒng)計(jì)結(jié)果
print "每秒請(qǐng)求數(shù)(QPS):"
# 遍歷統(tǒng)計(jì)結(jié)果
for(s in count) {
print s ": " count[s] "次/秒"
}
}' api.log
(3) 分析響應(yīng)時(shí)間分布(進(jìn)階)
# 按區(qū)間統(tǒng)計(jì)響應(yīng)時(shí)間分布
awk '
BEGIN {
print "響應(yīng)時(shí)間分布統(tǒng)計(jì):"
}
{
# 提取cost=后面的數(shù)字,去掉ms
split($4, arr, "=|ms") # 用=或ms分割,如:"cost=100ms" -> arr[2]="100"
time = arr[2] # 提取數(shù)字部分
# 按區(qū)間統(tǒng)計(jì)請(qǐng)求數(shù)
if(time <= 100) {
range["0-100ms"]++ # 統(tǒng)計(jì)小于等于100ms的請(qǐng)求
} else if(time <= 200) {
range["101-200ms"]++ # 統(tǒng)計(jì)101ms到200ms的請(qǐng)求
} else {
range["200ms+"]++ # 統(tǒng)計(jì)大于200ms的請(qǐng)求
}
total++ # 總請(qǐng)求數(shù)加1
}
END {
# 遍歷每個(gè)區(qū)間并打印統(tǒng)計(jì)結(jié)果
for(r in range) {
percent = range[r]/total*100
printf "%s: %d個(gè)請(qǐng)求 (%.1f%%)\n", r, range[r], percent
}
}' api.log
# 現(xiàn)在輸出應(yīng)該是:
響應(yīng)時(shí)間分布統(tǒng)計(jì):
0-100ms: 2個(gè)請(qǐng)求 (50.0%)
101-200ms: 1個(gè)請(qǐng)求 (25.0%)
200ms+: 1個(gè)請(qǐng)求 (25.0%)
3. 錯(cuò)誤分析
統(tǒng)計(jì)錯(cuò)誤率(常用)
2024-02-14 10:00:01 [api=/user/login] cost=100ms status=200
2024-02-14 10:00:02 [api=/user/info] cost=50ms status=200
2024-02-14 10:00:03 [api=/user/login] cost=800ms status=500
2024-02-14 10:00:04 [api=/order/create] cost=150ms status=200
# 計(jì)算接口錯(cuò)誤率
awk '
{
api=$3
status=$5
gsub(/.*=/, "", status)
# 統(tǒng)計(jì)總請(qǐng)求和錯(cuò)誤請(qǐng)求
total[api]++
if(status >= 400) {
errors[api]++
}
}
END {
print "接口錯(cuò)誤率統(tǒng)計(jì):"
for(api in total) {
if(errors[api] > 0) {
err_rate = errors[api]/total[api]*100
printf "%s: %.1f%% (%d/%d)\n",
api, err_rate, errors[api], total[api]
}
}
}' api.log
# 輸出:
接口錯(cuò)誤率統(tǒng)計(jì):
[api=/user/login]: 50.0% (1/2)
4. 生成性能報(bào)告(高級(jí))
把前面學(xué)到的都用上,生成一個(gè)完整的性能報(bào)告:
# 生成完整的接口性能分析報(bào)告
awk '
BEGIN {
print "=== 接口性能分析報(bào)告 ==="
print "時(shí)間范圍:" strftime("%Y-%m-%d %H:%M:%S")
print "\n1. 總體統(tǒng)計(jì)"
}
{
# 記錄基礎(chǔ)信息
api=$3
gsub(/.*=|ms.*/, "", $4)
cost=$4
gsub(/.*=/, "", $5)
status=$5
# 統(tǒng)計(jì)總請(qǐng)求
total_reqs++
# 按接口統(tǒng)計(jì)
reqs[api]++
total_cost[api] += cost
# 記錄最大最小響應(yīng)時(shí)間
if(cost > max_cost[api]) max_cost[api] = cost
if(min_cost[api] == 0 || cost < min_cost[api])
min_cost[api] = cost
# 統(tǒng)計(jì)錯(cuò)誤
if(status >= 400) errors[api]++
}
END {
# 1. 打印總體統(tǒng)計(jì)
printf "總請(qǐng)求數(shù):%d\n", total_reqs
# 2. 打印接口詳情
print "\n2. 接口詳情"
for(api in reqs) {
printf "\n接口:%s\n", api
printf " 總調(diào)用次數(shù):%d\n", reqs[api]
printf " 平均響應(yīng)時(shí)間:%.2fms\n",
total_cost[api]/reqs[api]
printf " 最大響應(yīng)時(shí)間:%dms\n", max_cost[api]
printf " 最小響應(yīng)時(shí)間:%dms\n", min_cost[api]
if(errors[api] > 0) {
printf " 錯(cuò)誤率:%.1f%%\n",
errors[api]/reqs[api]*100
}
}
}' api.log
# 輸出:
=== 接口性能分析報(bào)告 ===
時(shí)間范圍:2024-02-14 10:00:00
1. 總體統(tǒng)計(jì)
總請(qǐng)求數(shù):4
2. 接口詳情
接口:[api=/user/login]
總調(diào)用次數(shù):2
平均響應(yīng)時(shí)間:450.00ms
最大響應(yīng)時(shí)間:800ms
最小響應(yīng)時(shí)間:100ms
錯(cuò)誤率:50.0%
接口:[api=/user/info]
總調(diào)用次數(shù):1
平均響應(yīng)時(shí)間:50.00ms
最大響應(yīng)時(shí)間:50ms
最小響應(yīng)時(shí)間:50ms
接口:[api=/order/create]
總調(diào)用次數(shù):1
平均響應(yīng)時(shí)間:150.00ms
最大響應(yīng)時(shí)間:150ms
最小響應(yīng)時(shí)間:150ms
5. 實(shí)用小技巧
(1) 處理大文件時(shí)先取樣分析:
head -1000 big_log.txt | awk '你的命令'
(2) 實(shí)時(shí)監(jiān)控錯(cuò)誤和慢請(qǐng)求:
測(cè)試用例:
? cat api.log
# api.log 示例數(shù)據(jù):
2024-02-14 10:00:01 [api=/user/login] cost=100ms status=200 # 正常請(qǐng)求
2024-02-14 10:00:02 [api=/user/info] cost=550ms status=200 # 慢請(qǐng)求(>500ms)
2024-02-14 10:00:03 [api=/user/login] cost=800ms status=500 # 慢請(qǐng)求且報(bào)錯(cuò)
2024-02-14 10:00:04 [api=/order/create] cost=150ms status=404 # 錯(cuò)誤請(qǐng)求
2024-02-14 10:00:05 [api=/user/profile] cost=200ms status=200 # 正常請(qǐng)求
# 監(jiān)控命令:
tail -f api.log | awk '
$4 ~ /cost=[5-9][0-9][0-9]ms/ || $5 ~ /status=[45][0-9][0-9]/ {
# 檢查是否是慢請(qǐng)求
if($4 ~ /cost=[5-9][0-9][0-9]ms/) {
msg="慢請(qǐng)求"
}
# 檢查是否有錯(cuò)誤狀態(tài)碼
if($5 ~ /status=[45][0-9][0-9]/) {
msg=msg?msg" 且 狀態(tài)碼異常":"狀態(tài)碼異常"
}
# 打印告警信息
print "\033[31m告警:" $0 " # " msg "\033[0m"
# 重置消息變量
msg=""
}'
# 輸出(紅色顯示):
告警:2024-02-14 10:00:02 [api=/user/info] cost=550ms status=200 # 因?yàn)轫憫?yīng)時(shí)間>500ms
告警:2024-02-14 10:00:03 [api=/user/login] cost=800ms status=500 # 因?yàn)轫憫?yīng)時(shí)間>500ms且狀態(tài)碼500
告警:2024-02-14 10:00:04 [api=/order/create] cost=150ms status=404 # 因?yàn)闋顟B(tài)碼404
記?。?/p>
- 先從簡(jiǎn)單的統(tǒng)計(jì)開始
- 需要時(shí)再加更多的統(tǒng)計(jì)維度
- 復(fù)雜的分析可以分步驟進(jìn)行
- 多用print調(diào)試你的統(tǒng)計(jì)邏輯
學(xué)會(huì)了這些,你就能應(yīng)對(duì)大部分的日志分析工作了!
第四部分:實(shí)戰(zhàn)篇 - 應(yīng)用日志分析
接著我們來(lái)分析實(shí)際工作中最常見(jiàn)的幾種應(yīng)用日志。咱們由淺入深,一步步來(lái)。
1. 基礎(chǔ)日志分析
(1) 簡(jiǎn)單的應(yīng)用日志
先來(lái)看一個(gè)最基礎(chǔ)的應(yīng)用日志:
2024-02-14 10:00:01 [INFO] UserService - 用戶登錄成功,用戶名=admin
2024-02-14 10:00:02 [ERROR] OrderService - 訂單創(chuàng)建失?。簲?shù)據(jù)庫(kù)連接超時(shí)
2024-02-14 10:00:03 [WARN] UserService - 密碼錯(cuò)誤,用戶名=test
2024-02-14 10:00:04 [ERROR] PaymentService - 支付失?。河囝~不足
(2) 基礎(chǔ)日志過(guò)濾(最簡(jiǎn)單的用法)
# 命令1:顯示所有ERROR日志
awk '/ERROR/' app.log
# 輸出:
2024-02-14 10:00:02 [ERROR] OrderService - 訂單創(chuàng)建失?。簲?shù)據(jù)庫(kù)連接超時(shí)
2024-02-14 10:00:04 [ERROR] PaymentService - 支付失?。河囝~不足
# 命令2:查看特定服務(wù)的日志
awk '/UserService/' app.log
# 輸出:
2024-02-14 10:00:01 [INFO] UserService - 用戶登錄成功,用戶名=admin
2024-02-14 10:00:03 [WARN] UserService - 密碼錯(cuò)誤,用戶名=test
(3) 統(tǒng)計(jì)日志級(jí)別(常用功能)
# 命令:統(tǒng)計(jì)每種日志級(jí)別的數(shù)量
awk '
# 匹配有方括號(hào)的行
/\[.*\]/ {
# 提取方括號(hào)中的內(nèi)容,存入arr數(shù)組
match($0, /\[(.*?)\]/, arr)
# 對(duì)應(yīng)的日志級(jí)別計(jì)數(shù)加1
level[arr[1]]++
}
# 所有行處理完后執(zhí)行
END {
print "日志級(jí)別統(tǒng)計(jì):"
# 遍歷統(tǒng)計(jì)結(jié)果并打印
for(l in level) {
print l ": " level[l] "條"
}
}
' app.log
# 輸出:
日志級(jí)別統(tǒng)計(jì):
INFO: 1條
ERROR: 2條
WARN: 1條
2. 接口調(diào)用日志分析
(1) 接口日志示例
2024-02-14 10:00:01 [api=/user/login] cost=120ms status=200
2024-02-14 10:00:02 [api=/order/create] cost=500ms status=500
2024-02-14 10:00:03 [api=/user/info] cost=80ms status=200
(2) 分析接口響應(yīng)時(shí)間
# 命令:統(tǒng)計(jì)每個(gè)接口的平均響應(yīng)時(shí)間
awk '
{
# 提取接口名和響應(yīng)時(shí)間
api=$3 # 獲取接口名稱列
gsub(/\[|\]/, "", api) # 去掉方括號(hào)
gsub(/.*=|ms/, "", $4) # 提取響應(yīng)時(shí)間的數(shù)字部分
# 統(tǒng)計(jì)數(shù)據(jù)
apis[api] += $4 # 累加響應(yīng)時(shí)間
count[api]++ # 統(tǒng)計(jì)調(diào)用次數(shù)
}
END {
print "接口平均響應(yīng)時(shí)間:"
for(a in apis) {
printf "%s: %.2fms\n", a, apis[a]/count[a]
}
}' api.log
# 輸出:
接口平均響應(yīng)時(shí)間:
api=/user/login: 120.00ms
api=/order/create: 500.00ms
api=/user/info: 80.00ms
3. 錯(cuò)誤日志分析
(1) 異常堆棧日志
> cat Service.log
2024-02-14 10:00:01 [ERROR] NullPointerException: 空指針異常
at com.example.UserService.getUser(UserService.java:15)
at com.example.UserController.login(UserController.java:10)
2024-02-14 10:00:02 [ERROR] SQLException: 數(shù)據(jù)庫(kù)連接失敗
at com.example.OrderService.create(OrderService.java:25)
(2) 提取完整異常信息
# 命令:提取異常信息及其堆棧
awk '
# 匹配錯(cuò)誤行
/ERROR/ {
print "\n發(fā)現(xiàn)異常:"
print $0 # 打印錯(cuò)誤行
print "異常堆棧:"
}
# 匹配堆棧信息(以空格開頭的行)
/^[[:space:]]/ {
print $0 # 打印堆棧行
}
' Service.log
# 輸出:
發(fā)現(xiàn)異常:
2024-02-14 10:00:01 [ERROR] NullPointerException: 空指針異常
異常堆棧:
at com.example.UserService.getUser(UserService.java:15)
at com.example.UserController.login(UserController.java:10)
發(fā)現(xiàn)異常:
2024-02-14 10:00:02 [ERROR] SQLException: 數(shù)據(jù)庫(kù)連接失敗
異常堆棧:
at com.example.OrderService.create(OrderService.java:25)
4. 性能問(wèn)題分析
(1) 數(shù)據(jù)庫(kù)慢查詢?nèi)罩?/p>
2024-02-14 10:00:01 [SLOW_QUERY] cost=2.5s sql="SELECT * FROM orders WHERE user_id=123"
2024-02-14 10:00:05 [SLOW_QUERY] cost=1.8s sql="UPDATE users SET status=1"
2024-02-14 10:00:10 [SLOW_QUERY] cost=3.1s sql="SELECT * FROM order_items"
(2) 分析慢查詢
# 命令:分析超過(guò)2秒的慢查詢
awk '
{
# 提取執(zhí)行時(shí)間,去掉s得到純數(shù)字
time_str = $4
gsub("cost=|s", "", time_str) # 將cost=和s都替換為空
time = time_str + 0 # 轉(zhuǎn)換為數(shù)字
# 提取完整SQL語(yǔ)句
sql = substr($0, index($0, "sql="))
# 如果查詢時(shí)間超過(guò)2秒
if(time > 2) {
printf "\n時(shí)間:%s %s\n", $1, $2
printf "耗時(shí):%.1f秒\n", time
printf "SQL:%s\n", sql
printf "----------\n"
}
}' slow_query.log
# 輸出:
時(shí)間:2024-02-14 10:00:01
耗時(shí):2.5秒
SQL:"SELECT * FROM orders WHERE user_id=123"
----------
時(shí)間:2024-02-14 10:00:10
耗時(shí):3.1秒
SQL:"SELECT * FROM order_items"
----------
5. 監(jiān)控告警分析
(1) 告警日志
2024-02-14 10:00:01 [ALERT] service=order-service type=cpu_high value=92%
2024-02-14 10:00:05 [ALERT] service=user-service type=memory_high value=85%
2024-02-14 10:00:10 [ALERT] service=order-service type=disk_usage value=95%
(2) 統(tǒng)計(jì)告警情況
# 命令:按服務(wù)統(tǒng)計(jì)告警
awk '
BEGIN {
print "=== 告警分析報(bào)告 ==="
print "分析時(shí)間:" strftime("%Y-%m-%d %H:%M:%S")
print "-------------------"
}
/\[ALERT\]/ { # 只處理包含[ALERT]的行
# 提取基本信息
gsub(/service=|type=|value=|%|threshold=/, " ", $0)
for(i=1; i<=NF; i++) {
if($i == "[ALERT]") {
service = $(i+1) # 服務(wù)名
type = $(i+2) # 告警類型
value = $(i+3) # 當(dāng)前值
threshold = $(i+4) # 閾值
}
}
# 計(jì)算超出閾值的百分比
exceed = value - threshold
# 根據(jù)超出程度分級(jí)
if(exceed >= 20) {
level = "嚴(yán)重"
} else if(exceed >= 10) {
level = "警告"
} else {
level = "注意"
}
# 統(tǒng)計(jì)信息
services[service]++
types[type]++
levels[level]++
# 記錄最大值和時(shí)間
if(max_value[type] < value) {
max_value[type] = value
max_time[type] = $1 " " $2
}
# 保存詳細(xì)信息
details[++count] = sprintf("時(shí)間:%s %s\n服務(wù):%-15s 類型:%-12s 當(dāng)前值:%d%% (超出閾值:%d%%) 級(jí)別:%s",
$1, $2, service, type, value, exceed, level)
}
END {
# 1. 告警級(jí)別統(tǒng)計(jì)
print "\n1. 告警級(jí)別分布:"
for(l in levels) {
printf "%-6s: %d次\n", l, levels[l]
}
# 2. 服務(wù)告警統(tǒng)計(jì)
print "\n2. 服務(wù)告警統(tǒng)計(jì):"
for(svc in services) {
printf "%-20s: %d次告警\n", svc, services[svc]
}
# 3. 告警類型統(tǒng)計(jì)
print "\n3. 告警類型統(tǒng)計(jì):"
for(t in types) {
printf "%-15s: %d次\n", t, types[t]
printf " 最大值: %d%% (發(fā)生時(shí)間: %s)\n", max_value[t], max_time[t]
}
# 4. 詳細(xì)告警記錄
print "\n4. 詳細(xì)告警記錄:"
print "-------------------"
for(i=1; i<=count; i++) { # 使用count而不是NR
print details[i] "\n----------"
}
}' alert.log
# 輸出:
告警統(tǒng)計(jì):
=== 告警分析報(bào)告 ===
分析時(shí)間:2025-02-14 21:34:52
-------------------
1. 告警級(jí)別分布:
注意 : 3次
警告 : 2次
2. 服務(wù)告警統(tǒng)計(jì):
order-service : 3次告警
user-service : 2次告警
3. 告警類型統(tǒng)計(jì):
memory_high : 2次
最大值: 95% (發(fā)生時(shí)間: 2024-02-14 10:00:20)
cpu_high : 2次
最大值: 92% (發(fā)生時(shí)間: 2024-02-14 10:00:01)
disk_usage : 1次
最大值: 95% (發(fā)生時(shí)間: 2024-02-14 10:00:10)
4. 詳細(xì)告警記錄:
-------------------
時(shí)間:2024-02-14 10:00:01
服務(wù):order-service 類型:cpu_high 當(dāng)前值:92% (超出閾值:12%) 級(jí)別:警告
----------
時(shí)間:2024-02-14 10:00:05
服務(wù):user-service 類型:memory_high 當(dāng)前值:85% (超出閾值:5%) 級(jí)別:注意
----------
時(shí)間:2024-02-14 10:00:10
服務(wù):order-service 類型:disk_usage 當(dāng)前值:95% (超出閾值:5%) 級(jí)別:注意
----------
時(shí)間:2024-02-14 10:00:15
服務(wù):user-service 類型:cpu_high 當(dāng)前值:88% (超出閾值:8%) 級(jí)別:注意
----------
時(shí)間:2024-02-14 10:00:20
服務(wù):order-service 類型:memory_high 當(dāng)前值:95% (超出閾值:15%) 級(jí)別:警告
----------
這些是日常工作中最常用到的日志分析場(chǎng)景。我們從最簡(jiǎn)單的日志過(guò)濾開始,逐步深入到了復(fù)雜的統(tǒng)計(jì)分析。記住,解決復(fù)雜的問(wèn)題時(shí),可以先拆分成小步驟,一步一步來(lái)處理。
總結(jié)
看到這里,相信你已經(jīng)掌握了 awk 這個(gè)文本處理利器的基本使用。從最初的字段提取,到復(fù)雜的日志分析,再到性能監(jiān)控,只要靈活運(yùn)用,awk 幾乎能解決所有的文本處理需求。
不過(guò),真實(shí)的工作環(huán)境中,往往需要 多個(gè)命令配合使用 才能達(dá)到最好的效果。就像武俠小說(shuō)里的武功招式,單招玩得再熟,也不如組合技來(lái)得實(shí)用。
比如:
# 先用grep找出錯(cuò)誤日志,再用awk分析
grep "ERROR" app.log | awk '{print $1,$2}'
# 用sed處理格式,再用awk統(tǒng)計(jì)
sed 's/"http://g' access.log | awk '{count[$1]++} END{for(ip in count) print ip,count[ip]}'
下一篇,我將為大家?guī)?lái) grep、sed、awk 這三劍客的組合應(yīng)用,教你如何在實(shí)戰(zhàn)中發(fā)揮它們的最大威力。相信這些實(shí)用的"組合技",一定能幫你在日常工作中事半功倍。