# Python日志基础

## 标准库日志模块

Python在标准库 中包含了一个日志模块，提供了可伸缩的框架来发送日志消息。这个模块被广泛使用并且成为大多数开发者用于日志记录。

这个日志模块提供了一种应用程序配置的不同处理方式，以及路由日志消息到不同处理器的方法。这样通过一个高度可配置来处理不同的使用场景。

为了发出一个日志消息，调用者首先需要请求一个命名的logger。这个命名被应用程序用于配置不同的规则用于不同的loggers。这个logger就可以用于发送简单的格式化的消息给不同的日志级别（DEBUG, INFO, ERROR 等等），然后logger即可以被应用程序用于处理不同优先级的消息。

以下是例子：

```python
import logging
log = loggging.getLogger("my-logger")
log.info("Hello, world")
```

在内部，这个消息被加入到一个LogRecord对象，然后路由到这个logger注册的处理器对象。这个处理器就使用格式器将这个LogRecord转换成字符串并发送这个字符串。

幸运的是，程序员并不需要详细了解这个机制。Python文档包含了一个如何使用logging模块的文档。以下是该文档的一个使用指南。

## 从模块发出到日志

模块，也就是其他程序寓言所说的库，是发送日志消息到最好方式。模块不需要配置如何记录日志，因为这是应用程序的责任，只要导入和使用模块。模块只需要让应用程序能够路由它的日志消息就可以了。

基于上述理由，每个模块通常会使用一个logger，以便应用程序能够路由不同模块的日志，并使得模块中的日志代码简化。

```python
import loggin

log = logging.getLogger(__name__)

def do_something():
    log.debug("Doing something!")
```

就这么简单，**在Python中，`__name__`包含了当前模块到完整名字，所以可以在任何模块使用。**

## 配置logging

主应用将配置loggging子系统，这样日志消息就会去往它们该去到地方。Python logging模块提供了大量的调整方法，配置也非常简单。

总的来说，logging的配置包括添加一个`Formatter`和一个`Handler`到根logger。因为这个root logger是共用的，这个logging模块提供了一个称为`basicConfig`的功能来处理主要的使用场景。

应用程序将尽可能配置logging，通常是应用程序首先要做的事情，这样应用程序启动时就不会丢失日志。

最后，应用程序将在主应用代码中包装一个`try/except`代码块，以便通过logging接口而不是标准错误`stderr`来发送异常。

## 案例1：发送日志到Systemd的标准输出

这个最简单的也可能是最好的配置项是使用systemd，应用程序最后需要发送日志消息到标准输出`stdout`或标准错误`stderr`并让systemd转发消息给journald和syslog就可以。这种情况下，甚至不需要捕捉异常，因为Python已经将异常写入到标准错误中。

```python
import logging
import os

loggging.basicConfig(level=on.environ.get("LOGLEVEL","INFO"))

exit(main())
```

以上，应用程序现在将所有消息以level INFO或以上级别发送到`stderr`，使用的是简单格式：

```
ERROR:the.module.name:The log message
```

## 案例3：发送日志到文件

将日志记录到独立的文件，可以方便日志采集系统独立对不同应用采集日志，进行集中化分析处理。

```python
import logging
import logging.handlers
import os

def main():
    handler = logging.handlers.WatchedFileHandler(
            os.environ.get("LOGFILE", "/var/log/myApp.log"))
    formatter = logging.Formatter(logging.BASIC_FORMAT)
    handler.setFormatter(formatter)
    root = logging.getLogger()
    root.setLevel(os.environ.get("LOGLEVEL", "INFO"))
    root.addHandler(handler)

    ...

    try:
        vmname = sys.argv[2]
        logging.info("vmname is %s" % vmname)
    except:
        logging.exception("Can't get vmname!")
        sys.exit(-2)

if __name__ == "__main__":
    main()
```

参考 [When to use logging](https://docs.python.org/2/howto/logging.html#when-to-use-logging) 可以看到不同级别日志方式，例如：

```
logging.warning()
logging.error()
logging.exception()
logging.critical()
```

**`注意`** (参考 [TypeError: unsupported operand type(s) for %: 'NoneType' and 'str'](https://stackoverflow.com/questions/23372824/typeerror-unsupported-operand-types-for-nonetype-and-str))

正确的格式是：

```python
logging.info("vmname is %s" % vmname)
```

不要写成

```python
logging.info("vmname is %s") % vmname
```

错误的格式会导致提示：`TypeError: unsupported operand type(s) for %: 'NoneType' and 'str'`

类似 `print("I said: %r" % x)`

## 参考

* [Python Logging Basics](https://www.loggly.com/ultimate-guide/python-logging-basics/) - 本文译自这篇指南，加上自己的一些实践


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://huataihuang.gitbook.io/cloud-atlas-draft/develop/python/log/python_logging_basics.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
