feat(project): move edge_inference_service contents to root and update paths
- Moved all project files and directories (config, core, models, etc.) from edge_inference_service/ to the repository root ai_edge/ - Updated model path in config/settings.py to reflect new structure - Revised usage paths in __init__.py documentation
This commit is contained in:
373
utils/logger.py
Normal file
373
utils/logger.py
Normal file
@@ -0,0 +1,373 @@
|
||||
"""
|
||||
分级日志系统
|
||||
提供多级别日志支持,包含文件输出、控制台输出、性能指标记录
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import logging.handlers
|
||||
import time
|
||||
import threading
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional, Union
|
||||
from pathlib import Path
|
||||
|
||||
from config.settings import get_settings
|
||||
|
||||
|
||||
class PerformanceLogger:
|
||||
"""性能指标记录器"""
|
||||
|
||||
def __init__(self):
|
||||
self._metrics: Dict[str, list] = {}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def record(self, metric_name: str, value: float, tags: Optional[Dict[str, str]] = None):
|
||||
"""记录性能指标"""
|
||||
with self._lock:
|
||||
key = metric_name
|
||||
if metric_name not in self._metrics:
|
||||
self._metrics[metric_name] = []
|
||||
|
||||
self._metrics[metric_name].append({
|
||||
"value": value,
|
||||
"timestamp": time.time(),
|
||||
"datetime": datetime.now().isoformat(),
|
||||
"tags": tags or {}
|
||||
})
|
||||
|
||||
def get_metrics(self, metric_name: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""获取性能指标"""
|
||||
with self._lock:
|
||||
if metric_name:
|
||||
return self._metrics.get(metric_name, [])
|
||||
return dict(self._metrics)
|
||||
|
||||
def get_last_value(self, metric_name: str) -> Optional[float]:
|
||||
"""获取最新指标值"""
|
||||
with self._lock:
|
||||
metrics = self._metrics.get(metric_name, [])
|
||||
if metrics:
|
||||
return metrics[-1].get("value")
|
||||
return None
|
||||
|
||||
def get_statistics(self, metric_name: str) -> Dict[str, float]:
|
||||
"""获取指标统计信息"""
|
||||
with self._lock:
|
||||
values = [m["value"] for m in self._metrics.get(metric_name, [])]
|
||||
if not values:
|
||||
return {}
|
||||
|
||||
return {
|
||||
"count": len(values),
|
||||
"min": min(values),
|
||||
"max": max(values),
|
||||
"avg": sum(values) / len(values),
|
||||
"sum": sum(values),
|
||||
}
|
||||
|
||||
def clear(self):
|
||||
"""清空所有指标"""
|
||||
with self._lock:
|
||||
self._metrics.clear()
|
||||
|
||||
|
||||
class StructuredLogger:
|
||||
"""结构化日志记录器"""
|
||||
|
||||
def __init__(self, name: str = "edge_inference"):
|
||||
self.name = name
|
||||
self._logger = None
|
||||
self._performance_logger = PerformanceLogger()
|
||||
self._log_dir = "./logs"
|
||||
self._init_logger()
|
||||
|
||||
def _init_logger(self):
|
||||
"""初始化日志配置"""
|
||||
settings = get_settings()
|
||||
|
||||
self._log_level = getattr(logging, settings.log_level.upper(), logging.INFO)
|
||||
self._log_dir = settings.log_dir
|
||||
self._max_size = settings.log_file_max_size
|
||||
self._backup_count = settings.log_file_backup_count
|
||||
|
||||
os.makedirs(self._log_dir, exist_ok=True)
|
||||
|
||||
self._logger = logging.getLogger(self.name)
|
||||
self._logger.setLevel(self._log_level)
|
||||
|
||||
self._logger.handlers.clear()
|
||||
|
||||
formatter = logging.Formatter(
|
||||
fmt='%(asctime)s | %(levelname)-8s | %(name)s | %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
console_handler.setLevel(self._log_level)
|
||||
console_handler.setFormatter(formatter)
|
||||
self._logger.addHandler(console_handler)
|
||||
|
||||
self._add_file_handler(formatter)
|
||||
|
||||
def _add_file_handler(self, formatter: logging.Formatter):
|
||||
"""添加文件处理器"""
|
||||
log_file = os.path.join(self._log_dir, f"{self.name}.log")
|
||||
|
||||
try:
|
||||
file_handler = logging.handlers.RotatingFileHandler(
|
||||
log_file,
|
||||
maxBytes=self._max_size,
|
||||
backupCount=self._backup_count,
|
||||
encoding='utf-8'
|
||||
)
|
||||
file_handler.setLevel(self._log_level)
|
||||
file_handler.setFormatter(formatter)
|
||||
self._logger.addHandler(file_handler)
|
||||
|
||||
error_file = os.path.join(self._log_dir, f"{self.name}_error.log")
|
||||
error_handler = logging.handlers.RotatingFileHandler(
|
||||
error_file,
|
||||
maxBytes=self._max_size,
|
||||
backupCount=self._backup_count,
|
||||
encoding='utf-8'
|
||||
)
|
||||
error_handler.setLevel(logging.ERROR)
|
||||
error_handler.setFormatter(formatter)
|
||||
self._logger.addHandler(error_handler)
|
||||
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"创建日志文件处理器失败: {e}\n")
|
||||
|
||||
def _log(
|
||||
self,
|
||||
level: int,
|
||||
message: str,
|
||||
extra: Optional[Dict[str, Any]] = None,
|
||||
exc_info: bool = False
|
||||
):
|
||||
"""结构化日志记录"""
|
||||
log_data = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"logger": self.name,
|
||||
}
|
||||
|
||||
if extra:
|
||||
log_data.update(extra)
|
||||
|
||||
extra_fields = {"structured_data": json.dumps(log_data, ensure_ascii=False)}
|
||||
|
||||
self._logger.log(level, message, extra=extra_fields, exc_info=exc_info)
|
||||
|
||||
def debug(self, message: str, **kwargs):
|
||||
"""DEBUG级别日志"""
|
||||
self._log(logging.DEBUG, message, kwargs)
|
||||
|
||||
def info(self, message: str, **kwargs):
|
||||
"""INFO级别日志"""
|
||||
self._log(logging.INFO, message, kwargs)
|
||||
|
||||
def warning(self, message: str, **kwargs):
|
||||
"""WARNING级别日志"""
|
||||
self._log(logging.WARNING, message, kwargs)
|
||||
|
||||
def error(self, message: str, exc_info: bool = True, **kwargs):
|
||||
"""ERROR级别日志"""
|
||||
self._log(logging.ERROR, message, kwargs, exc_info=exc_info)
|
||||
|
||||
def critical(self, message: str, exc_info: bool = True, **kwargs):
|
||||
"""CRITICAL级别日志"""
|
||||
self._log(logging.CRITICAL, message, kwargs, exc_info=exc_info)
|
||||
|
||||
def performance(self, metric_name: str, value: float,
|
||||
duration_ms: Optional[float] = None, **tags):
|
||||
"""记录性能指标"""
|
||||
self._performance_logger.record(metric_name, value, tags)
|
||||
|
||||
perf_data = {
|
||||
"metric": metric_name,
|
||||
"value": value,
|
||||
"duration_ms": duration_ms,
|
||||
"tags": tags
|
||||
}
|
||||
|
||||
self.info(f"性能指标: {metric_name} = {value}", **perf_data)
|
||||
|
||||
def log_inference_latency(self, latency_ms: float, batch_size: int = 1):
|
||||
"""记录推理延迟"""
|
||||
self.performance(
|
||||
"inference_latency_ms",
|
||||
latency_ms,
|
||||
batch_size=batch_size,
|
||||
throughput_fps=1000.0 / latency_ms if latency_ms > 0 else 0
|
||||
)
|
||||
|
||||
def log_frame_rate(self, fps: float, camera_id: str):
|
||||
"""记录帧率"""
|
||||
self.performance(
|
||||
"frame_rate_fps",
|
||||
fps,
|
||||
camera_id=camera_id
|
||||
)
|
||||
|
||||
def log_resource_usage(
|
||||
self,
|
||||
cpu_percent: float,
|
||||
memory_mb: float,
|
||||
gpu_memory_mb: Optional[float] = None
|
||||
):
|
||||
"""记录资源使用情况"""
|
||||
self.performance(
|
||||
"cpu_percent",
|
||||
cpu_percent,
|
||||
memory_mb=memory_mb,
|
||||
gpu_memory_mb=gpu_memory_mb
|
||||
)
|
||||
|
||||
def log_alert(self, alert_type: str, camera_id: str, roi_id: str,
|
||||
confidence: Optional[float] = None):
|
||||
"""记录告警事件"""
|
||||
self.info(
|
||||
f"告警触发: {alert_type}",
|
||||
alert_type=alert_type,
|
||||
camera_id=camera_id,
|
||||
roi_id=roi_id,
|
||||
confidence=confidence
|
||||
)
|
||||
|
||||
def log_connection_event(self, event_type: str,
|
||||
connection_type: str,
|
||||
target: str,
|
||||
success: bool,
|
||||
error_msg: Optional[str] = None):
|
||||
"""记录连接事件"""
|
||||
self.info(
|
||||
f"连接事件: {event_type} - {connection_type} -> {target}",
|
||||
event_type=event_type,
|
||||
connection_type=connection_type,
|
||||
target=target,
|
||||
success=success,
|
||||
error_msg=error_msg
|
||||
)
|
||||
|
||||
def get_performance_metrics(self) -> Dict[str, Any]:
|
||||
"""获取性能指标"""
|
||||
return {
|
||||
"metrics": self._performance_logger.get_metrics(),
|
||||
"statistics": {
|
||||
name: self._performance_logger.get_statistics(name)
|
||||
for name in self._performance_logger.get_metrics()
|
||||
}
|
||||
}
|
||||
|
||||
def get_statistics(self, metric_name: str) -> Dict[str, float]:
|
||||
"""获取指定指标统计"""
|
||||
return self._performance_logger.get_statistics(metric_name)
|
||||
|
||||
def export_metrics(self, output_path: str):
|
||||
"""导出性能指标到文件"""
|
||||
metrics = self.get_performance_metrics()
|
||||
try:
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(metrics, f, ensure_ascii=False, indent=2)
|
||||
self.info(f"性能指标已导出: {output_path}")
|
||||
except Exception as e:
|
||||
self.error(f"导出性能指标失败: {e}")
|
||||
|
||||
def flush(self):
|
||||
"""刷新日志处理器"""
|
||||
for handler in self._logger.handlers:
|
||||
if hasattr(handler, 'flush'):
|
||||
handler.flush()
|
||||
|
||||
def close(self):
|
||||
"""关闭日志系统"""
|
||||
self.flush()
|
||||
for handler in self._logger.handlers:
|
||||
handler.close()
|
||||
self._logger.handlers.clear()
|
||||
|
||||
|
||||
class ContextLogger:
|
||||
"""上下文日志记录器,自动附加上下文信息"""
|
||||
|
||||
def __init__(self, base_logger: StructuredLogger, context: Dict[str, Any]):
|
||||
self._base_logger = base_logger
|
||||
self._context = context
|
||||
|
||||
def _add_context(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""添加上下文信息"""
|
||||
result = dict(self._context)
|
||||
result.update(kwargs)
|
||||
return result
|
||||
|
||||
def debug(self, message: str, **kwargs):
|
||||
"""DEBUG级别日志"""
|
||||
self._base_logger.debug(message, **self._add_context(kwargs))
|
||||
|
||||
def info(self, message: str, **kwargs):
|
||||
"""INFO级别日志"""
|
||||
self._base_logger.info(message, **self._add_context(kwargs))
|
||||
|
||||
def warning(self, message: str, **kwargs):
|
||||
"""WARNING级别日志"""
|
||||
self._base_logger.warning(message, **self._add_context(kwargs))
|
||||
|
||||
def error(self, message: str, **kwargs):
|
||||
"""ERROR级别日志"""
|
||||
self._base_logger.error(message, **self._add_context(kwargs))
|
||||
|
||||
def critical(self, message: str, **kwargs):
|
||||
"""CRITICAL级别日志"""
|
||||
self._base_logger.critical(message, **self._add_context(kwargs))
|
||||
|
||||
def with_context(self, **additional_context) -> 'ContextLogger':
|
||||
"""添加额外上下文"""
|
||||
new_context = dict(self._context)
|
||||
new_context.update(additional_context)
|
||||
return ContextLogger(self._base_logger, new_context)
|
||||
|
||||
|
||||
_structured_logger_instance = None
|
||||
|
||||
|
||||
def get_logger(name: str = "edge_inference") -> StructuredLogger:
|
||||
"""获取结构化日志记录器单例"""
|
||||
global _structured_logger_instance
|
||||
if _structured_logger_instance is None:
|
||||
_structured_logger_instance = StructuredLogger(name)
|
||||
return _structured_logger_instance
|
||||
|
||||
|
||||
def create_logger_with_context(base_logger: StructuredLogger,
|
||||
context: Dict[str, Any]) -> ContextLogger:
|
||||
"""创建带上下文的日志记录器"""
|
||||
return ContextLogger(base_logger, context)
|
||||
|
||||
|
||||
def log_performance_operation(operation_name: str, logger_instance: StructuredLogger):
|
||||
"""性能日志装饰器"""
|
||||
def decorator(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
start_time = time.perf_counter()
|
||||
try:
|
||||
result = func(*args, **kwargs)
|
||||
duration_ms = (time.perf_counter() - start_time) * 1000
|
||||
logger_instance.log_inference_latency(
|
||||
duration_ms,
|
||||
batch_size=1
|
||||
)
|
||||
logger_instance.debug(
|
||||
f"操作完成: {operation_name}, 耗时: {duration_ms:.2f}ms"
|
||||
)
|
||||
return result
|
||||
except Exception as e:
|
||||
duration_ms = (time.perf_counter() - start_time) * 1000
|
||||
logger_instance.error(
|
||||
f"操作失败: {operation_name}, 耗时: {duration_ms:.2f}ms, 错误: {e}"
|
||||
)
|
||||
raise
|
||||
return wrapper
|
||||
return decorator
|
||||
Reference in New Issue
Block a user