日志和配置是应用不可缺少的部分,本文用于介绍dynaconf和loguru的简要用法。

dynaconf

dynaconf是一个配置管理包,支持多种配置文件格式,如:toml、yaml、json、ini及环境变量等

pip install dynaconf

mkdir config
cd config
dynaconf init -f toml

命令生成的目录结构如下:

config
├── .gitignore
├── .secrets.toml
├── config.py
└── settings.toml

.secrets.toml用于存放敏感信息,默认被添加到.gitignore中,不会提交到代码仓库。

从环境变量读取配置:

# 环境变量前缀在config.py中设置
export PYDEMO_tag=dynaconf

 

from src.config.config import settings
print(settings.tag) # dynaconf

config.py中可以指定读取的配置文件:

from dynaconf import Dynaconf

settings = Dynaconf(
    # 环境变量前缀
    envvar_prefix="PYDEMO",
    # 可以指定多个配置文件,如:settings.dev.toml
    settings_files=['settings.toml', '.secrets.toml'],
)

settinngs.toml中写入配置:

[person]
name = "eason"
age = 30

读取配置数据:

from dataclasses import dataclass
from src.config.config import settings

print(settings.person) # 输出:{'name': 'eason', 'age': 30}

绑定配置到类型

也可以定义具体的类型来绑定配置数据,这样在使用时更方便

from dataclasses import dataclass
from src.config.config import settings


@dataclass
class Person:
    name: str
    age: int


p = Person(**settings.person)
print(p.name)

多环境配置

单个配置文件

不同环境读取不同的配置:

[production]
person = { name = "prod", age = 100 }
[development]
person = { name = "dev", age = 100 }

config.py中设置environments的值是True

settings = Dynaconf(
    envvar_prefix="PYDEMO",
    settings_files=['settings.toml', '.secrets.toml'],
    environments=True
)

环境变量settings.ENV_FOR_DYNACONF的值默认是development,dynaconf会读取[development]节点下的配置

# 设置环境变量,从production节点下读取配置
export ENV_FOR_DYNACONF=production
# unset ENV_FOR_DYNACONF

或者

import os

os.environ["ENV_FOR_DYNACONF"] = "production"

输出结果

from src.config.config import settings

print(settings.ENV_FOR_DYNACONF)  # 输出production

p = Person(**settings.person)  # 输出prod
print(p.name)

多个配置文件

from dynaconf import Dynaconf

_cfg_files = ['settings.pro.toml', '.secrets.pro.toml']
if __debug__:
    _cfg_files = ['settings.dev.toml', '.secrets.dev.toml']

settings = Dynaconf(
    envvar_prefix="DYNACONF",
    settings_files=_cfg_files
)

这样我们就可以把不同环境下的配置项写入到不同的配置文件中了

 


 

loguru

loguru是一个易于配置和使用的Python日志库

安装:

pip install loguru

默认输出日志到控制台:

from loguru import logger

logger.info("一条日志信息")

日志输出结果如下:

2023-06-07 21:06:04.154 | INFO     | __main__:<module>:3 - 一条日志信息

loguru输出的日志带有颜色,不仅美观,还易于阅读,如下图所示

python 日志记录到毫秒_环境变量

 

结构化日志

除了简单输出日志外,还可以记录结构化日志,示例如下:

from dataclasses import dataclass

from loguru import logger


@dataclass
class Person:
    name: str
    age: int


person = Person("eason", 30)
logger.info("人员信息:{p}", p=person)

日志输出结果:

2023-06-19 20:48:10.900 | INFO     | __main__:<module>:14 - 人员信息:Person(name='eason', age=30)

对于异常,loguru也可以输出详细的调用堆栈:

from loguru import logger


def throws():
    raise NotImplementedError("未实现的函数")


try:
    throws()
except Exception as err:
    logger.exception("出错了:{err}", err=err)

输出日志:

python 日志记录到毫秒_API_02

 

滚动日志

在生产环境,日志通常会记录到文本中而非仅仅打印到控制台。文本日志要考虑到磁盘占用问题,通常会采用滚动日志,配置如下:

# 禁用控制台日志
logger.remove(0)
logger.add("/Users/Eason/Desktop/pdemo/logs/log.log"
           , encoding="utf-8"
           , level="INFO"        # 输出的最小日志级别
           , rotation="1 MB"     # 每个日志文件的大小,超过该大小则创建新文件
           , retention=10        # 保留的日志文件数量,不超过10个
           , enqueue=True)       # 多进程安全

替换FastAPI中的日志模块

FastAPI是Python中用于开发API的web框架,默认使用内置的logging模块,为了统一使用loguru来记录日志,需要对日志模块进行替换。下面我们结合dynaconf来实现一个自定义的可配置日志,项目目录如下:

.
├── README.md
├── docs
│   └── dev
│       └── local.http
├── mypy.ini
├── requirements.txt
├── ruff.toml
├── src
    ├── config
    │   ├── _config.py
    │   ├── app_options.py
    │   └── settings.yaml
    ├── my_logger.py
    └── main.py

配置如下:

server:
  port: 9001

log:
  level: INFO
  encoding: utf-8
  rotation: 10 MB
  retention: 20
  enqueue: True
  file_path: /Users/Eason/Desktop/pdemo/logs/log.log

app_options.py中定义配置模型:

from __future__ import annotations

from dataclasses import dataclass

from src.config._config import settings


@dataclass
class AppOptions:
    log: LogOptions
    server: ServerOptions

@dataclass
class LogOptions:
    level: str
    file_path: str
    enqueue: bool
    rotation: int | str
    retention: int
    encoding: str


@dataclass
class ServerOptions:
    port: int



options = AppOptions(log=LogOptions(**settings.log), server=ServerOptions(**settings.server))

my_logger.py中添加配置相关代码:

from __future__ import annotations

from dataclasses import dataclass

from src.config._config import settings


@dataclass
class AppOptions:
    log: LogOptions
    server: ServerOptions

@dataclass
class LogOptions:
    level: str
    file_path: str
    enqueue: bool
    rotation: int | str
    retention: int
    encoding: str


@dataclass
class ServerOptions:
    port: int



options = AppOptions(log=LogOptions(**settings.log), server=ServerOptions(**settings.server))

main.py文件中定义API:

import uvicorn
from fastapi import FastAPI, Response, status

import src.my_logger as mylogger
from src.config.app_options import options


app = FastAPI(title="API-Demo", debug=__debug__)


@app.get("/healthcheck")
@app.head("/healthcheck")
def health_check():
    return "healthcheck"



if __name__ == "__main__":
    mylogger.init_log()
    log_config = mylogger.replace_uvicorn_log(uvicorn.config.LOGGING_CONFIG)
    mylogger.logger.info("服务已启动,监听端口:{port}", port=options.server.port)
    uvicorn.run(app, host="0.0.0.0", port=options.server.port, log_config=log_config)

启动API服务,输出如下日志:

python 日志记录到毫秒_环境变量_03

 

可以看到,已经使用loguru成功替换掉了内置的logging模块。