Learn python with socratica [My notes] - part 15- Logging

Lesson 17

Introduction

logging就是记录的意思,也就是生成日志的一种方式。在程序运行过程中,logging模块可以记录所有的一切【按需记录】。这对于开发者来说很重要,好的程序是错哪儿都知道直接知道发生什么问题。

Functions

logging帮助程序员把重要的信息写入文件或者其他形式的输出,这些信息更多的是代码的执行部分和代码的问题。每个logging都有一个等级,一共5个内建的等级,包括:Debug,Info,Warning,Error,critical。开发者也可以自己创建新的等级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
	import logging
dir(logging)

output:
['BASIC_FORMAT',
'BufferingFormatter',
'CRITICAL',
'DEBUG',
'ERROR',
'FATAL',
'FileHandler',
'Filter',
'Filterer',
'Formatter',
'Handler',
'INFO',
'LogRecord',
'Logger',
'LoggerAdapter',
'Manager',
'NOTSET',
'NullHandler',
'PercentStyle',
'PlaceHolder',
'RootLogger',
'StrFormatStyle',
'StreamHandler',
'StringTemplateStyle',
'Template',
'WARN',
'WARNING',
'_STYLES',
'_StderrHandler',
'__all__',
'__author__',
'__builtins__',
'__cached__',
'__date__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__path__',
'__spec__',
'__status__',
'__version__',
'_acquireLock',
'_addHandlerRef',
'_checkLevel',
'_defaultFormatter',
'_defaultLastResort',
'_handlerList',
'_handlers',
'_levelToName',
'_lock',
'_logRecordFactory',
'_loggerClass',
'_nameToLevel',
'_releaseLock',
'_removeHandlerRef',
'_showwarning',
'_srcfile',
'_startTime',
'_warnings_showwarning',
'addLevelName',
'atexit',
'basicConfig',
'captureWarnings',
'collections',
'critical',
'currentframe',
'debug',
'disable',
'error',
'exception',
'fatal',
'getLevelName',
'getLogRecordFactory',
'getLogger',
'getLoggerClass',
'handlers',
'info',
'io',
'lastResort',
'log',
'logMultiprocessing',
'logProcesses',
'logThreads',
'makeLogRecord',
'os',
'raiseExceptions',
'root',
'setLogRecordFactory',
'setLoggerClass',
'shutdown',
'sys',
'threading',
'time',
'traceback',
'warn',
'warning',
'warnings',
'weakref']

logging的功能还是比较复杂的,这节课主要以基本的日志为主,其他的形式需要萌新们自己实验。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
 	import logging
help(logging.basicConfig)

output:
Help on function basicConfig in module logging:

basicConfig(**kwargs)
Do basic configuration for the logging system.

This function does nothing if the root logger already has handlers
configured. It is a convenience method intended for use by simple scripts
to do one-shot configuration of the logging package.

The default behaviour is to create a StreamHandler which writes to
sys.stderr, set a formatter using the BASIC_FORMAT format string, and
add the handler to the root logger.

A number of optional keyword arguments may be specified, which can alter
the default behaviour.

filename Specifies that a FileHandler be created, using the specified
filename, rather than a StreamHandler.
filemode Specifies the mode to open the file, if filename is specified
(if filemode is unspecified, it defaults to 'a').
format Use the specified format string for the handler.
datefmt Use the specified date/time format.
style If a format string is specified, use this to specify the
type of format string (possible values '%', '{', '$', for
%-formatting, :meth:`str.format` and :class:`string.Template`
- defaults to '%').
level Set the root logger level to the specified level.
stream Use the specified stream to initialize the StreamHandler. Note
that this argument is incompatible with 'filename' - if both
are present, 'stream' is ignored.
handlers If specified, this should be an iterable of already created
handlers, which will be added to the root handler. Any handler
in the list which does not have a formatter assigned will be
assigned the formatter created in this function.

Note that you could specify a stream created using open(filename, mode)
rather than passing the filename and mode in. However, it should be
remembered that StreamHandler does not close its stream (since it may be
using sys.stdout or sys.stderr), whereas FileHandler closes its stream
when the handler is closed.

.. versionchanged:: 3.2
Added the ``style`` parameter.

