1. 实时监控配置
1.1 基础实时监控设置
# 基本实时HTML监控
goaccess /var/log/nginx/access.log \
--log-format=COMBINED \
--real-time-html \
--ws-url=ws://localhost:7890 \
--port=7890 \
--addr=0.0.0.0 \
-o /var/www/html/goaccess.html
# 配置文件方式
# /etc/goaccess/goaccess.conf
real-time-html true
port 7890
addr 0.0.0.0
ws-url ws://localhost:7890
output /var/www/html/goaccess.html
1.2 高级实时监控配置
# /etc/goaccess/realtime.conf
# 高级实时监控配置
# 基础设置
time-format %H:%M:%S
date-format %d/%b/%Y
log-format %h %^[%d:%t %^] "%r" %s %b "%R" "%u"
# 实时设置
real-time-html true
port 7890
addr 0.0.0.0
ws-url ws://your-domain.com:7890
# 输出设置
output /var/www/html/realtime.html
html-report-title "实时Web访问监控"
# 性能优化
enable-mmap true
cache-lcnum 1000000
cache-ncnum 65536
# 过滤设置
ignore-ip 127.0.0.1
ignore-ip ::1
ignore-status 200
ignore-status 304
# 静态文件
static-file .css
static-file .js
static-file .jpg
static-file .png
static-file .gif
static-file .ico
static-file .pdf
static-file .zip
# GeoIP
geoip-database /usr/share/GeoIP/GeoIP.dat
# 显示设置
color-scheme 1
no-progress true
1.3 多日志文件实时监控
#!/bin/bash
# multi_log_monitor.sh - 多日志文件实时监控脚本
# 配置变量
LOG_DIR="/var/log/nginx"
OUTPUT_DIR="/var/www/html/goaccess"
CONFIG_FILE="/etc/goaccess/goaccess.conf"
# 创建输出目录
mkdir -p $OUTPUT_DIR
# 主站点监控
echo "启动主站点实时监控..."
goaccess $LOG_DIR/access.log \
--config-file=$CONFIG_FILE \
--real-time-html \
--ws-url=ws://localhost:7890 \
--port=7890 \
--addr=0.0.0.0 \
--output=$OUTPUT_DIR/main_site.html &
# API监控
echo "启动API实时监控..."
goaccess $LOG_DIR/api.log \
--config-file=$CONFIG_FILE \
--real-time-html \
--ws-url=ws://localhost:7891 \
--port=7891 \
--addr=0.0.0.0 \
--output=$OUTPUT_DIR/api.html &
# 移动端监控
echo "启动移动端实时监控..."
goaccess $LOG_DIR/mobile.log \
--config-file=$CONFIG_FILE \
--real-time-html \
--ws-url=ws://localhost:7892 \
--port=7892 \
--addr=0.0.0.0 \
--output=$OUTPUT_DIR/mobile.html &
# 错误日志监控
echo "启动错误日志监控..."
goaccess $LOG_DIR/error.log \
--log-format='%d %t [%L] %^ %^ %^ %m' \
--date-format='%Y/%m/%d' \
--time-format='%H:%M:%S' \
--real-time-html \
--ws-url=ws://localhost:7893 \
--port=7893 \
--addr=0.0.0.0 \
--output=$OUTPUT_DIR/errors.html &
echo "所有监控已启动"
echo "访问地址:"
echo "主站点: http://localhost/goaccess/main_site.html"
echo "API: http://localhost/goaccess/api.html"
echo "移动端: http://localhost/goaccess/mobile.html"
echo "错误日志: http://localhost/goaccess/errors.html"
# 等待所有后台进程
wait
2. 告警系统设计
2.1 基础告警脚本
#!/usr/bin/env python3
# goaccess_alerting.py - GoAccess告警系统
import json
import time
import smtplib
import requests
import subprocess
from datetime import datetime, timedelta
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from collections import defaultdict
import logging
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/var/log/goaccess_alerts.log'),
logging.StreamHandler()
]
)
class GoAccessAlerting:
def __init__(self, config_file='alert_config.json'):
self.config = self.load_config(config_file)
self.alert_history = defaultdict(list)
self.thresholds = self.config.get('thresholds', {})
self.notification_config = self.config.get('notifications', {})
def load_config(self, config_file):
"""加载配置文件"""
try:
with open(config_file, 'r', encoding='utf-8') as f:
return json.load(f)
except FileNotFoundError:
# 默认配置
return {
'thresholds': {
'requests_per_minute': 1000,
'error_rate_percent': 5.0,
'response_time_ms': 2000,
'unique_visitors_per_hour': 10000,
'bandwidth_mbps': 100,
'status_4xx_per_minute': 50,
'status_5xx_per_minute': 10
},
'notifications': {
'email': {
'enabled': True,
'smtp_server': 'localhost',
'smtp_port': 587,
'username': '',
'password': '',
'from_email': 'alerts@example.com',
'to_emails': ['admin@example.com']
},
'slack': {
'enabled': False,
'webhook_url': ''
},
'webhook': {
'enabled': False,
'url': '',
'headers': {}
}
},
'log_files': [
'/var/log/nginx/access.log'
],
'check_interval': 60
}
def get_goaccess_stats(self, log_file):
"""获取GoAccess统计数据"""
cmd = [
'goaccess', log_file,
'--log-format=COMBINED',
'--json-pretty-print'
]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode == 0:
return json.loads(result.stdout)
else:
logging.error(f"GoAccess执行失败: {result.stderr}")
return None
except subprocess.TimeoutExpired:
logging.error("GoAccess执行超时")
return None
except Exception as e:
logging.error(f"执行GoAccess时出错: {e}")
return None
def calculate_metrics(self, stats):
"""计算关键指标"""
if not stats:
return {}
metrics = {}
# 总请求数
general = stats.get('general', {})
metrics['total_requests'] = general.get('total_requests', 0)
metrics['unique_visitors'] = general.get('unique_visitors', 0)
metrics['bandwidth'] = general.get('bandwidth', 0)
# 计算每分钟请求数(假设日志是最近一小时的)
metrics['requests_per_minute'] = metrics['total_requests'] / 60
# 状态码统计
status_codes = stats.get('status_codes', {}).get('data', [])
total_requests = metrics['total_requests']
status_4xx = sum(item['hits'] for item in status_codes
if item['data'].startswith('4'))
status_5xx = sum(item['hits'] for item in status_codes
if item['data'].startswith('5'))
metrics['status_4xx_count'] = status_4xx
metrics['status_5xx_count'] = status_5xx
metrics['status_4xx_per_minute'] = status_4xx / 60
metrics['status_5xx_per_minute'] = status_5xx / 60
# 错误率
if total_requests > 0:
metrics['error_rate_percent'] = ((status_4xx + status_5xx) / total_requests) * 100
else:
metrics['error_rate_percent'] = 0
# 带宽(转换为Mbps)
metrics['bandwidth_mbps'] = metrics['bandwidth'] / (1024 * 1024) / 60 # 每分钟Mbps
return metrics
def check_thresholds(self, metrics):
"""检查阈值"""
alerts = []
for metric, value in metrics.items():
if metric in self.thresholds:
threshold = self.thresholds[metric]
if value > threshold:
alert = {
'type': 'threshold_exceeded',
'metric': metric,
'value': value,
'threshold': threshold,
'timestamp': datetime.now().isoformat(),
'severity': self.get_severity(metric, value, threshold)
}
alerts.append(alert)
return alerts
def get_severity(self, metric, value, threshold):
"""确定告警严重程度"""
ratio = value / threshold
if ratio >= 3.0:
return 'critical'
elif ratio >= 2.0:
return 'high'
elif ratio >= 1.5:
return 'medium'
else:
return 'low'
def should_send_alert(self, alert):
"""判断是否应该发送告警(避免重复告警)"""
metric = alert['metric']
now = datetime.now()
# 检查最近的告警历史
recent_alerts = [
alert_time for alert_time in self.alert_history[metric]
if now - alert_time < timedelta(minutes=30)
]
# 如果30分钟内已有告警,则不重复发送
if recent_alerts:
return False
# 记录此次告警
self.alert_history[metric].append(now)
# 清理旧的告警记录
self.alert_history[metric] = [
alert_time for alert_time in self.alert_history[metric]
if now - alert_time < timedelta(hours=24)
]
return True
def send_email_alert(self, alerts):
"""发送邮件告警"""
email_config = self.notification_config.get('email', {})
if not email_config.get('enabled', False):
return
try:
# 创建邮件
msg = MIMEMultipart()
msg['From'] = email_config['from_email']
msg['To'] = ', '.join(email_config['to_emails'])
msg['Subject'] = f"GoAccess告警 - {len(alerts)}个异常检测到"
# 邮件内容
body = self.format_email_body(alerts)
msg.attach(MIMEText(body, 'html', 'utf-8'))
# 发送邮件
server = smtplib.SMTP(email_config['smtp_server'], email_config['smtp_port'])
if email_config.get('username'):
server.starttls()
server.login(email_config['username'], email_config['password'])
server.send_message(msg)
server.quit()
logging.info(f"邮件告警已发送,包含{len(alerts)}个告警")
except Exception as e:
logging.error(f"发送邮件告警失败: {e}")
def format_email_body(self, alerts):
"""格式化邮件内容"""
html = """
<html>
<head>
<style>
body { font-family: Arial, sans-serif; }
.alert { margin: 10px 0; padding: 10px; border-left: 4px solid; }
.critical { border-color: #d32f2f; background-color: #ffebee; }
.high { border-color: #f57c00; background-color: #fff3e0; }
.medium { border-color: #fbc02d; background-color: #fffde7; }
.low { border-color: #388e3c; background-color: #e8f5e8; }
.metric { font-weight: bold; }
.value { color: #d32f2f; font-weight: bold; }
.timestamp { color: #666; font-size: 0.9em; }
</style>
</head>
<body>
<h2>GoAccess监控告警</h2>
<p>检测到以下异常情况:</p>
"""
for alert in alerts:
severity_class = alert['severity']
html += f"""
<div class="alert {severity_class}">
<div class="metric">指标: {alert['metric']}</div>
<div>当前值: <span class="value">{alert['value']:.2f}</span></div>
<div>阈值: {alert['threshold']}</div>
<div>严重程度: {alert['severity'].upper()}</div>
<div class="timestamp">时间: {alert['timestamp']}</div>
</div>
"""
html += """
<p>请及时检查系统状态。</p>
<p><em>此邮件由GoAccess监控系统自动发送</em></p>
</body>
</html>
"""
return html
def send_slack_alert(self, alerts):
"""发送Slack告警"""
slack_config = self.notification_config.get('slack', {})
if not slack_config.get('enabled', False):
return
try:
webhook_url = slack_config['webhook_url']
# 构建Slack消息
text = f"🚨 GoAccess告警:检测到{len(alerts)}个异常"
attachments = []
for alert in alerts:
color = {
'critical': 'danger',
'high': 'warning',
'medium': 'warning',
'low': 'good'
}.get(alert['severity'], 'warning')
attachment = {
'color': color,
'fields': [
{
'title': '指标',
'value': alert['metric'],
'short': True
},
{
'title': '当前值',
'value': f"{alert['value']:.2f}",
'short': True
},
{
'title': '阈值',
'value': str(alert['threshold']),
'short': True
},
{
'title': '严重程度',
'value': alert['severity'].upper(),
'short': True
}
],
'ts': int(datetime.now().timestamp())
}
attachments.append(attachment)
payload = {
'text': text,
'attachments': attachments
}
response = requests.post(webhook_url, json=payload, timeout=10)
response.raise_for_status()
logging.info(f"Slack告警已发送,包含{len(alerts)}个告警")
except Exception as e:
logging.error(f"发送Slack告警失败: {e}")
def send_webhook_alert(self, alerts):
"""发送Webhook告警"""
webhook_config = self.notification_config.get('webhook', {})
if not webhook_config.get('enabled', False):
return
try:
url = webhook_config['url']
headers = webhook_config.get('headers', {})
headers.setdefault('Content-Type', 'application/json')
payload = {
'timestamp': datetime.now().isoformat(),
'source': 'goaccess_monitoring',
'alert_count': len(alerts),
'alerts': alerts
}
response = requests.post(url, json=payload, headers=headers, timeout=10)
response.raise_for_status()
logging.info(f"Webhook告警已发送,包含{len(alerts)}个告警")
except Exception as e:
logging.error(f"发送Webhook告警失败: {e}")
def send_alerts(self, alerts):
"""发送所有类型的告警"""
if not alerts:
return
# 过滤需要发送的告警
alerts_to_send = [alert for alert in alerts if self.should_send_alert(alert)]
if not alerts_to_send:
logging.info("所有告警都在抑制期内,跳过发送")
return
logging.info(f"发送{len(alerts_to_send)}个告警")
# 发送各种类型的通知
self.send_email_alert(alerts_to_send)
self.send_slack_alert(alerts_to_send)
self.send_webhook_alert(alerts_to_send)
def run_check(self):
"""执行一次检查"""
logging.info("开始执行监控检查")
all_alerts = []
for log_file in self.config['log_files']:
logging.info(f"检查日志文件: {log_file}")
# 获取统计数据
stats = self.get_goaccess_stats(log_file)
if not stats:
continue
# 计算指标
metrics = self.calculate_metrics(stats)
logging.info(f"计算得到指标: {metrics}")
# 检查阈值
alerts = self.check_thresholds(metrics)
if alerts:
logging.warning(f"检测到{len(alerts)}个告警")
all_alerts.extend(alerts)
# 发送告警
if all_alerts:
self.send_alerts(all_alerts)
else:
logging.info("未检测到异常")
def run_continuous(self):
"""持续监控模式"""
logging.info("启动持续监控模式")
interval = self.config.get('check_interval', 60)
try:
while True:
self.run_check()
logging.info(f"等待{interval}秒后进行下次检查")
time.sleep(interval)
except KeyboardInterrupt:
logging.info("监控被用户中断")
except Exception as e:
logging.error(f"监控过程中出错: {e}")
def create_default_config():
"""创建默认配置文件"""
config = {
'thresholds': {
'requests_per_minute': 1000,
'error_rate_percent': 5.0,
'response_time_ms': 2000,
'unique_visitors_per_hour': 10000,
'bandwidth_mbps': 100,
'status_4xx_per_minute': 50,
'status_5xx_per_minute': 10
},
'notifications': {
'email': {
'enabled': True,
'smtp_server': 'localhost',
'smtp_port': 587,
'username': '',
'password': '',
'from_email': 'alerts@example.com',
'to_emails': ['admin@example.com']
},
'slack': {
'enabled': False,
'webhook_url': 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'
},
'webhook': {
'enabled': False,
'url': 'https://your-webhook-endpoint.com/alerts',
'headers': {
'Authorization': 'Bearer YOUR_TOKEN'
}
}
},
'log_files': [
'/var/log/nginx/access.log'
],
'check_interval': 60
}
with open('alert_config.json', 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False)
print("默认配置文件已创建: alert_config.json")
print("请根据需要修改配置")
def main():
"""主函数"""
import argparse
parser = argparse.ArgumentParser(description='GoAccess告警系统')
parser.add_argument('--config', default='alert_config.json', help='配置文件路径')
parser.add_argument('--create-config', action='store_true', help='创建默认配置文件')
parser.add_argument('--once', action='store_true', help='执行一次检查后退出')
args = parser.parse_args()
if args.create_config:
create_default_config()
return
# 创建告警系统
alerting = GoAccessAlerting(args.config)
if args.once:
alerting.run_check()
else:
alerting.run_continuous()
if __name__ == "__main__":
main()
2.2 告警配置文件示例
{
"thresholds": {
"requests_per_minute": 1000,
"error_rate_percent": 5.0,
"response_time_ms": 2000,
"unique_visitors_per_hour": 10000,
"bandwidth_mbps": 100,
"status_4xx_per_minute": 50,
"status_5xx_per_minute": 10,
"bot_requests_percent": 30.0,
"suspicious_ips_count": 5
},
"notifications": {
"email": {
"enabled": true,
"smtp_server": "smtp.gmail.com",
"smtp_port": 587,
"username": "your-email@gmail.com",
"password": "your-app-password",
"from_email": "alerts@yourcompany.com",
"to_emails": [
"admin@yourcompany.com",
"devops@yourcompany.com"
]
},
"slack": {
"enabled": true,
"webhook_url": "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK",
"channel": "#alerts",
"username": "GoAccess Bot"
},
"webhook": {
"enabled": true,
"url": "https://your-monitoring-system.com/webhooks/goaccess",
"headers": {
"Authorization": "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json"
}
},
"sms": {
"enabled": false,
"provider": "twilio",
"account_sid": "YOUR_TWILIO_SID",
"auth_token": "YOUR_TWILIO_TOKEN",
"from_number": "+1234567890",
"to_numbers": ["+1987654321"]
}
},
"log_files": [
"/var/log/nginx/access.log",
"/var/log/apache2/access.log"
],
"check_interval": 60,
"alert_suppression": {
"same_alert_interval_minutes": 30,
"max_alerts_per_hour": 10
},
"business_hours": {
"enabled": true,
"start_hour": 9,
"end_hour": 18,
"timezone": "Asia/Shanghai",
"weekdays_only": true
}
}
3. 系统集成
3.1 Systemd服务配置
# /etc/systemd/system/goaccess-realtime.service
[Unit]
Description=GoAccess Real-time Web Log Analyzer
After=network.target nginx.service
Requires=nginx.service
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/html
ExecStart=/usr/bin/goaccess /var/log/nginx/access.log \
--config-file=/etc/goaccess/goaccess.conf \
--real-time-html \
--ws-url=ws://localhost:7890 \
--port=7890 \
--addr=0.0.0.0 \
--output=/var/www/html/goaccess.html
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
# 安全设置
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/www/html /tmp
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/goaccess-alerting.service
[Unit]
Description=GoAccess Alerting System
After=network.target
[Service]
Type=simple
User=goaccess
Group=goaccess
WorkingDirectory=/opt/goaccess-alerting
ExecStart=/usr/bin/python3 /opt/goaccess-alerting/goaccess_alerting.py
Restart=always
RestartSec=30
StandardOutput=journal
StandardError=journal
# 环境变量
Environment=PYTHONPATH=/opt/goaccess-alerting
Environment=CONFIG_FILE=/etc/goaccess/alert_config.json
[Install]
WantedBy=multi-user.target
3.2 Nginx反向代理配置
# /etc/nginx/sites-available/goaccess-monitoring
server {
listen 80;
server_name monitoring.example.com;
# 重定向到HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name monitoring.example.com;
# SSL配置
ssl_certificate /etc/ssl/certs/monitoring.crt;
ssl_certificate_key /etc/ssl/private/monitoring.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
# 安全头
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# 基本认证
auth_basic "GoAccess Monitoring";
auth_basic_user_file /etc/nginx/.htpasswd;
# 静态文件
location / {
root /var/www/html;
index goaccess.html;
try_files $uri $uri/ =404;
# 缓存控制
location ~* \.(html)$ {
expires 1m;
add_header Cache-Control "no-cache, must-revalidate";
}
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# WebSocket代理
location /ws {
proxy_pass http://127.0.0.1:7890;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket超时设置
proxy_read_timeout 86400;
proxy_send_timeout 86400;
}
# API端点(用于外部集成)
location /api/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# API认证
auth_request /auth;
}
# 认证端点
location = /auth {
internal;
proxy_pass http://127.0.0.1:8081/auth;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
# 健康检查
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# 日志配置
access_log /var/log/nginx/goaccess_access.log;
error_log /var/log/nginx/goaccess_error.log;
}
3.3 Docker部署配置
# docker-compose.yml
version: '3.8'
services:
goaccess:
image: allinurl/goaccess:latest
container_name: goaccess-realtime
volumes:
- /var/log/nginx:/opt/log:ro
- ./goaccess-data:/opt/goaccess
- ./goaccess.conf:/etc/goaccess/goaccess.conf:ro
ports:
- "7890:7890"
command: >
goaccess /opt/log/access.log
--config-file=/etc/goaccess/goaccess.conf
--real-time-html
--ws-url=ws://localhost:7890
--port=7890
--addr=0.0.0.0
--output=/opt/goaccess/goaccess.html
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:7890"]
interval: 30s
timeout: 10s
retries: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
goaccess-alerting:
build: ./alerting
container_name: goaccess-alerting
volumes:
- /var/log/nginx:/opt/log:ro
- ./alert_config.json:/app/alert_config.json:ro
- ./alerting-data:/app/data
environment:
- CONFIG_FILE=/app/alert_config.json
- LOG_LEVEL=INFO
depends_on:
- goaccess
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
nginx:
image: nginx:alpine
container_name: goaccess-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./goaccess-data:/var/www/html:ro
- ./ssl:/etc/ssl:ro
depends_on:
- goaccess
restart: unless-stopped
volumes:
goaccess-data:
alerting-data:
# alerting/Dockerfile
FROM python:3.9-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
curl \
goaccess \
&& rm -rf /var/lib/apt/lists/*
# 安装Python依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY goaccess_alerting.py .
COPY alert_config.json .
# 创建非root用户
RUN useradd -m -u 1000 goaccess
USER goaccess
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python3 -c "import requests; requests.get('http://localhost:8080/health', timeout=5)"
CMD ["python3", "goaccess_alerting.py"]
4. 监控仪表板
4.1 自定义HTML仪表板
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GoAccess监控仪表板</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f5f5;
color: #333;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1rem 2rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header h1 {
margin: 0;
font-size: 2rem;
}
.header .subtitle {
opacity: 0.9;
margin-top: 0.5rem;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.card {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: transform 0.2s ease;
}
.card:hover {
transform: translateY(-2px);
}
.card h3 {
color: #667eea;
margin-bottom: 1rem;
font-size: 1.2rem;
}
.metric {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
padding: 0.5rem 0;
border-bottom: 1px solid #eee;
}
.metric:last-child {
border-bottom: none;
}
.metric-label {
font-weight: 500;
}
.metric-value {
font-weight: bold;
font-size: 1.1rem;
}
.status-ok {
color: #4caf50;
}
.status-warning {
color: #ff9800;
}
.status-error {
color: #f44336;
}
.iframe-container {
background: white;
border-radius: 8px;
padding: 1rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
margin-bottom: 2rem;
}
.iframe-container iframe {
width: 100%;
height: 600px;
border: none;
border-radius: 4px;
}
.refresh-info {
text-align: center;
color: #666;
font-size: 0.9rem;
margin-top: 1rem;
}
.loading {
text-align: center;
padding: 2rem;
color: #666;
}
.alert {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 4px;
padding: 1rem;
margin-bottom: 1rem;
}
.alert.error {
background: #f8d7da;
border-color: #f5c6cb;
}
@media (max-width: 768px) {
.container {
padding: 1rem;
}
.grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="header">
<h1>GoAccess监控仪表板</h1>
<div class="subtitle">实时Web访问分析与监控</div>
</div>
<div class="container">
<!-- 状态概览 -->
<div class="grid">
<div class="card">
<h3>📊 实时统计</h3>
<div id="realtime-stats">
<div class="loading">加载中...</div>
</div>
</div>
<div class="card">
<h3>🚨 告警状态</h3>
<div id="alert-status">
<div class="loading">加载中...</div>
</div>
</div>
<div class="card">
<h3>🌍 地理分布</h3>
<div id="geo-stats">
<div class="loading">加载中...</div>
</div>
</div>
<div class="card">
<h3>⚡ 性能指标</h3>
<div id="performance-stats">
<div class="loading">加载中...</div>
</div>
</div>
</div>
<!-- GoAccess实时报告 -->
<div class="iframe-container">
<h3>📈 详细分析报告</h3>
<iframe src="/goaccess.html" title="GoAccess实时报告"></iframe>
</div>
<div class="refresh-info">
页面每30秒自动刷新 | 最后更新: <span id="last-update"></span>
</div>
</div>
<script>
// 更新时间显示
function updateLastUpdateTime() {
document.getElementById('last-update').textContent = new Date().toLocaleString();
}
// 获取统计数据
async function fetchStats() {
try {
const response = await fetch('/api/stats');
const data = await response.json();
// 更新实时统计
updateRealtimeStats(data.realtime);
// 更新告警状态
updateAlertStatus(data.alerts);
// 更新地理统计
updateGeoStats(data.geo);
// 更新性能指标
updatePerformanceStats(data.performance);
} catch (error) {
console.error('获取统计数据失败:', error);
showError('无法获取统计数据');
}
}
function updateRealtimeStats(stats) {
const container = document.getElementById('realtime-stats');
container.innerHTML = `
<div class="metric">
<span class="metric-label">当前访问者</span>
<span class="metric-value status-ok">${stats.current_visitors || 0}</span>
</div>
<div class="metric">
<span class="metric-label">每分钟请求</span>
<span class="metric-value">${stats.requests_per_minute || 0}</span>
</div>
<div class="metric">
<span class="metric-label">带宽使用</span>
<span class="metric-value">${stats.bandwidth || '0 MB/s'}</span>
</div>
<div class="metric">
<span class="metric-label">错误率</span>
<span class="metric-value ${stats.error_rate > 5 ? 'status-error' : 'status-ok'}">
${stats.error_rate || 0}%
</span>
</div>
`;
}
function updateAlertStatus(alerts) {
const container = document.getElementById('alert-status');
if (!alerts || alerts.length === 0) {
container.innerHTML = `
<div class="metric">
<span class="metric-label">系统状态</span>
<span class="metric-value status-ok">正常</span>
</div>
<div class="metric">
<span class="metric-label">活跃告警</span>
<span class="metric-value status-ok">0</span>
</div>
`;
} else {
const criticalAlerts = alerts.filter(a => a.severity === 'critical').length;
const warningAlerts = alerts.filter(a => a.severity === 'warning').length;
container.innerHTML = `
<div class="metric">
<span class="metric-label">系统状态</span>
<span class="metric-value status-error">异常</span>
</div>
<div class="metric">
<span class="metric-label">严重告警</span>
<span class="metric-value status-error">${criticalAlerts}</span>
</div>
<div class="metric">
<span class="metric-label">警告告警</span>
<span class="metric-value status-warning">${warningAlerts}</span>
</div>
`;
}
}
function updateGeoStats(geo) {
const container = document.getElementById('geo-stats');
container.innerHTML = `
<div class="metric">
<span class="metric-label">访问国家</span>
<span class="metric-value">${geo.countries || 0}</span>
</div>
<div class="metric">
<span class="metric-label">主要来源</span>
<span class="metric-value">${geo.top_country || 'N/A'}</span>
</div>
<div class="metric">
<span class="metric-label">国际流量</span>
<span class="metric-value">${geo.international_percent || 0}%</span>
</div>
`;
}
function updatePerformanceStats(perf) {
const container = document.getElementById('performance-stats');
container.innerHTML = `
<div class="metric">
<span class="metric-label">平均响应时间</span>
<span class="metric-value ${perf.avg_response_time > 2000 ? 'status-warning' : 'status-ok'}">
${perf.avg_response_time || 0}ms
</span>
</div>
<div class="metric">
<span class="metric-label">CPU使用率</span>
<span class="metric-value ${perf.cpu_usage > 80 ? 'status-error' : 'status-ok'}">
${perf.cpu_usage || 0}%
</span>
</div>
<div class="metric">
<span class="metric-label">内存使用</span>
<span class="metric-value ${perf.memory_usage > 80 ? 'status-warning' : 'status-ok'}">
${perf.memory_usage || 0}%
</span>
</div>
`;
}
function showError(message) {
const container = document.querySelector('.container');
const alert = document.createElement('div');
alert.className = 'alert error';
alert.textContent = message;
container.insertBefore(alert, container.firstChild);
setTimeout(() => {
alert.remove();
}, 5000);
}
// 初始化
updateLastUpdateTime();
fetchStats();
// 定期刷新
setInterval(() => {
fetchStats();
updateLastUpdateTime();
}, 30000);
</script>
</body>
</html>
5. 下一步
实时监控和告警系统配置完成后,您可以:
- 学习与其他监控系统的集成
- 了解安全分析和威胁检测
- 掌握性能优化和故障排除
- 学习自动化运维和管理
- 探索高级分析和报告功能
下一章我们将介绍GoAccess与其他工具的集成方法。