logging模块
1.logger的高级
相关组件
一个记录器可以绑定多个处理器
名称 | 作用 |
---|---|
Loggers | 记录器,提供应用程序代码直接使用的接口 |
Handlers | 处理器,将记录器产生的日志发送至目的地 |
Filters | 过滤器,提供更好的粒度控制,决定哪些日志会被输出 |
Formatters | 格式化器,设置日志内容的组成结构和消息字段 |
logging的日志等级
默认的日志级别为WARNING
日志等级(level) | 描述 |
---|---|
DEBUG | 最详细的日志信息,典型应用场景是 问题诊断 |
INFO | 信息详细程度仅次于DEBUG,通常只记录关键节点信息,用于确认一切都是按照我们预期的那样进行工作 |
WARNING | 当某些不期望的事情发生时记录的信息(如,磁盘可用空间较低),但是此时应用程序还是正常运行的 |
ERROR | 由于一个更严重的问题导致某些功能不能正常运行时记录的信息 |
CRITICAL | 当发生严重错误,导致应用程序不能继续运行时记录的信息 |
Handlers
它们将日志分发到不同的目的地。可以是文件、标准输出、邮件、或者通过 socke、htt等协议发送到任何地方
setFormatter():设置当前Handler对象使用的消息格式
- Streamhandler
标准输出stout分发器
sh = logging.StreamHandler(stream=None)
- Filehandler
将日志保存到磁盘文件的处理器
fh = logging.FileHandler(filename,mode='a',encoding=None,delay=False)
- BaseRotatingHandler
- RotatingFilehandler
滚动的多日志输出,按照时间or其他方式去生成多个日志 - TimedRotatingfilehandler
以下的使用较少
- Sockethandler
- Dataaramhandler
- Smtphandler
- Sysloghandler
- Nteventloghandler
- Httphandler
- WatchedFilehandler
- Qutelehandler
- Nullhandler
Formatters格式化
%(levelname)-8s
:左对齐(不足8位字符,空格占位)%(levelname)8s
:右对齐
属性 | 格式 | 描述 |
---|---|---|
asctime | %(asctime)s | 日志产生的时间,默认格式为msecs2003-07-0816:49:45,896 |
msecs | %(msecs)d | 日志生成时间的亳秒部分 |
created | %(created)f | time.tme)生成的日志创建时间戳 |
message | %(message)s | 具体的日志信息 |
filename | %(filename)s | 生成日志的程序名 |
name | %(name)s | 日志调用者 |
funcname | %( funcname)s | 调用日志的函数名 |
levelname | %(levelname)s | 日志级別( DEBUG,INFO, WARNING, 'ERRORCRITICAL) |
levene | %( leveling)s | 日志级别对应的数值 |
lineno | %(lineno)d | 日志所针对的代码行号(如果可用的话) |
module | %( module)s | 生成日志的模块名 |
pathname | %( pathname)s | 生成日志的文件的完整路径 |
process | %(process)d | 生成日志的进程D(如果可用) |
processname | %(processname)s | 进程名(如果可用) |
thread | %(thread)d | 生成日志的线程D(如果可用) |
threadname | %( threadname)s | 线程名(如果可用) |
2.初步使用
记录器的设置日志输出级别为第一道过滤,然后再是处理器过滤
test.py
import logging
import logging.handlers
# 1.创建一个记录器
mylogger = logging.getLogger('test.demo')
# 日志输出级别,需要比handler低(推荐使用DEBUG级别)
mylogger.setLevel(logging.DEBUG)
# 2.创建处理器handler
# 屏幕输出
simpleHandler = logging.StreamHandler()
simpleHandler.setLevel(logging.DEBUG) # 屏幕输出级别过滤
# 和文件一样
fileHandler = logging.FileHandler(filename='testDemo.log', mode='a', encoding='utf-8')
fileHandler.setLevel(logging.INFO) # 文件输出级别过滤
# soket发送 (SocketHandler在logging.handlers中)
socketHandler = logging.handlers.SocketHandler('127.0.0.1', 9020)
socketHandler.setLevel(logging.WARN)
# 3.formatter格式
# 数字表示:实现了占位对齐
# datefmt可以设置日期格式
simple_format = logging.Formatter("%(asctime)-20s %(levelname)-8s %(filename)-5s:%(lineno)-5d %(message)s")
file_format = logging.Formatter(
fmt="[%(asctime)-20s] [%(levelname)-5s] [%(threadName)-5s %(thread)-10d] [%(filename)-2s:%(lineno)-2d] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
socket_format = logging.Formatter('%(asctime)-20s %(levelname)-8s %(filename)-5s:%(lineno)-5d %(message)s')
# 4.给【处理器】添加【formatter格式】
simpleHandler.setFormatter(simple_format)
fileHandler.setFormatter(file_format)
socketHandler.setFormatter(socket_format)
mylogger.addHandler(socketHandler)
# 5.【记录器】绑定【处理器】
mylogger.addHandler(simpleHandler)
mylogger.addHandler(fileHandler)
# 6.定义一个过滤器(过滤器主要过滤的是logger对象加)
# 表示只输出到 logging.getLogger('test.XXX') test.开头的对象中,其他一概不输出
# flt = logging.Filter("test")
# 关联过滤器
# mylogger.addFilter(flt) # 可以给logger对象加
# fileHandler.addFilter(flt) # 也可以给Handler加
# 7.打印日志的代码
name = 'test'
age = 11
mylogger.debug("姓名 %s, 年龄%d", name, age)
mylogger.info("姓名 %s, 年龄%d" % (name, age))
mylogger.debug("姓名 {}, 年龄{}".format(name, age))
mylogger.error(f"姓名{name}, 年龄{age}")
mylogger.warning(f"姓名{name}, 年龄{age}")
mylogger.critical(f"姓名{name}, 年龄{age}")
2022-06-18 17:46:59,577 DEBUG aa.py:56 姓名 test, 年龄11
2022-06-18 17:46:59,577 INFO aa.py:57 姓名 test, 年龄11
2022-06-18 17:46:59,577 DEBUG aa.py:58 姓名 test, 年龄11
2022-06-18 17:46:59,577 ERROR aa.py:59 姓名test, 年龄11
2022-06-18 17:46:59,587 WARNING aa.py:60 姓名test, 年龄11
2022-06-18 17:46:59,587 CRITICAL aa.py:61 姓名test, 年龄11
3.错误信息打印
使用exception(e)打印
import logging
import logging.handlers
# 1.创建一个记录器
mylogger = logging.getLogger('test.demo')
# 日志输出级别,需要比handler低(推荐使用DEBUG级别)
mylogger.setLevel(logging.DEBUG)
# 2.创建处理器handler
# 屏幕输出
simpleHandler = logging.StreamHandler()
simpleHandler.setLevel(logging.DEBUG) # 屏幕输出级别过滤
# 和文件一样
fileHandler = logging.FileHandler(filename='testDemo.log', mode='a', encoding='utf-8')
fileHandler.setLevel(logging.INFO) # 文件输出级别过滤
# soket发送 (SocketHandler在logging.handlers中)
socketHandler = logging.handlers.SocketHandler('127.0.0.1', 9020)
socketHandler.setLevel(logging.WARN)
# 3.formatter格式
# 数字表示:实现了占位对齐
# datefmt可以设置日期格式
simple_format = logging.Formatter("%(asctime)-20s %(levelname)-8s %(filename)-5s:%(lineno)-5d %(message)s")
file_format = logging.Formatter(
fmt="[%(asctime)-20s] [%(levelname)-5s] [%(threadName)-5s %(thread)-10d] [%(filename)-2s:%(lineno)-2d] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
socket_format = logging.Formatter('%(asctime)-20s %(levelname)-8s %(filename)-5s:%(lineno)-5d %(message)s')
# 4.给【处理器】添加【formatter格式】
simpleHandler.setFormatter(simple_format)
fileHandler.setFormatter(file_format)
socketHandler.setFormatter(socket_format)
mylogger.addHandler(socketHandler)
# 5.【记录器】绑定【处理器】
mylogger.addHandler(simpleHandler)
mylogger.addHandler(fileHandler)
try:
int(name)
except Exception as e:
mylogger.error(e)
mylogger.exception(e) # 推荐使用exception打印错误信息
日志信息
# mylogger.error(e)打印的
[2022-06-18 16:33:16 ] [ERROR] [MainThread 8672656896] [y.py:52] invalid literal for int() with base 10: 'test'
# mylogger.exception(e)打印的
[2022-06-18 16:33:16 ] [ERROR] [MainThread 8596639232] [y.py:52] invalid literal for int() with base 10: 'test'
Traceback (most recent call last):
File "/Users/lxd670/test_pg3/testlog/y.py", line 50, in <module>
int(name)
4.配置文件配置logging
logging.conf
#记录器:提供应用程序代码直接使用的接口
#设置记录器名称,root必须存在!!!
[loggers]
keys=root,applog
#处理器,将记录器产生的日志发送至目的地
#设置处理器类型
[handlers]
keys=fileHandler,consoleHandler
#格式化器,设置日志内容的组成结构和消息字段
#设置格式化器的种类
[formatters]
keys=simpleFormatter
#设置记录器root的级别与种类
[logger_root]
level=DEBUG
handlers=consoleHandler
#设置记录器applog的级别与种类
[logger_applog]
level=DEBUG
handlers=fileHandler,consoleHandler
#起个对外的名字
qualname=applog
#继承关系
propagate=0
#设置
[handler_consoleHandler]
class=StreamHandler
args=(sys.stdout,)
level=DEBUG
formatter=simpleFormatter
[handler_fileHandler]
class=handlers.TimedRotatingFileHandler
#在午夜1点(3600s)开启下一个log文件,第四个参数0表示保留历史文件
args=('applog.log','midnight',3600,0)
level=DEBUG
formatter=simpleFormatter
[formatter_simpleFormatter]
format=%(asctime)s|%(levelname)8s|%(filename)s[:%(lineno)d]|%(message)s
#设置时间输出格式
datefmt=%Y-%m-%d %H:%M:%S
文件使用使用
import logging
import logging.config
logging.config.fileConfig('logging.conf')
#使用字典就能从任意格式文件进行配置,字典是一种接口格式
# logging.config.dictConfig({"loggers":"root,applog"})
rootLogger = logging.getLogger('applog')
rootLogger.debug("This is root Logger, debug")
logger = logging.getLogger('cn.cccb.applog')
logger.debug("This is applog, debug")
try:
int(a)
except Exception as e:
logger.exception(e)
5.字典配置
文件结构
mylog
├── conf.py
├── logs
│ └── demo_2022-06-18.log
├── my_handler.py
└── use_log.py
配置文件-conf.py
'class': 'test.my.TimeHandler'
conf.py不需要引入,会自己查使用的时候需要把当前文件夹添加带环境变量里
import os
# 1.配置日志输出文件加
logfile_dir='./logs'
if not os.path.isdir(logfile_dir):
os.makedirs(logfile_dir)
# 2.配置format(不写字典里,写在外面清晰)
standard_format = '[%(asctime)-20s] [%(levelname)-5s] [%(threadName)-5s %(thread)-10d] [%(filename)-2s:%(lineno)-2d] %(message)s'
simple_format = '%(asctime)-20s %(levelname)-8s %(filename)-5s:%(lineno)-5d %(message)s'
# 3.log配置字典
LOGGING_DIC = {
'version': 1, # 日志的版本号
'disable_existing_loggers': False, # 是否继承原有日志
'formatters': { # 1.格式化器<<<<<<
'standard': {
'format': standard_format
},
'simple': {
'format': simple_format,
'datefmt':"%Y-%m-%d %H:%M:%S" # 设置日期格式
},
},
'filters': {}, # 2.过滤器<<<<<<
'handlers': { # 3.处理器<<<<<<
'console': { # 相当于console=logging.StreamHandler()绑定日志级别、日志格式化
'level': 'DEBUG', # 定义级别
'class': 'logging.StreamHandler', # 绑定Handler
'formatter': 'simple' # 绑定formatter
},
'default': {
'level': 'DEBUG',
# 绑定自定义TimeHandler(继承WatchedFileHandler)
'class': 'mylog.my_handler.TimeHandler',
'formatter': 'standard', # 绑定formatter
'name': 'demo', # 设置输出日志的文件名
'file_path': logfile_dir, # 输出路径
'when': 'D', # 按天生成日志
'backupCount': 14, # 保留前14天的日志
'delay': True, #
'encoding': 'utf-8', # 编码
}
},
'loggers': { # 4.记录器<<<<<<
'generate': { # 相当于 logger = logging.getLogger('generate')
'handlers': ['default', 'console'],
'level': 'DEBUG', # 记录器的日志级别输出
'propagate': True, #
},
'test.demo': { # 相当于 logger = logging.getLogger('test.demo')
'handlers': ['console'],
'level': 'DEBUG',
'propagate': True,
},
},
}
自定义Handler-my_handler.py
文件名_年-月-日-时-分—秒.log
import os
import datetime
from logging.handlers import WatchedFileHandler
class TimeHandler(WatchedFileHandler):
def __init__(self, name, file_path, backupCount=3, mode='a', when='D', encoding=None, delay=False):
if not os.path.exists(file_path):
os.makedirs(file_path)
self._file_name = name
self.file_path = file_path
self.backupCount = backupCount if backupCount > 3 else 3
self.when = when.upper()
self.when_dict = {
'S': "%Y-%m-%d-%H-%M-%S",
'M': "%Y-%m-%d-%H-%M",
'H': "%Y-%m-%d-%H",
'D': "%Y-%m-%d"
}
self.file_name = "{}_{}.log".format(self._file_name, datetime.datetime.now().strftime(self.when_dict.get(self.when,'D')))
filename = os.path.join(self.file_path, self.file_name)
super().__init__(filename=filename, mode=mode,encoding=encoding, delay=delay)
def doChangeFile(self):
if self.stream:
self.stream.close()
self.stream = None
if not self.delay:
self.stream = self._open()
if self.backupCount > 0:
for s in self.getFilesToDelete():
print(f'remove :{s}')
os.remove(s)
def getFilesToDelete(self):
fileNames = os.listdir(self.file_path)
result = []
for fileName in fileNames:
if fileName.startswith(f"{self._file_name}_{datetime.datetime.now().strftime(self.when_dict.get(self.when,'D'))}"):
result.append(os.path.join(self.file_path, fileName))
result.sort()
if len(result) <= self.backupCount:
result = []
else:
result = result[:len(result) - self.backupCount]
return result
def emit(self, record):
current_file_name = "{}_{}.log".format(self._file_name,datetime.datetime.now().strftime(self.when_dict.get(self.when,'D')))
if current_file_name != self.file_name:
self.doChangeFile()
self.file_name = current_file_name
self.baseFilename = os.path.abspath(os.path.join(self.file_path, self.file_name))
if self.stream:
self.stream.flush()
self.stream.close()
self.stream = self._open()
self._statstream()
self.reopenIfNeeded()
super().emit(record)
使用-use_log.py
可以在定义在mylog里面,也可定义在外面
import logging.config
# 导入配置文件
from mylog.conf import LOGGING_DIC
logging.config.dictConfig(LOGGING_DIC)
# 获取日志对象
generate_logger = logging.getLogger('generate')
test_demo_logger = logging.getLogger('test.demo')
# 使用
generate_logger.info("generate test")
test_demo_logger.info("test.demo test")
# 控制台输出
2022-06-18 17:42:58 INFO use_log.py:9 generate test
2022-06-18 17:42:58 INFO use_log.py:10 test.demo test
# 日志文件
[2022-06-18 17:42:58,979] [INFO ] [MainThread 8606850560] [use_log.py:9 ] generate test
5.socket解决多进程日志问题
流程
客户端 -> socket -> 服务端
客户端发送的是一个字典,里面包含了日志信息(网络传输是byte类型,所以需要序列化)
服务端获取日志字典中的name创建logger对象(单例模式)
然后把其他信息交给logger对象处理
打印 obj = self.unPickle(chunk)
{'name': 'test.demo', 'msg': '姓名test, 年龄11', 'args': None, 'levelname': 'ERROR', 'levelno': 40, 'pathname': '/Users/lxd670/test_pg3/testlog/y.py', 'filename': 'y.py', 'module': 'y', 'exc_info': None, 'exc_text': None, 'stack_info': None, 'lineno': 46, 'funcName': '<module>', 'created': 1655548199.731752, 'msecs': 731.7519187927246, 'relativeCreated': 12.069940567016602, 'thread': 8627547648, 'threadName': 'MainThread', 'processName': 'MainProcess', 'process': 56709, 'asctime': '2022-06-18 18:29:59'}
文件路径
ser_log
├── conf.py
├── logs
│ └── socket_2022-06-18.log
├── my_handler.py
└── run.py
client.py
服务端
服务端不是特殊情况只需配置一个日志轮询handler就好了
使用
RotatingFilehandler
和TimedRotatingfilehandler
配置文件 - conf.py
import os
logfile_dir = './logs'
if not os.path.isdir(logfile_dir):
os.makedirs(logfile_dir)
standard_format = '[%(asctime)-20s] [%(levelname)-5s] [%(threadName)-5s %(thread)-10d] [%(filename)-2s:%(lineno)-2d] %(message)s'
simple_format = '%(asctime)-20s %(levelname)-8s %(filename)-5s:%(lineno)-5d %(message)s'
# log配置字典
LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': standard_format
},
'simple': {
'format': simple_format,
'datefmt': "%Y-%m-%d %H:%M:%S"
},
},
'filters': {},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'default': {
'level': 'DEBUG',
'class': 'ser_log.my_handler.TimeHandler',
'formatter': 'standard',
'name': 'socket',
'file_path': logfile_dir,
'when': 'D',
'backupCount': 14,
'delay': True,
'encoding': 'utf-8',
}
},
'loggers': {
'generate': {
'handlers': ['default', 'console'],
'level': 'DEBUG',
'propagate': True,
}
},
}
自定义handler - my_handler.py
import os
import datetime
from logging.handlers import WatchedFileHandler
class TimeHandler(WatchedFileHandler):
# 支持多进程的TimeHandler
def __init__(self, name, file_path, backupCount=3, mode='a', when='D', encoding=None, delay=False):
if not os.path.exists(file_path):
os.makedirs(file_path)
self._file_name = name
self.file_path = file_path
self.backupCount = backupCount if backupCount > 3 else 3
self.when = when.upper()
self.when_dict = {
'S': "%Y-%m-%d-%H-%M-%S",
'M': "%Y-%m-%d-%H-%M",
'H': "%Y-%m-%d-%H",
'D': "%Y-%m-%d"
}
self.file_name = "{}_{}.log".format(self._file_name, datetime.datetime.now().strftime(self.when_dict.get(self.when,'D')))
filename = os.path.join(self.file_path, self.file_name)
super().__init__(filename=filename, mode=mode,encoding=encoding, delay=delay)
def doChangeFile(self):
if self.stream:
self.stream.close()
self.stream = None
if not self.delay:
self.stream = self._open()
if self.backupCount > 0:
for s in self.getFilesToDelete():
print(f'remove :{s}')
os.remove(s)
def getFilesToDelete(self):
fileNames = os.listdir(self.file_path)
result = []
for fileName in fileNames:
if fileName.startswith(f"{self._file_name}_{datetime.datetime.now().strftime(self.when_dict.get(self.when,'D'))}"):
result.append(os.path.join(self.file_path, fileName))
result.sort()
if len(result) <= self.backupCount:
result = []
else:
result = result[:len(result) - self.backupCount]
return result
def emit(self, record):
current_file_name = "{}_{}.log".format(self._file_name,datetime.datetime.now().strftime(self.when_dict.get(self.when,'D')))
if current_file_name != self.file_name:
self.doChangeFile()
self.file_name = current_file_name
self.baseFilename = os.path.abspath(os.path.join(self.file_path, self.file_name))
if self.stream:
self.stream.flush()
self.stream.close()
self.stream = self._open()
self._statstream()
self.reopenIfNeeded()
super().emit(record)
入口文件 - run.py
import pickle
import logging
import logging.handlers
import socketserver
import struct
class LogRecordStreamHandler(socketserver.StreamRequestHandler):
"""
流式日志记录请求的处理程序。
这基本上使用任何日志记录策略记录记录
本地配置。
"""
def handle(self):
"""
处理多个请求 - 每个请求的长度为 4 字节,
然后是pickle格式的LogRecord。 记录记录
根据本地配置的任何策略。
"""
while True:
chunk = self.connection.recv(4)
if len(chunk) < 4:
break
slen = struct.unpack('>L', chunk)[0]
chunk = self.connection.recv(slen)
while len(chunk) < slen:
chunk = chunk + self.connection.recv(slen - len(chunk))
obj = self.unPickle(chunk)
record = logging.makeLogRecord(obj)
self.handleLogRecord(record)
def unPickle(self, data):
return pickle.loads(data)
def handleLogRecord(self, record):
# 获取远程使用的logger名字
if self.server.logname is not None:
name = self.server.logname
else:
name = record.name
# 创建对于的logger实例化
logger = logging.getLogger(name)
logger.handle(record)
class LogRecordSocketReceiver(socketserver.ThreadingTCPServer):
"""
Simple TCP socket-based logging receiver suitable for testing.
"""
allow_reuse_address = True
# 绑定ip和端口
# logging.handlers.DEFAULT_TCP_LOGGING_PORT是9020端口
def __init__(self, host='localhost',
port=logging.handlers.DEFAULT_TCP_LOGGING_PORT,
handler=LogRecordStreamHandler):
socketserver.ThreadingTCPServer.__init__(self, (host, port), handler)
self.abort = 0
self.timeout = 1
self.logname = None
def serve_until_stopped(self):
import select
abort = 0
while not abort:
rd, wr, ex = select.select([self.socket.fileno()],
[], [],
self.timeout)
if rd:
self.handle_request()
abort = self.abort
def main():
import logging.config
from ser_log import conf
# 配置日志
logging.config.dictConfig(conf.LOGGING_DIC)
tcpserver = LogRecordSocketReceiver()
print('About to start TCP server...')
tcpserver.serve_until_stopped()
if __name__ == '__main__':
main()
# 服务端控制台打印
About to start TCP server...
2022-06-18 17:59:41 DEBUG clin.py:19 我是客户端日志
# 服务端log文件
[2022-06-18 17:59:41,694] [DEBUG] [MainThread 8663756288] [clin.py:19] 我是客户端日志
客户端
client.py
import logging
import logging.handlers
generateLogger = logging.getLogger(f'generate')
generateLogger.setLevel(logging.DEBUG)
# 创建SocketHandler
socketHandler = logging.handlers.SocketHandler('127.0.0.1', 9020)
# 创建simpleHandler
simpleHandler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)-20s %(levelname)-8s %(filename)-5s:%(lineno)-5d %(message)s')
simpleHandler.setFormatter(formatter)
# rootLogger绑定Handler对象
generateLogger.addHandler(socketHandler)
generateLogger.addHandler(simpleHandler)
# 发送日志
generateLogger.debug(f'我是客户端日志')
# 客户端控制台打印
2022-06-18 17:59:41,694 DEBUG clin.py:19 我是客户端日志
客户端发送多个socket及注意事项
logging.getLogger()
没有name值默认获取root
import os, time
from multiprocessing import Process, Pool
import logging
import logging.handlers
# 创建rootLogger
rootLogger = logging.getLogger()
rootLogger.setLevel(logging.DEBUG)
# 创建Handler
# 网络输出
socketHandler1 = logging.handlers.SocketHandler('127.0.0.1', logging.handlers.DEFAULT_TCP_LOGGING_PORT)
socketHandler2 = logging.handlers.SocketHandler('127.0.0.2', logging.handlers.DEFAULT_TCP_LOGGING_PORT)
# 屏幕输出
simpleHandler = logging.StreamHandler()
# 绑定两个socket
rootLogger.addHandler(socketHandler1)
rootLogger.addHandler(socketHandler2)
rootLogger.addHandler(simpleHandler)
def run_a_sub_proc(name, age):
rootLogger.info('root>>>')
# 配置给rootLogger设置的,其他实例化的也都有效
# logging.getLogger([name]) # 这个name需要和服务端对于,这样服务端才会记录
generate_logger = logging.getLogger('generate')
generate_logger.debug("姓名 %s, 年龄%d", name, age)
generate_logger.info("姓名 %s, 年龄%d" % (name, age))
generate_logger.debug("姓名 {}, 年龄{}".format(name, age))
generate_logger.error(f"姓名{name}, 年龄{age}")
generate_logger.warning(f"姓名{name}, 年龄{age}")
generate_logger.critical(f"姓名{name}, 年龄{age}")
if __name__ == '__main__':
print(f'主进程({os.getpid()})开始...')
p = Pool(3)
for i in range(1, 10):
p.apply_async(run_a_sub_proc, args=(i, i + 1))
p.close()
p.join()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律