.. versionchanged:: 3.3
Added the ``handlers`` parameter. A ``ValueError`` is now thrown for
incompatible arguments (e.g. ``handlers`` specified together with
``filename``/``filemode``, or ``filename``/``filemode`` specified
together with ``stream``, or ``handlers`` specified together with
``stream``.



import logging
logging.basicConfig(filename="./log.out")
logger = logging.getLogger()

logger.info("first")
print(logger.level)

output:
30

这里会生成一个日志文件在当前目录下, 30代表着什么呢?在logging的等级制度里,分为:NOTSET-0,DEBUG-10,INFO-20,WARNING-30,ERROR-40,CRITICAL-50。30就代表当前的日志等级是basicConfig默认的WARNING。此时,我们做一个改动:

1
2
3
4
5
6
7
8
9
10
import logging
logging.basicConfig(filename="./log.out",
level = logging.DEBUG)
logger = logging.getLogger()

logger.info("first")
print(logger.level)

output:
10

这里,理论上会打印出10,但是出错了,暂时找不出原因。如果打开log.out,内容如下:INFO:root:first.

New Format

根据自己的需要改写日志的格式,python提供了很多的信息模块,举个例子:

1
2
3
4
5
6
7
8
import logging
LOG_FORMAT="%(levelname) %(asctime)s - %(massage)s"
logging.basicConfig(filename="./log.out",
level = logging.DEBUG,
format = LOG_FORMAT)
logger = logging.getLogger()

logger.info("first")

如果打开日志,会发现多了一行新的日志,格式会改为:INFO Y-M-D H-M-S-MS - first。如果想要每次都重写日志,则需要加入filemode

1
2
3
4
5
6
7
8
9
import logging
LOG_FORMAT="%(levelname) %(asctime)s - %(massage)s"
logging.basicConfig(filename="./log.out",
level = logging.DEBUG,
format = LOG_FORMAT,
filemode = 'w')
logger = logging.getLogger()

logger.info("second")

此时的输出日志就只有second这一行了。这里还需要注意一点,只有当前等级之上的message才会被写入日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import logging
LOG_FORMAT="%(levelname) %(asctime)s - %(massage)s"
logging.basicConfig(filename="./log.out",
level = logging.DEBUG,
format = LOG_FORMAT,
filemode = 'w')
logger = logging.getLogger()

# Test Massages
logger.debug("This is a harmless debug message.")
logger.info("Just some useful info.")
logger.warning("Warning!")
logger.error("Did you just try to divide by zero?")
logger.critical("The entire internet is down!")

运行之后,所有的massage都会在日志里出现。但是如果把basicConfig的level改成ERROR,那么之后存在最后两条。

Example

如何运用日志更好地调试代码呢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  import logging
import math

LOG_FORMAT="%(levelname) %(asctime)s - %(massage)s"
logging.basicConfig(filename="./mathlog.out",
level = logging.DEBUG,
format = LOG_FORMAT,
filemode = 'w')
logger = logging.getLogger()

def quadratic_formula(a,b,c):
"""Return the solutions to the equation ax^2 + bx + c = 0."""
logger.info("quafratic_formula({0},{1},{2})".format(a,b,c))

# compute the discriminant
logger.debug("# Compute the discriminant")
disc = b**2 - 4*a*c

# compute the two roots
logger.debug("# Compute the two roots")
root1 = (-b + math.sqrt(disc)) / (2*a)
root2 = (-b - math.sqrt(disc)) / (2*a)

# Return the roots
logger.debug("# Return the roots")
return (root1, root2)

roots = quadratic_formula(1,0,-4)
print(roots)

output:
(2.0, -2.0)

如果打开日志文件,一共四行:

  • INFO
  • DEBUG- # Compute the discriminant
  • DEBUG- # Compute the two roots
  • DEBUG- # return the roots

下面举一个错误例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
 	import logging
import math

LOG_FORMAT="%(levelname) %(asctime)s - %(massage)s"
logging.basicConfig(filename="./mathlog.out",
level = logging.DEBUG,
format = LOG_FORMAT,
filemode = 'w')
logger = logging.getLogger()

def quadratic_formula(a,b,c):
"""Return the solutions to the equation ax^2 + bx + c = 0."""
logger.info("quafratic_formula({0},{1},{2})".format(a,b,c))

# compute the discriminant
logger.debug("# Compute the discriminant")
disc = b**2 - 4*a*c

# compute the two roots
logger.debug("# Compute the two roots")
root1 = (-b + math.sqrt(disc)) / (2*a)
root2 = (-b - math.sqrt(disc)) / (2*a)

# Return the roots
logger.debug("# Return the roots")
return (root1, root2)

# error: c=-4 -> c=1
roots = quadratic_formula(1,0,1)
print(roots)

output:
---------------------------------------------------------------------------

ValueError Traceback (most recent call last)

<ipython-input-15-d0549ea89a68> in <module>()
27
28 # error: c=-4 -> c=1
---> 29 roots = quadratic_formula(1,0,1)
30 print(roots)

<ipython-input-15-d0549ea89a68> in quadratic_formula(a, b, c)
19 # compute the two roots
20 logger.debug("# Compute the two roots")
---> 21 root1 = (-b + math.sqrt(disc)) / (2*a)
22 root2 = (-b - math.sqrt(disc)) / (2*a)
23

ValueError: math domain error

出错,因此计算到math.sqrt时因为根号下不能为负,所以报错。如果打开此时的日志文件,可以发现一共有三行:

  • INFO
  • DEBUG- # Compute the discriminant
  • DEBUG- # Compute the two roots

最后一行即为出错的地方,这也就是我们平时DEBUG时候的方式。在代码中添加logging可以更好地找出错误所在,也可以提高代码的可读性。

Youtube source:
https://www.youtube.com/watch?v=bY6m6_IIN94&list=PLi01XoE8jYohWFPpC17Z-wWhPOSuh8Er-