日志记录是脚本开发中不可或缺的调试和监控手段，它能帮助您跟踪脚本执行过程、记录错误信息、排查问题根源。本页面将指导您为 SunvStation 脚本配置一个功能完善的日志记录器，让您的脚本具备专业级的日志管理能力。

## 日志系统架构概览

SunvStation 的日志功能通过 **LogMixin** 混入类实现，这是 SSProcessManager 继承体系中的基础模块之一。日志系统采用**依赖注入**设计模式，允许您将自定义配置的日志记录器注入到 SSProcess 中，从而灵活控制日志的输出方式、格式和级别。

```mermaid
classDiagram
    class Logger {
        <<Python标准库>>
        +debug(msg)
        +info(msg)
        +warning(msg)
        +error(msg)
        +critical(msg)
    }
    
    class LogAware {
        <<抽象基类>>
        +set_logger(Logger)*
        +log_error_msg(str)*
    }
    
    class LogMixin {
        +Logger logger
        +set_logger(Logger)
        +log_error_msg(str)
    }
    
    class SSProcessManager {
        +Logger logger
        +set_logger(Logger)
        +log_error_msg(str)
    }
    
    LogAware <|-- LogMixin
    LogMixin <|-- SSProcessManager
    SSProcessManager ..> Logger : 使用
```

这个架构设计的核心优势在于**关注点分离**：SSProcessManager 只负责调用日志接口，而日志的具体配置（输出到文件、控制台或远程服务器）完全由您控制，不会影响业务逻辑代码。

