# Python 日志处理 logging

## 学习目标
- 掌握 logging 模块的基本用法
- 理解日志级别和日志记录器的层次结构
- 学会配置日志处理器和格式化器
- 掌握日志配置文件的使用

---

## 1. 基础用法

### 1.1 简单日志输出

```python
import logging

# 基本配置（一次性）
logging.basicConfig(level=logging.INFO)

# 不同级别的日志
logging.debug("调试信息")     # 最详细
logging.info("普通信息")      # 一般信息
logging.warning("警告信息")   # 警告
logging.error("错误信息")     # 错误
logging.critical("严重错误")  # 最严重

# 输出：
# WARNING:root:警告信息
# ERROR:root:错误信息
# CRITICAL:root:严重错误
# （默认级别是 WARNING，低于此级别的不会显示）
```

### 1.2 基本配置

```python
import logging

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)

logging.info("程序启动")
logging.debug("调试: x = %d", 42)

# 输出：
# 2024-01-15 14:30:00 - INFO - 程序启动
# 2024-01-15 14:30:00 - DEBUG - 调试: x = 42
```

---

## 2. 日志级别

| 级别 | 数值 | 用途 |
|------|------|------|
| DEBUG | 10 | 详细的调试信息 |
| INFO | 20 | 确认程序按预期运行 |
| WARNING | 30 | 警告，但程序仍继续运行 |
| ERROR | 40 | 错误，某项功能无法正常执行 |
| CRITICAL | 50 | 严重错误，程序可能无法继续 |

```python
import logging

# 设置级别
logger = logging.getLogger('myapp')
logger.setLevel(logging.DEBUG)

# 检查级别
print(logger.isEnabledFor(logging.DEBUG))   # True
print(logger.isEnabledFor(logging.INFO))    # True
print(logger.isEnabledFor(logging.WARNING)) # True
```

---

## 3. 日志记录器层次结构

```python
import logging

# 创建命名记录器
# 层次: root -> app -> app.module -> app.module.sub
logger = logging.getLogger('app.module')

# 子记录器继承父记录器的配置
parent = logging.getLogger('app')
child = logging.getLogger('app.module')
grandchild = logging.getLogger('app.module.sub')

# 设置父记录器级别
parent.setLevel(logging.WARNING)

# 子记录器继承级别
print(child.level)       # 0（继承父级别）
print(child.getEffectiveLevel())  # 30（WARNING）

# 子记录器可独立设置
child.setLevel(logging.DEBUG)
print(child.getEffectiveLevel())  # 10（DEBUG）
```

---

## 4. 处理器和格式化器

### 4.1 输出到控制台

```python
import logging

# 创建记录器
logger = logging.getLogger('mylogger')
logger.setLevel(logging.DEBUG)

# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# 创建格式化器
formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
console_handler.setFormatter(formatter)

# 添加处理器
logger.addHandler(console_handler)

# 使用
logger.info("普通信息")
logger.debug("调试信息（不会显示，因为处理器级别是 INFO）")
```

### 4.2 输出到文件

```python
import logging

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

# 文件处理器
file_handler = logging.FileHandler('app.log', encoding='utf-8')
file_handler.setLevel(logging.DEBUG)

# 格式化器
formatter = logging.Formatter(
    '%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d - %(message)s'
)
file_handler.setFormatter(formatter)

logger.addHandler(file_handler)

logger.info("写入日志文件")
logger.error("发生错误")
```

### 4.3 多处理器组合

```python
import logging

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

# 控制台处理器 - 只显示 WARNING 及以上
console = logging.StreamHandler()
console.setLevel(logging.WARNING)
console.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))

# 文件处理器 - 记录所有级别
file_handler = logging.FileHandler('debug.log', encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter(
    '%(asctime)s [%(levelname)s] %(message)s'
))

# 添加两个处理器
logger.addHandler(console)
logger.addHandler(file_handler)

# 使用
logger.debug("调试信息（只在文件）")
logger.info("普通信息（只在文件）")
logger.warning("警告（文件+控制台）")
logger.error("错误（文件+控制台）")
```

---

## 5. 日志轮转

### 5.1 按大小轮转

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

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

# 文件最大 1MB，保留 3 个备份
handler = RotatingFileHandler(
    'app.log',
    maxBytes=1024 * 1024,  # 1MB
    backupCount=3,
    encoding='utf-8'
)
handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
logger.addHandler(handler)

# 大量日志测试
for i in range(10000):
    logger.info(f"日志行 {i}: " + "x" * 100)
