linux日志分析

Linux日志文件

一、主要日志文件位置及含义

系统核心日志

  • /var/log/messages (CentOS/RHEL) 或 /var/log/syslog (Ubuntu/Debian)

    • 作用:记录系统整体运行状态,包括服务启动/停止、内核消息、驱动程序信息等
    • 适合分析:系统级问题、服务异常、硬件兼容性
  • /var/log/secure (CentOS) 或 /var/log/auth.log (Ubuntu)

    • 作用:专门记录安全相关事件,包括SSH登录、sudo提权、用户切换等
    • 适合分析:登录失败、暴力破解、权限异常
  • /var/log/dmesg

    • 作用:内核环缓冲区日志,记录硬件检测、驱动加载、内核错误
    • 适合分析:硬件故障、驱动冲突、启动时硬件问题
  • **/var/log/boot.log

    • 作用:系统启动过程日志,记录各服务启动顺序和结果
    • 适合分析:启动缓慢、服务启动失败
  • **/var/log/cron

    • 作用:定时任务执行日志,记录crontab任务的执行时间和结果
    • 适合分析:定时任务失败、cron配置错误

二、基础查看命令详解

1. 实时监控类命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 命令:tail -f /var/log/messages
# 含义:
# - tail:显示文件末尾内容
# - -f (follow):实时跟踪文件新增内容,不会自动退出
# 应用场景:重现问题时实时观察日志输出
# 退出方式:按 Ctrl+C 终止

# 示例:监控最后100行并持续更新
tail -100f /var/log/syslog
# -100:先显示最后100行,然后进入跟踪模式
# 比直接tail -f更有用,可以看到问题发生前的上下文

# 同时监控多个日志文件
tail -f /var/log/messages /var/log/secure
# 输出格式:[文件路径] 实际日志内容
# 这样可以关联分析多个服务的交互问题

输出示例解读

1
2
Nov 10 14:25:01 server sshd[12345]: Failed password for root from 192.168.1.100 port 3321 ssh2
# 时间戳    主机名   进程名[PID]: 具体消息内容

2. 分页浏览工具

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 命令:less /var/log/messages
# 核心优势:
# 1. 加载大文件速度快(不像vim会一次性加载全部)
# 2. 支持前后翻页,适合深入分析
# 3. 搜索功能强大,不会阻塞

# less中的关键操作:
# /keyword  向后搜索关键词(输入后按回车)
# ?keyword  向前搜索关键词
# n         跳转到下一个匹配项 (next)
# N         跳转到上一个匹配项
# G         跳到文件末尾 (goto end)
# g         跳到文件开头
# 空格键    向下翻页
# b         向上翻页 (back)
# q         退出 (quit)

# 实用技巧:less +F /var/log/messages
# +F参数:启动时直接进入跟踪模式(类似tail -f)
# 按Ctrl+C退出跟踪模式,转为普通浏览模式

3. 精确时间范围查看

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 命令:grep "Nov 10" /var/log/messages
# 含义:筛选包含"Nov 10"字符串的行(11月10日的日志)
# 局限性:会匹配到所有含该字符串的内容,可能不够精确

# 更精确的时间匹配(使用正则表达式)
grep "^Nov 10 14:2[0-9]" /var/log/messages
# ^:行首锚定,确保从时间戳开始匹配
# 14:2[0-9]:匹配14:20-14:29的时间段

# 使用awk提取特定时间范围
awk '$2=="10" && $3>="14:00:00" && $3<="14:30:00"' /var/log/messages
# $2:第二列(日期)
# $3:第三列(时间)
# 逻辑:日期是10号且时间在14:00-14:30之间

三、进阶分析技巧深度解析

1. grep搜索实战详解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 基础错误查找(不区分大小写)
grep -i "error" /var/log/messages
# -i (ignore case):忽略大小写,匹配Error、ERROR、error等
# 为什么用:避免遗漏不同格式的错误信息