Sources: [log_mixin.py](ssprocess_mixins/log_mixin.py#L19-L42), [PySSProcess.py](PySSProcess.py#L34-L35)

## 配置日志记录器流程

配置日志记录器的过程分为四个关键步骤，下图清晰地展示了从创建到使用的完整流程：

```mermaid
flowchart TD
    A[开始配置日志] --> B[创建Logger实例]
    B --> C[配置Handler处理器]
    C --> D[设置Formatter格式化器]
    D --> E[配置日志级别]
    E --> F[注入到SSProcess]
    F --> G[开始使用日志功能]
    
    C --> C1[文件处理器<br>FileHandler]
    C --> C2[控制台处理器<br>StreamHandler]
    C --> C3[综合配置<br>多处理器]
    
    E --> E1[DEBUG]
    E --> E2[INFO]
    E --> E3[WARNING]
    E --> E4[ERROR]
    
    G --> G1[自动记录错误]
    G --> G2[手动记录日志]
    
    style F fill:#e1f5ff
    style G fill:#fff4e1
```

理解这个流程后，我们将逐步详细解析每个环节的具体实现方法。

Sources: [log_mixin.py](ssprocess_mixins/log_mixin.py#L26-L42)

## 步骤一：创建 Logger 实例

首先需要创建一个 Python 标准库的 Logger 实例。建议使用脚本文件名作为 Logger 的名称，这样可以在日志输出中清晰地识别日志来源。

```python
import logging

# 创建 Logger 实例，使用脚本文件名作为标识符
logger = logging.getLogger('my_script')
logger.setLevel(logging.DEBUG)  # 设置最低日志级别
```

### 日志级别说明

Python logging 模块提供了五个标准日志级别，按严重程度从低到高排列：

| 级别 | 数值 | 说明 | 使用场景 |
|-----|------|------|---------|
| DEBUG | 10 | 调试信息 | 详细的程序运行细节，仅在开发和调试时使用 |
| INFO | 20 | 一般信息 | 程序正常运行的关键步骤确认 |
| WARNING | 30 | 警告信息 | 程序可以继续运行但可能存在潜在问题 |
| ERROR | 40 | 错误信息 | 程序遇到错误但能够继续运行 |
| CRITICAL | 50 | 严重错误 | 严重错误导致程序可能无法继续运行 |

设置日志级别后，只有**等于或高于该级别**的日志消息才会被处理。例如，设置 `WARNING` 级别时，`DEBUG` 和 `INFO` 级别的消息将被忽略。

Sources: [Python logging 模块](https://docs.python.org/3/library/logging.html#logging-levels)

## 步骤二：配置 Handler 处理器

Handler 决定了日志消息的输出目的地。常用的 Handler 类型包括文件处理器和控制台处理器。您可以根据需求选择单一处理器或组合使用多个处理器。

### 文件处理器配置

将日志输出到文件是最常见的需求，特别适合长期运行的脚本和自动化任务。

```python
# 创建文件处理器，指定日志文件路径
file_handler = logging.FileHandler(
    'script_log.log',       # 日志文件路径
    mode='a',               # 追加模式（'a'追加，'w'覆盖）
    encoding='utf-8'        # 文件编码
)
file_handler.setLevel(logging.INFO)  # 文件处理器只记录 INFO 级别及以上
```

### 控制台处理器配置

控制台处理器将日志输出到标准输出流，适合开发调试阶段使用。

```python
# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)  # 控制台只显示 WARNING 级别及以上
```

Sources: [log_mixin.py](ssprocess_mixins/log_mixin.py#L26-L42)

## 步骤三：设置 Formatter 格式化器

Formatter 定义了日志消息的输出格式，包括时间戳、日志级别、消息内容等信息。合理的格式能让日志更易于阅读和分析。

### 基本格式示例

```python
# 创建格式化器
formatter = logging.Formatter(
    fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

# 将格式化器应用到处理器
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
```

### 格式化参数说明

| 格式参数 | 说明 | 示例输出 |
|---------|------|---------|
| `%(asctime)s` | 日志记录时间 | 2024-01-15 14:30:25 |
| `%(name)s` | Logger 名称 | my_script |
| `%(levelname)s` | 日志级别名称 | ERROR |
| `%(message)s` | 日志消息内容 | 无法获取当前数据源 |
| `%(filename)s` | 文件名 | geo_edit_mixin.py |
| `%(lineno)d` | 行号 | 175 |
| `%(funcName)s` | 函数名 | createNewObjByCode |

### 完整格式示例（包含位置信息）

```python
# 包含文件名、行号和函数名的详细格式
detailed_formatter = logging.Formatter(
    fmt='%(asctime)s [%(filename)s:%(lineno)d - %(funcName)s] %(levelname)s: %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
file_handler.setFormatter(detailed_formatter)
```

这种详细格式在调试问题时特别有用，可以快速定位到产生日志的具体代码位置。

Sources: [log_mixin.py](ssprocess_mixins/log_mixin.py#L36-L42)

## 步骤四：组装并注入到 SSProcess

完成上述配置后，需要将处理器添加到 Logger，然后将 Logger 注入到 SSProcess 中。

```python
from sunvpy import SSProcess

# 将处理器添加到 Logger
logger.addHandler(file_handler)
logger.addHandler(console_handler)

# 将配置好的 Logger 注入到 SSProcess
SSProcess.set_logger(logger)

# 验证配置成功
print("日志记录器配置完成！")
```

至此，SSProcess 已经具备了完整的日志记录能力，所有内部调用 `log_error_msg()` 的错误消息都会按照您的配置输出到指定的目的地。

Sources: [PySSProcess.py](PySSProcess.py#L79), [geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L175)

## 常用日志配置方案

不同的应用场景需要不同的日志配置策略。以下是几种常用的配置方案，您可以根据实际需求选择或修改。

### 方案一：开发调试配置

适合开发阶段，输出详细调试信息到控制台，同时记录警告和错误到文件。

| 配置项 | 控制台处理器 | 文件处理器 |
|-------|------------|-----------|
| 日志级别 | DEBUG | WARNING |
| 格式 | 简洁格式 | 详细格式 |
| 用途 | 实时查看 | 长期保存 |

```python
# 开发调试配置
import logging
from sunvpy import SSProcess

logger = logging.getLogger('debug_script')
logger.setLevel(logging.DEBUG)

# 控制台处理器 - 显示所有调试信息
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(logging.Formatter(
    '%(levelname)s: %(message)s'
))

# 文件处理器 - 只记录警告和错误
file_handler = logging.FileHandler('debug.log', encoding='utf-8')
file_handler.setLevel(logging.WARNING)
file_handler.setFormatter(logging.Formatter(
    '%(asctime)s - %(levelname)s - %(message)s'
))

logger.addHandler(console_handler)
logger.addHandler(file_handler)

SSProcess.set_logger(logger)
```

### 方案二：生产环境配置

适合正式运行的脚本，只记录重要信息到文件，避免输出过多调试信息影响性能。

| 配置项 | 文件处理器 |
|-------|-----------|
| 日志级别 | INFO |
| 格式 | 包含时间戳和位置信息 |
| 文件轮转 | 按大小或日期自动分割 |

```python
# 生产环境配置（带日志文件轮转）
import logging
from logging.handlers import RotatingFileHandler
from sunvpy import SSProcess

logger = logging.getLogger('production_script')
logger.setLevel(logging.INFO)

# 创建支持文件轮转的处理器
# 每个日志文件最大 5MB，最多保留 3 个备份文件
file_handler = RotatingFileHandler(
    'production.log',
    maxBytes=5*1024*1024,  # 5MB
    backupCount=3,
    encoding='utf-8'
)
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(logging.Formatter(
    '%(asctime)s [%(levelname)s] %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
))

logger.addHandler(file_handler)
SSProcess.set_logger(logger)
```

### 方案三：简洁配置

如果您只需要最基础的日志功能，可以使用以下最简配置：

```python
# 最简配置
import logging
from sunvpy import SSProcess

# 配置根 logger（一次性配置所有 logger）
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    filename='simple.log',
    filemode='a'
)

# 获取并注入 logger
SSProcess.set_logger(logging.getLogger())
```

Sources: [log_mixin.py](ssprocess_mixins/log_mixin.py#L26-L42)

## 日志记录方法说明

配置完成后，您可以通过两种方式记录日志：

### 自动记录（系统内部调用）

SSProcess 的许多内部操作会在遇到错误时自动调用 `log_error_msg()` 记录日志，例如在无法获取数据源时会自动记录错误。

```python
# SSProcess 内部代码示例（来自 geo_edit_mixin.py）
if ds_eps is None:
    self.log_error_msg("无法获取当前数据源！")
    return self.curNewObj
```

这类日志无需您手动触发，会在问题发生时自动记录。

Sources: [geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L174-L176)

### 手动记录（自定义日志）

您也可以在脚本中手动记录自定义日志消息，帮助跟踪脚本执行过程。

```python
from sunvpy import SSProcess
import logging

# 配置日志（使用之前介绍的任何方案）
logger = logging.getLogger('manual_logging')
logger.setLevel(logging.INFO)
handler = logging.FileHandler('manual.log', encoding='utf-8')
handler.setFormatter(logging.Formatter(
    '%(asctime)s - %(levelname)s - %(message)s'
))
logger.addHandler(handler)
SSProcess.set_logger(logger)

# 记录脚本开始执行
logger.info("脚本开始执行")

# 执行选择集查询
SSProcess.clearSelection()
SSProcess.setSelectCondition("SSObj_Code", "==", "3103013")
SSProcess.selectFilter()

count = SSProcess.getSelGeoCount()
logger.info(f"查询完成，共找到 {count} 个对象")

# 处理结果
if count == 0:
    logger.warning("未找到任何符合条件的对象")
else:
    logger.info("开始处理对象...")
    # 处理逻辑...
    logger.info("对象处理完成")

# 记录脚本执行结束
logger.info("脚本执行结束")
```

### 日志记录最佳实践

| 实践 | 说明 |
|-----|------|
| 使用适当的日志级别 | DEBUG 用于调试，INFO 用于关键步骤，WARNING 用于潜在问题，ERROR 用于错误 |
| 包含上下文信息 | 日志消息应包含足够的信息以便理解问题（如对象 ID、条件值等） |
| 避免过度日志 | 生产环境中避免记录大量 DEBUG 级别日志，影响性能 |
| 使用结构化日志 | 对于复杂应用，考虑使用 JSON 格式的结构化日志 |
| 定期清理日志 | 配置日志轮转或定期清理旧日志文件，避免占用过多磁盘空间 |

Sources: [log_mixin.py](ssprocess_mixins/log_mixin.py#L36-L42)

## 完整示例：带日志的属性修改脚本

以下是一个完整的示例脚本，展示如何在实际项目中集成日志功能。该脚本修改选择集中对象的属性，并在每个关键步骤记录日志。

```python
import logging
from logging.handlers import RotatingFileHandler
from sunvpy import SSProcess

# ========== 日志配置开始 ==========
logger = logging.getLogger('attribute_update_script')
logger.setLevel(logging.INFO)

# 配置文件处理器（自动轮转）
file_handler = RotatingFileHandler(
    'attribute_update.log',
    maxBytes=10*1024*1024,  # 10MB
    backupCount=5,
    encoding='utf-8'
)
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(logging.Formatter(
    '%(asctime)s [%(levelname)s] %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
))

# 配置控制台处理器（只显示警告和错误）
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING)

logger.addHandler(file_handler)
logger.addHandler(console_handler)
SSProcess.set_logger(logger)
# ========== 日志配置结束 ==========

# ========== 主脚本开始 ==========
logger.info("=" * 60)
logger.info("属性修改脚本开始执行")
logger.info("=" * 60)

try:
    # 步骤 1：清除旧选择集和条件
    logger.info("步骤 1：清除旧选择集和条件")
    SSProcess.clearSelection()
    SSProcess.clearSelectCondition()
    
    # 步骤 2：设置选择条件
    logger.info("步骤 2：设置选择条件")
    filter_code = "3103013"
    SSProcess.setSelectCondition("SSObj_Code", "==", filter_code)
    logger.info(f"筛选条件：SSObj_Code == {filter_code}")
    
    # 步骤 3：执行查询
    logger.info("步骤 3：执行查询")
    SSProcess.selectFilter()
    
    count = SSProcess.getSelGeoCount()
    logger.info(f"查询结果：共找到 {count} 个对象")
    
    if count == 0:
        logger.warning("选择集为空，脚本退出")
        exit()
    
    # 步骤 4：批量修改属性
    logger.info("步骤 4：开始批量修改属性")
    
    success_count = 0
    fail_count = 0
    
    for i in range(count):
        try:
            # 修改属性
            SSProcess.setSelGeoValue(i, "SSObj_Color", "16711680")  # 红色
            SSProcess.setSelGeoValue(i, "SSObj_Name", f"修改后对象_{i}")
            
            success_count += 1
            
            # 每 100 个对象记录一次进度
            if (i + 1) % 100 == 0:
                logger.info(f"已处理 {i + 1}/{count} 个对象")
                
        except Exception as e:
            fail_count += 1
            logger.error(f"修改第 {i} 个对象时出错：{str(e)}")
    
    # 步骤 5：保存修改
    logger.info("步骤 5：保存修改到数据库")
    SSProcess.saveBufferObjToDatabase()
    
    # 输出统计信息
    logger.info("=" * 60)
    logger.info("执行统计：")
    logger.info(f"  总处理对象数：{count}")
    logger.info(f"  成功修改数：{success_count}")
    logger.info(f"  失败对象数：{fail_count}")
    logger.info("=" * 60)
    
except Exception as e:
    logger.critical(f"脚本执行过程中发生严重错误：{str(e)}", exc_info=True)
    raise

logger.info("脚本执行结束")
logger.info("=" * 60)
```

运行此脚本后，日志文件 `attribute_update.log` 将包含完整的执行轨迹，便于后续分析和排查问题。

Sources: [log_mixin.py](ssprocess_mixins/log_mixin.py#L26-L42), [geo_edit_mixin.py](ssprocess_mixins/geo_edit_mixin.py#L67-L200)

## 故障排查

### 日志文件未生成

**问题**：脚本运行后没有生成预期的日志文件。

**可能原因及解决方法**：

| 原因 | 解决方法 |
|-----|---------|
| 日志级别设置过高 | 检查 Logger 和 Handler 的级别设置，确保有符合条件的日志消息 |
| 未将 Handler 添加到 Logger | 确认调用了 `logger.addHandler(handler)` |
| 文件路径无写入权限 | 检查文件路径是否存在且具有写入权限 |
| 日志消息级别低于配置 | 确保记录的日志消息级别不低于 Handler 的级别 |

### 日志输出乱码

**问题**：日志文件中出现中文乱码。

**解决方法**：在创建 FileHandler 时显式指定 `encoding='utf-8'` 参数：

```python
file_handler = logging.FileHandler('log.txt', encoding='utf-8')
```

### 未记录 SSProcess 内部错误

**问题**：SSProcess 操作出错时没有记录到日志文件。

**原因**：SSProcess 内部的 `log_error_msg()` 方法只在配置了 logger 时才会记录日志。

**解决方法**：确保在调用任何 SSProcess 方法之前调用 `SSProcess.set_logger(logger)`。

```python
# 正确的顺序
logger = configure_logger()  # 先配置 logger
SSProcess.set_logger(logger)  # 然后注入
SSProcess.selectFilter()      # 最后才执行操作
```

Sources: [log_mixin.py](ssprocess_mixins/log_mixin.py#L36-L42)

## 下一步学习

完成日志配置的学习后，建议您继续探索以下相关主题：

- [进度条使用指南](38-jin-du-tiao-shi-yong-zhi-nan) - 学习如何在长时间运行的脚本中提供进度反馈
- [错误处理与异常管理](39-cuo-wu-chu-li-yu-yi-chang-guan-li) - 掌握更高级的错误处理技巧
- [对象缓存机制](23-dui-xiang-huan-cun-ji-zhi) - 了解如何高效处理大量对象

结合日志记录、进度显示和错误处理，您将能够开发出健壮、易调试、用户友好的专业级 SunvStation 脚本。