```

### 5.2 按时间轮转

```python
import logging
from logging.handlers import TimedRotatingFileHandler

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

# 每天轮转，保留 7 天
handler = TimedRotatingFileHandler(
    'app.log',
    when='midnight',  # 每天午夜轮转
    interval=1,
    backupCount=7,
    encoding='utf-8'
)
handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
logger.addHandler(handler)
```

---

## 6. 配置方式

### 6.1 代码配置

```python
import logging

def setup_logging():
    """通过代码配置日志"""
    
    # 根记录器
    root = logging.getLogger()
    root.setLevel(logging.DEBUG)
    
    # 控制台处理器
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    console.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
    root.addHandler(console)
    
    # 文件处理器
    file_handler = logging.FileHandler('app.log', encoding='utf-8')
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(logging.Formatter(
        '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
    ))
    root.addHandler(file_handler)

setup_logging()
logging.info("日志已配置")
```

### 6.2 字典配置（推荐）

```python
import logging
import logging.config

LOGGING_CONFIG = {
    'version': 1,
    'disable_existing_loggers': False,
    
    'formatters': {
        'standard': {
            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
        },
        'simple': {
            'format': '%(levelname)s: %(message)s'
        },
    },
    
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'level': 'INFO',
            'formatter': 'simple',
        },
        'file': {
            'class': 'logging.FileHandler',
            'filename': 'app.log',
            'level': 'DEBUG',
            'formatter': 'standard',
            'encoding': 'utf-8',
        },
    },
    
    'loggers': {
        'myapp': {
            'level': 'DEBUG',
            'handlers': ['console', 'file'],
            'propagate': False,
        },
    },
    
    'root': {
        'level': 'WARNING',
        'handlers': ['console'],
    },
}

logging.config.dictConfig(LOGGING_CONFIG)

logger = logging.getLogger('myapp')
logger.info("使用字典配置")
```

### 6.3 配置文件

```ini
# logging.conf
[loggers]
keys=root,myapp

[handlers]
keys=console,file

[formatters]
keys=standard,simple

[logger_root]
level=WARNING
handlers=console

[logger_myapp]
level=DEBUG
handlers=console,file
qualname=myapp
propagate=0

[handler_console]
class=StreamHandler
level=INFO
formatter=simple
args=(sys.stdout,)

[handler_file]
class=FileHandler
level=DEBUG
formatter=standard
args=('app.log', 'a', 'utf-8')

[formatter_standard]
format=%(asctime)s [%(levelname)s] %(name)s: %(message)s

[formatter_simple]
format=%(levelname)s: %(message)s
```

```python
# 使用配置文件
import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('myapp')
logger.info("使用配置文件")
```

---

## 7. 实用技巧

### 7.1 异常信息记录

```python
import logging

logging.basicConfig(level=logging.ERROR)

try:
    1 / 0
except Exception:
    # 自动记录异常 traceback
    logging.exception("发生错误")

# 输出包含完整的堆栈信息
```

### 7.2 自定义过滤器

```python
import logging

class LevelFilter(logging.Filter):
    """只允许指定级别的日志"""
    
    def __init__(self, level):
        self.level = level
    
    def filter(self, record):
        return record.levelno == self.level

# 使用
logger = logging.getLogger('filtered')
handler = logging.StreamHandler()
handler.addFilter(LevelFilter(logging.WARNING))
logger.addHandler(handler)
```

### 7.3 上下文信息

```python
import logging

logging.basicConfig(
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logger = logging.getLogger('context')

# 使用 extra 添加上下文
logger.info('用户操作', extra={'user_id': '12345'})
```

---

## 本节小结

- **级别**：DEBUG < INFO < WARNING < ERROR < CRITICAL
- **记录器**：通过 `getLogger(name)` 获取，支持层次结构
- **处理器**：`StreamHandler`（控制台）、`FileHandler`（文件）、`RotatingFileHandler`（轮转）
- **格式化器**：控制日志输出格式，支持多种字段
- **配置方式**：代码配置、字典配置（推荐）、配置文件
- **最佳实践**：使用模块级记录器，使用 `__name__` 作为记录器名

---

## 练习

1. 配置日志同时输出到控制台（INFO+）和文件（DEBUG+），文件按天轮转
2. 编写一个装饰器，自动记录函数的入参、出参和执行时间
3. 创建一个自定义处理器，将 ERROR 以上级别的日志发送到邮件
4. 实现一个日志过滤器，只记录包含特定关键词的日志

