2026-01-12 00:53:31 +08:00
|
|
|
|
import logging
|
|
|
|
|
|
import os
|
2026-01-18 20:35:35 +08:00
|
|
|
|
from datetime import datetime, timedelta, timezone
|
2026-01-12 00:53:31 +08:00
|
|
|
|
from logging.handlers import RotatingFileHandler
|
2026-01-16 22:24:14 +08:00
|
|
|
|
from extensions import redis_client, db
|
2026-01-12 00:53:31 +08:00
|
|
|
|
import json
|
2026-01-16 22:24:14 +08:00
|
|
|
|
from flask import request, has_request_context, g
|
2026-01-12 00:53:31 +08:00
|
|
|
|
|
|
|
|
|
|
# 创建日志目录
|
|
|
|
|
|
LOG_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'logs')
|
|
|
|
|
|
os.makedirs(LOG_DIR, exist_ok=True)
|
|
|
|
|
|
|
2026-01-18 20:35:35 +08:00
|
|
|
|
# 北京时区 (UTC+8)
|
|
|
|
|
|
BEIJING_TZ = timezone(timedelta(hours=8))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BeijingTimeFormatter(logging.Formatter):
|
|
|
|
|
|
"""自定义 Formatter,确保日志时间始终使用北京时间(UTC+8)"""
|
|
|
|
|
|
|
|
|
|
|
|
def formatTime(self, record, datefmt=None):
|
|
|
|
|
|
# 将 record.created (UTC 时间戳) 转换为北京时间
|
|
|
|
|
|
ct = datetime.fromtimestamp(record.created, tz=BEIJING_TZ)
|
|
|
|
|
|
if datefmt:
|
|
|
|
|
|
s = ct.strftime(datefmt)
|
|
|
|
|
|
else:
|
|
|
|
|
|
s = ct.strftime('%Y-%m-%d %H:%M:%S')
|
|
|
|
|
|
return s
|
|
|
|
|
|
|
2026-01-12 00:53:31 +08:00
|
|
|
|
class SystemLogger:
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
self.logger = logging.getLogger('vision_ai')
|
|
|
|
|
|
self.logger.setLevel(logging.INFO)
|
|
|
|
|
|
|
|
|
|
|
|
# 文件处理器 (自动轮转,最大10MB,保留5个备份)
|
|
|
|
|
|
file_handler = RotatingFileHandler(
|
|
|
|
|
|
os.path.join(LOG_DIR, 'system.log'),
|
|
|
|
|
|
maxBytes=10*1024*1024,
|
|
|
|
|
|
backupCount=5,
|
|
|
|
|
|
encoding='utf-8'
|
|
|
|
|
|
)
|
|
|
|
|
|
file_handler.setLevel(logging.INFO)
|
|
|
|
|
|
|
|
|
|
|
|
# 控制台处理器
|
|
|
|
|
|
console_handler = logging.StreamHandler()
|
|
|
|
|
|
console_handler.setLevel(logging.INFO)
|
|
|
|
|
|
|
2026-01-18 20:35:35 +08:00
|
|
|
|
# 格式化 - 使用自定义的北京时间格式化器
|
|
|
|
|
|
formatter = BeijingTimeFormatter(
|
2026-01-12 00:53:31 +08:00
|
|
|
|
'[%(asctime)s] %(levelname)s - %(message)s',
|
|
|
|
|
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
|
|
|
|
)
|
|
|
|
|
|
file_handler.setFormatter(formatter)
|
|
|
|
|
|
console_handler.setFormatter(formatter)
|
|
|
|
|
|
|
|
|
|
|
|
self.logger.addHandler(file_handler)
|
|
|
|
|
|
self.logger.addHandler(console_handler)
|
|
|
|
|
|
|
|
|
|
|
|
def _push_to_redis(self, level, message, extra=None):
|
2026-01-12 23:29:29 +08:00
|
|
|
|
"""推送到 Redis 并保留 30 天数据"""
|
2026-01-12 00:53:31 +08:00
|
|
|
|
try:
|
2026-01-18 20:45:28 +08:00
|
|
|
|
now = datetime.now(timezone.utc)
|
|
|
|
|
|
bj_now = now.astimezone(BEIJING_TZ)
|
2026-01-16 22:24:14 +08:00
|
|
|
|
user_id = None
|
|
|
|
|
|
if has_request_context():
|
|
|
|
|
|
user_id = g.get('user_id') or (getattr(g, 'user', None).id if hasattr(g, 'user') and g.user else None)
|
|
|
|
|
|
|
2026-01-12 00:53:31 +08:00
|
|
|
|
log_entry = {
|
2026-01-17 23:15:58 +08:00
|
|
|
|
"time": bj_now.strftime('%Y-%m-%d %H:%M:%S'),
|
2026-01-12 00:53:31 +08:00
|
|
|
|
"level": level,
|
|
|
|
|
|
"message": message,
|
2026-01-16 22:24:14 +08:00
|
|
|
|
"user_id": user_id,
|
2026-01-12 00:53:31 +08:00
|
|
|
|
"extra": extra or {}
|
|
|
|
|
|
}
|
2026-01-12 23:29:29 +08:00
|
|
|
|
# 使用有序集合 (ZSET),分数为时间戳,方便按时间清理
|
|
|
|
|
|
timestamp = now.timestamp()
|
|
|
|
|
|
redis_client.zadd('system_logs_zset', {json.dumps(log_entry, ensure_ascii=False): timestamp})
|
|
|
|
|
|
|
|
|
|
|
|
# 清理 30 天之前的日志 (30 * 24 * 3600 秒)
|
|
|
|
|
|
thirty_days_ago = timestamp - (30 * 24 * 3600)
|
|
|
|
|
|
redis_client.zremrangebyscore('system_logs_zset', 0, thirty_days_ago)
|
2026-01-12 00:53:31 +08:00
|
|
|
|
except:
|
|
|
|
|
|
pass
|
2026-01-16 22:24:14 +08:00
|
|
|
|
|
|
|
|
|
|
def _write_to_db(self, level, message, module=None, extra=None):
|
|
|
|
|
|
"""写入数据库"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
from models import SystemLog
|
|
|
|
|
|
|
2026-01-18 21:09:43 +08:00
|
|
|
|
# 直接存储北京时间,避免时区转换问题
|
|
|
|
|
|
bj_now = datetime.now(BEIJING_TZ).replace(tzinfo=None)
|
|
|
|
|
|
|
2026-01-16 22:24:14 +08:00
|
|
|
|
log_data = {
|
|
|
|
|
|
'level': level,
|
|
|
|
|
|
'message': message,
|
|
|
|
|
|
'module': module,
|
|
|
|
|
|
'user_id': extra.get('user_id') if extra else None,
|
|
|
|
|
|
'extra': json.dumps(extra, ensure_ascii=False) if extra else None,
|
2026-01-18 21:09:43 +08:00
|
|
|
|
'created_at': bj_now # 直接存储北京时间
|
2026-01-16 22:24:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 捕获请求上下文信息
|
|
|
|
|
|
if has_request_context():
|
|
|
|
|
|
log_data.update({
|
|
|
|
|
|
'ip': request.remote_addr,
|
|
|
|
|
|
'path': request.path,
|
|
|
|
|
|
'method': request.method,
|
|
|
|
|
|
'user_agent': request.headers.get('User-Agent')
|
|
|
|
|
|
})
|
|
|
|
|
|
# 如果 log_data 还没 user_id,尝试从 context 获取
|
|
|
|
|
|
if not log_data['user_id']:
|
|
|
|
|
|
log_data['user_id'] = getattr(g, 'user_id', None) or (getattr(g.user, 'id', None) if hasattr(g, 'user') and g.user else None)
|
|
|
|
|
|
|
|
|
|
|
|
new_log = SystemLog(**log_data)
|
|
|
|
|
|
db.session.add(new_log)
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
# 避免日志错误导致程序崩溃
|
|
|
|
|
|
self.logger.error(f"Failed to write log to DB: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
|
def info(self, message, module=None, **kwargs):
|
2026-01-12 00:53:31 +08:00
|
|
|
|
self.logger.info(message)
|
|
|
|
|
|
self._push_to_redis('INFO', message, kwargs)
|
2026-01-16 22:24:14 +08:00
|
|
|
|
self._write_to_db('INFO', message, module, kwargs)
|
2026-01-12 00:53:31 +08:00
|
|
|
|
|
2026-01-16 22:24:14 +08:00
|
|
|
|
def warning(self, message, module=None, **kwargs):
|
2026-01-12 00:53:31 +08:00
|
|
|
|
self.logger.warning(message)
|
|
|
|
|
|
self._push_to_redis('WARNING', message, kwargs)
|
2026-01-16 22:24:14 +08:00
|
|
|
|
self._write_to_db('WARNING', message, module, kwargs)
|
2026-01-12 00:53:31 +08:00
|
|
|
|
|
2026-01-16 22:24:14 +08:00
|
|
|
|
def error(self, message, module=None, **kwargs):
|
2026-01-12 00:53:31 +08:00
|
|
|
|
self.logger.error(message)
|
|
|
|
|
|
self._push_to_redis('ERROR', message, kwargs)
|
2026-01-16 22:24:14 +08:00
|
|
|
|
self._write_to_db('ERROR', message, module, kwargs)
|
2026-01-12 00:53:31 +08:00
|
|
|
|
|
2026-01-16 22:24:14 +08:00
|
|
|
|
def debug(self, message, module=None, **kwargs):
|
2026-01-12 00:53:31 +08:00
|
|
|
|
self.logger.debug(message)
|
|
|
|
|
|
self._push_to_redis('DEBUG', message, kwargs)
|
2026-01-16 22:24:14 +08:00
|
|
|
|
self._write_to_db('DEBUG', message, module, kwargs)
|
2026-01-12 00:53:31 +08:00
|
|
|
|
|
|
|
|
|
|
# 全局日志实例
|
|
|
|
|
|
system_logger = SystemLogger()
|