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. 下一步

实时监控和告警系统配置完成后,您可以:

  1. 学习与其他监控系统的集成
  2. 了解安全分析和威胁检测
  3. 掌握性能优化和故障排除
  4. 学习自动化运维和管理
  5. 探索高级分析和报告功能

下一章我们将介绍GoAccess与其他工具的集成方法。