# 同时查找多个关键词(使用正则表达式)
grep -i "error\|fail\|critical\|denied" /var/log/messages
# \|:正则表达式的"或"操作符
# 为什么用:不同类型的错误可能使用不同词汇描述

# 显示匹配行的上下文(最关键的技巧)
grep -C 5 -i "fatal" /var/log/messages
# -C 5 (Context):显示匹配行前后各5行
# 为什么用:单个错误行往往信息不足,需要上下文定位根本原因
# -A 5 (After):只显示后5行
# -B 5 (Before):只显示前5行

# 排除干扰信息(反向匹配)
grep "error" /var/log/messages | grep -v "debug"
# |:管道符,将前一个命令的输出作为后一个命令的输入
# grep -v:反向匹配,排除包含指定关键词的行
# 为什么用:开发环境可能有很多debug日志干扰

# 统计错误出现次数(量化分析)
grep -c "error" /var/log/messages
# -c (count):只输出匹配的行数,不显示内容
# 应用场景:判断问题的频率和严重程度

# 显示匹配的文件名(多文件搜索时)
grep -l "error" /var/log/*
# -l (list):只显示包含匹配内容的文件名
# 应用场景:快速定位哪些日志文件有问题

实战案例:SSH暴力破解分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 第一步:提取所有失败登录的IP
grep "Failed password" /var/log/secure | awk '{print $11}' | sort | uniq -c | sort -nr
# 分解说明:
# grep "Failed password":筛选认证失败记录
# awk '{print $11}':提取第11列(IP地址)
# sort:排序(为uniq做准备)
# uniq -c:统计每个IP出现的次数
# sort -nr:按数字降序排列(-n:数字排序, -r:逆序)

# 典型输出解读:
#    253 192.168.1.100
#     45 192.168.1.101
# IP 192.168.1.100 在日志中出现了253次,是主要攻击源

# 第二步:查看这些IP的详细攻击时间
grep "192.168.1.100" /var/log/secure | head -5
# 查看该IP的前5条记录,确认攻击模式

2. 排序与统计高级用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 命令:sort /var/log/messages | uniq -c | sort -nr | head -20
# 完整含义:找出日志中最常出现的20种模式

# 分步解析:
1. sort /var/log/messages
   - 先对整个文件排序(uniq只能处理相邻的重复行)

2. uniq -c
   - 合并相邻的重复行
   - -c:在每行前添加出现次数
   - 输出示例:"   127 Nov 10 14:25:01 server crond[1234]: ..."

3. sort -nr
   - -n:按数字大小排序(默认是字典序)
   - -r:逆序排列(从大到小)

4. head -20
   - 只显示前20行(最常见的前20种日志)

# 应用场景:快速识别系统的主要活动在做什么
# 例如发现"Out of memory"出现上千次,说明内存泄漏严重

Web日志分析案例:找出最多访问IP

1
2
3
4
5
6
7
8
# 命令:awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -nr | head -10
# 详细解释:
# awk '{print $1}':提取访问日志的第一列(客户端IP)
# 后续命令同上,最终得到TOP 10访问IP

# 进阶:找出访问最频繁的URL
awk '{print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -nr | head -10
# $7:通常表示HTTP请求中的URL路径

3. 时间戳分析实战

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 命令:sed -n '/14:00:00/,/14:30:00/p' /var/log/messages
# 含义:提取14:00:00到14:30:00之间的日志
# sed -n:安静模式,只打印匹配的行
# /start/,/end/:范围匹配,从开始模式到结束模式
# p:打印命令

# 为什么用sed而不是grep?
# - sed可以按行号或模式范围提取,适合连续时间段
# - grep只能匹配单个模式

# 更精确的时间分析(使用awk)
awk '$3>="14:00:00" && $3<="14:30:00" {print}' /var/log/messages
# $3>=:第三列(时间)大于等于14:00:00
# &&:逻辑与操作符
# {print}:满足条件时执行打印操作

四、journalctl 现代日志系统详解

journalctl是systemd时代的日志管理工具,功能远超传统syslog

1. 基础用法

1
2
3
4
5
6
7
8
9
# 命令:journalctl
# 默认行为:显示所有日志,从最早到最新
# 特点:使用less作为分页器,支持所有less操作
# 退出:按q键

# 实时跟踪模式
journalctl -f
# -f (follow):类似tail -f,但功能更强大
# 优势:可以实时过滤,如 journalctl -f -u nginx

2. 按服务单元过滤(最常用)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 查看Nginx服务的所有日志
journalctl -u nginx
# -u (unit):指定systemd服务单元
# 为什么重要:传统日志需手动找文件,journalctl自动聚合

# 查看SSH服务的错误日志
journalctl -u sshd -p err
# -p err:只显示错误级别及以上的日志
# 日志级别:emerg(0), alert(1), crit(2), error(3), warning(4), notice(5), info(6), debug(7)

# 查看服务从启动到停止的所有日志
journalctl -u nginx --since "2024-11-10" --until "2024-11-11"
# --since/--until:时间范围过滤,支持多种格式

3. 进程与日志关联

1
2
3
4
5
6
7
8
# 查看特定PID的日志
journalctl _PID=1234
# _PID:systemd的日志字段,精确匹配进程ID
# 应用场景:某个进程崩溃,查看它的所有历史日志

# 查看特定用户的日志
journalctl _UID=1000
# _UID:用户ID,查看某个用户的所有操作日志

4. 启动日志分析(排查启动问题神器)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 列出所有启动记录
journalctl --list-boots
# 输出示例:
# -2 0f8c4c5f5d3a4e8a9b2c1d0e3f7a6b5c Mon 2024-11-09 ...
# -1 a1b2c3d4e5f678901234567890123456 Sun 2024-11-10 ...
#  0 c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f Mon 2024-11-11 ...
# 数字:启动ID,0表示当前启动,-1表示上一次
# 哈希:启动的唯一标识符

# 查看上一次启动的日志
journalctl -b -1
# -b:限制为启动日志
# -1:指定启动ID

# 查看某次启动的内核日志
journalctl -b 0 -k
# -k:只显示内核消息(类似dmesg)

5. 组合查询实战

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 查询Nginx在过去1小时的错误日志
journalctl -u nginx -p err --since "1 hour ago"
# 组合选项:
# -u nginx:服务过滤
# -p err:级别过滤
# --since:时间过滤

# 输出示例:
-- Logs begin at Mon 2024-11-11 08:00:00 CST, end at Mon 2024-11-11 14:30:12 CST. --
Nov 11 14:25:15 web01 nginx[1234]: 2024/11/11 14:25:15 [error] 1234#0: *5678 connect() failed (111: Connection refused) while connecting to upstream

# 解读:
# 时间戳 主机名 进程名[PID]: 具体错误消息
# [error]:错误级别
# 1234#0:进程ID和线程ID
# *5678:连接ID

五、实战案例分析详解

案例1:SSH登录失败完整排查流程

场景:发现服务器响应慢,怀疑有暴力破解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Step 1:统计失败登录次数
grep "Failed password" /var/log/secure | wc -l
# wc -l:统计行数(word count -lines)
# 如果数字很大(如>1000),说明确实存在暴力破解

# Step 2:找出攻击来源IP
grep "Failed password" /var/log/secure | awk '{print $11}' | sort | uniq -c | sort -nr | head -5
# $11的含义:在CentOS的secure日志中,IP地址通常是第11列
# 如果日志格式不同,先用head查看列数:head -1 /var/log/secure

# Step 3:查看攻击时间分布(判断是否持续攻击)
grep "192.168.1.100" /var/log/secure | awk '{print $1" "$2" "$3}' | uniq -c
# 输出示例:
#     50 Nov 10 14:2[0-9]:
#     50 Nov 10 14:3[0-9]:
# 说明每分钟50次尝试,是自动化攻击

# Step 4:查看是否有成功登录(判断是否已攻破)
grep "Accepted" /var/log/secure
# 如果找到了攻击IP的成功登录,说明威胁严重
# 应立即更换密码、检查后门

# Step 5:统计尝试的用户名
grep "Failed password" /var/log/secure | awk '{print $9}' | sort | uniq -c | sort -nr | head -10
# $9:通常是尝试登录的用户名
# 常见模式:root、admin、test等默认账户

案例2:Web服务502/503错误深度分析

场景:网站出现502 Bad Gateway错误

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Step 1:确认Nginx错误日志位置
# 查看Nginx配置:grep "error_log" /etc/nginx/nginx.conf
# 通常为:/var/log/nginx/error.log

# Step 2:实时观察错误
tail -f /var/log/nginx/error.log
# 观察到的典型错误:
# connect() failed (111: Connection refused) while connecting to upstream
# 含义:Nginx无法连接到后端服务(PHP-FPM/应用服务器)

# Step 3:同时监控PHP-FPM日志(确保看到完整上下文)
journalctl -f -u nginx -u php-fpm
# -f:实时跟踪
# -u nginx -u php-fpm:同时显示两个服务的日志,按时间混合排序
# 为什么重要:可以同时看到Nginx的请求和PHP-FPM的处理

# Step 4:检查系统资源(可能原因)
journalctl -p warning | grep -E "memory|oom|kill"
# -p warning:查看警告及以上级别
# -E:扩展正则表达式
# 查找OOM(Out of Memory)错误,可能导致PHP-FPM被杀

# Step 5:分析错误频率
grep "2024/11/11" /var/log/nginx/error.log | awk '{print $2}' | cut -d: -f1,2 | uniq -c
# $2:日期字段,如2024/11/11
# cut -d: -f1,2:按冒号分割,取小时和分钟
# 输出示例:识别错误高发时段
#     50 14:25
#     30 14:26

案例3:磁盘I/O错误导致服务异常

场景:服务突然变慢,怀疑磁盘问题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# Step 1:查看内核日志中的硬件错误
dmesg | grep -i "error\|fail\|bad"
# dmesg:显示内核消息缓冲区
# -i:忽略大小写
# 典型输出:
# [12345.678] sd 0:0:1:0: [sdb] Add. Sense: Unrecovered read error

# Step 2:查看系统日志中的磁盘错误
journalctl -p err | grep -E "disk|I/O|sda|sdb"
# -p err:只显示错误级别
# -E:扩展正则,同时匹配多个关键词
# 典型输出:
# Nov 11 14:30:12 server kernel: Buffer I/O error on dev sdb1, logical block 123456

# Step 3:检查SMART状态(需要smartmontools)
smartctl -a /dev/sda | grep -E "Error|Fail|Reallocated"
# 如果看到"Reallocated_Sector_Ct"数值很大,说明磁盘有坏道

# Step 4:统计错误频率
journalctl --since "24 hours ago" | grep "I/O error" | wc -l
# 如果数量持续增长,说明故障在恶化,需要立即备份

案例4:服务启动失败根本原因定位

场景:systemctl start nginx失败

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Step 1:查看服务状态(快速诊断)
systemctl status nginx
# 关键信息:
# Active: failed (Result: exit-code)  # 服务已失败
# Process: 1234 ExecStart=/usr/sbin/nginx (code=exited, status=1/FAILURE)
# status=1:通用错误码

# Step 2:获取详细日志
journalctl -u nginx -n 50
# -n 50:显示最后50行(类似tail -50)
# 关键信息:通常会显示具体错误,如 "nginx: [emerg] bind() to 0.0.0.0:80 failed"

# Step 3:检查配置语法
nginx -t
# 为什么重要:90%的启动失败是配置错误
# 典型输出:
# nginx: [emerg] unknown directive "sever" in /etc/nginx/conf.d/site.conf:5
# 明确指出第5行有拼写错误(sever应为server)

# Step 4:查看启动过程中的所有日志
journalctl -u nginx --since "5 minutes ago" -o verbose
# -o verbose:详细输出,显示所有字段
# 包含:_SYSTEMD_UNIT, _PID, _UID等元数据

# Step 5:检查依赖服务
systemctl list-dependencies nginx
# 显示:nginx.service依赖于network.target
# 如果网络服务未启动,Nginx也会失败

六、日志分析脚本详解

自动化分析脚本(带详细注释)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
cat > /usr/local/bin/log_analyzer.sh << 'EOF'
#!/bin/bash
# 脚本功能:自动分析系统日志并生成报告
# 使用方法:./log_analyzer.sh

LOGFILE=/var/log/messages
DATE=$(date +%b\ %d)  # 例如:Nov 11
REPORT=/tmp/log_report_$(date +%Y%m%d).txt

# 1. 基础统计
echo "=== 日志基础统计 ===" | tee $REPORT
echo "总错误数: $(grep -ci "error" $LOGFILE)" | tee -a $REPORT
# tee命令:同时输出到屏幕和文件
# -a:追加模式

# 2. 最频繁的10种错误(带上下文)
echo -e "\n=== TOP 10 错误模式 ===" | tee -a $REPORT
grep -i "error" $LOGFILE | sort | uniq -c | sort -nr | head -10 | tee -a $REPORT
# -e:允许使用转义字符(如\n换行)

# 3. 安全事件分析
echo -e "\n=== 安全事件统计 ===" | tee -a $REPORT
FAILED_LOGINS=$(grep -c "Failed password" /var/log/secure 2>/dev/null || echo "0")
# 2>/dev/null:错误重定向到黑洞(文件不存在时不报错)
# || echo "0":如果命令失败,显示0
echo "SSH失败登录次数: $FAILED_LOGINS" | tee -a $REPORT

# 4. 今天的警告日志
echo -e "\n=== 今日警告日志 ===" | tee -a $REPORT
grep -i "warning" $LOGFILE | grep "$DATE" | tail -5 | tee -a $REPORT
# tail -5:只显示最近5条,避免过多

# 5. 内存不足检查
echo -e "\n=== OOM Killer 活动 ===" | tee -a $REPORT
journalctl -p warning | grep -i "oom-killer" | tail -3 | tee -a $REPORT
# OOM Killer:内存不足时杀进程的机制
# 如果有输出,说明系统曾内存耗尽

echo -e "\n报告已生成: $REPORT"
EOF

chmod +x /usr/local/bin/log_analyzer.sh

脚本运行示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
./log_analyzer.sh

# 输出解读:
=== 日志基础统计 ===
总错误数: 158
# 说明:系统共有158条错误记录,需要关注

=== TOP 10 错误模式 ===
     45 Nov 11 14:25:01 server CRON[1234]: (root) CMD (/usr/local/bin/backup.sh)
     32 Nov 11 14:26:12 server kernel: [UFW BLOCK] IN=eth0 OUT= MAC=...
# 解读:CRON任务出现45次,是正常的定时任务
# UFW BLOCK出现32次,是防火墙正常拦截

=== 安全事件统计 ===
SSH失败登录次数: 0
# 良好:没有暴力破解尝试

=== OOM Killer 活动 ===
# 为空:系统未发生内存耗尽

七、日志轮转机制详解

logrotate工作原理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 查看logrotate配置
cat /etc/logrotate.d/nginx
# 典型配置:
# /var/log/nginx/*.log {
#     daily                    # 每天轮转一次
#     missingok                # 日志不存在时不报错
#     rotate 52                # 保留52个旧日志(52天)
#     compress                 # 压缩旧日志(gzip)
#     delaycompress            # 延迟压缩(下次轮转时压缩)
#     notifempty               # 日志为空时不轮转
#     create 0640 nginx adm    # 新日志权限和属主
#     sharedscripts            # 多个日志共享脚本
#     postrotate               # 轮转后执行的命令
#         if [ -f /var/run/nginx.pid ]; then
#             kill -USR1 $(cat /var/run/nginx.pid)  # 通知Nginx重新打开日志
#         fi
#     endscript
# }

# 手动执行日志轮转(测试配置)
logrotate -d /etc/logrotate.d/nginx
# -d (debug):测试模式,不实际执行,显示详细过程

# 强制立即轮转
logrotate -f /etc/logrotate.d/nginx
# -f (force):强制执行,忽略时间条件

八、常见问题快速定位表(带解读)

问题现象查看命令关键输出解读解决方案
系统启动失败journalctl -b -1红色"FAILED"行查看失败服务的依赖关系
SSH无法登录tail -f /var/log/secure“Connection closed by …”检查防火墙、SELinux
磁盘空间不足df -h + du -sh /*100%使用率清理大文件或扩展分区
内存耗尽journalctl -p err | grep oom“Kill process”增加内存或优化应用
CPU 100%top + journalctl -p warning“soft lockup”检查内核版本、驱动
网络超时grep "timeout" /var/log/messages“Connection timed out”检查网络配置、DNS
权限被拒绝grep "denied" /var/log/audit/audit.logavc: denied调整SELinux策略
服务频繁重启journalctl -u <服务> --since "1 hour ago"多条"Starting…"检查配置、资源限制
数据库慢查询grep "slow query" /var/log/mysql.logQuery_time: 10.5s优化SQL或添加索引
SSL证书过期grep "expired" /var/log/httpd/error_log“certificate expired”更新证书

九、高级技巧与最佳实践

1. 日志格式标准化

1
2
3
4
5
6
# 使用logger命令写入自定义日志
logger -t MYAPP -p user.warning "Database connection failed"
# -t:标签(tag),在日志中显示为进程名
# -p:优先级(facility.level)
# 在/var/log/messages中显示:
# Nov 11 14:30:00 server MYAPP: Database connection failed

2. 日志关联分析

1
2
3
4
5
6
# 同时查看多个关联服务的日志(时间对齐)
journalctl -u nginx -u php-fpm --since "10 minutes ago" -o short
# -o short:短格式,只显示时间和服务名,便于对比
# 输出示例:
# 14:25:10 web01 nginx[1234]: GET /api/user
# 14:25:11 web01 php-fpm[5678]: PHP Fatal error:...

3. 性能分析

1
2
3
4
5
# 统计每秒日志条数(判断系统负载)
journalctl --since "1 hour ago" | awk '{print $3}' | cut -d: -f1,2,3 | uniq -c | sort -nr | head
# 输出解读:
#    500 14:25:30
# 表示在14:25:30这一秒内有500条日志,系统可能异常繁忙

4. 日志远程传输(集中管理)

1
2
3
4
5
# 配置rsyslog发送到远程服务器
echo '*.* @@192.168.1.200:514' >> /etc/rsyslog.conf
# @@:TCP协议(@为UDP)
# 514:syslog默认端口
systemctl restart rsyslog

总结建议

  1. 理解日志结构:时间戳、主机名、进程名、消息体四部分
  2. 善用管道组合:grep+awk+sort+uniq是黄金组合
  3. 重视上下文:错误前5行和后5行往往有关键线索
  4. 时间范围最关键:先确定问题发生时间,再精确提取
  5. 从全局到局部:先用journalctl看整体,再用grep精确定位

掌握这些,你就能像资深运维一样快速诊断90%的Linux问题!

本文采用 CC BY-NC-SA 4.0 许可协议
使用 Hugo 构建
主题 StackJimmy 设计