python Logging日志记录模块详解
写在篇前
logging是Python的一个标准库,其中定义的函数和类为应用程序和库的开发实现了一个灵活的事件日志系统。Python logging 的配置由四个部分组成:Logger、Handlers、Filter、Formatter。本篇博客将依次介绍这四个主要部分以及logging的基本应用。
在开始之前,我们有必要先了解一下,什么时候我们才有必要使用logging模块,什么时候抛出异常(raise Exception
),什么时候使用简单的print
函数即可,官方文档给我们总结了一个表格:
Task you want to perform | The best tool for the task |
---|---|
命令行终端输出或则程序一般的输出情景 |
print()
|
报告程序正常运行期间发生的事件(例如,状态监视或故障调查) |
logging.info() (或则 logging.debug() 用于诊断目的细节输出)
|
发出有关特定运行时事件的警告 |
warnings.warn() : 用于如果问题是可以避免的,且应修改应用程序以消除警告的情景;logging.warning() 用于如果应用程序无法处理该情况,但仍应注意该事件的情景。
|
报告有关特定运行时事件的错误 | Raise an exception |
报告在不引发异常的情况下抑制错误(例如,长时间运行的服务器进程中的错误处理程序) |
logging.error() , logging.exception() 或则logging.critical() 适用于特定错误和应用程序域
|
Loggers
logger是暴露给代码进行日志操作的接口。需要注意的是,logger不应该直接实例化,而应通过模块级函数logging.getLogger(name)
创建。如果name是具有层级结构的命名方式,则logger之间也会有层级关系。如name为foo.bar
,foo.bar.baz
, foo.bam
的logger是foo
的子孙,默认子logger日志会向父logger传播,可以通过logger.propagate=False
禁止;对具有相同名称的getLogger()
的多次调用将始终返回同一Logger对象的引用。logger对象的功能包括以下三项:
- 向应用程序暴露
info
、debug
等方法,用于程序运行时进行日志记录; - 根据log level(默认过滤工具)或Filter对象确定要处理的日志消息;
- 将日志消息传递给所有感兴趣的Log Handler;
另外,需要理清以下两个概念:
Log Record
Each message that is written to the
Logger
is aLog Record
. 可以使用makeLogRecord()
函数创建record对象(一般用不上),record对象常用的属性如下,全部属性请参考官方文档:record.levelname
# record levelrecord.levelno
# record level numberrecord.msg
# record承载的日志消息record.pathname
# emit该日志消息的程序文件record.lineno
# emit该日志消息的程序行号record.getMessage()
# 同record.msg
从python 3.2起,logging模块提供了工厂函数
getLogRecordFactory()
和setLogRecordFactory()
方便、支持用户自定义record属性。old_factory = logging.getLogRecordFactory()def record_factory(*args, **kwargs):record = old_factory(*args, **kwargs)record.custom_attribute = 0xdecafbadreturn recordlogging.setLogRecordFactory(record_factory)
Log Level
每个logger都需要设置一个log level,该log level描述了logger将处理日志消息的级别; 每个log record也具有一个log Level,指示该特定日志消息的级别。 log record还可以包含被记录事件的metadata,包括诸如堆栈跟踪或错误代码之类的详细信息。如果设置了logger的log level,系统便只会输出 level 数值大于或等于该 level 的的日志结果,例如我们设置了输出日志 level 为 INFO,那么输出级别小于 INFO 的日志,如DEBUG 和 NOSET 级别的消息不会输出。logger还有一个effective level的概念,如果logger没有显式设置log level,则使用其parent logger的log level作为其effective level,其中root logger的默认log level是
WARNING
。NOTEST
:lowest level# Level Num 0logger.setLevel(level=logging.NOTEST)
DEBUG
: Low level system information for debugging purposes# Level Num 10logger.setLevel(level=logging.DEBUGlogger.debug('Debugging')
INFO
: General system information# Level Num 20logger.setLevel(level=logging.INFO)logger.info('This is a log info')
WARNING
: Information describing a minor problem that has occurred.# Level Num 30,默认是该levellogger.setLevel(level=logging.WARNING)logger.warning('Warning exists')logger.warn('Warning exists') # deprecated
ERROR
: Information describing a major problem that has occurred.# Level Num 40logger.setLevel(level=logging.ERROR)logger.error('some error occur')
CRITICAL
: Information describing a critical problem that has occurred.# Level Num 50logger.setLevel(level=logging.NOTEST)logger.critical('critical err occur')logger.fatal('fatal error occur')
上面各个函数还有一个统一的调用方式logger.log(level, msg, exc_info=True)
,其中,level
是指上面标注的level num
,exc_info
指示是否打印执行信息;Logger.exception()
创建与 Logger.error()
相似的日志消息,不同之处是, Logger.exception()
同时还记录当前的堆栈追踪,请仅从异常处理程序调用此方法。
logger对象还有一些其他属性、方法值得关注:
>>>logger = logging.getLogger('main') # 如果不指定name将会返回root logger
>>>logger.setLevel(level=logging.DEBUG)>>>logger.disabled # False
>>>logger.propagate=False
>>>logger.getEffectiveLevel() # 10
>>>logger.isEnabledFor(20) # True>>>logging.disable(20)
>>>logger.isEnabledFor(20) # False
>>>logger.isEnabledFor(30) # True>>>child_logger = logger.getChild('def.ghi') # <Logger main.def.ghi (DEBUG)># Filter
addFilter(filter)
removeFilter(filter)# handler
logger.hasHandlers() # False
addHandler(hdlr)
removeHandler(hdlr)
关于上面各种Level的调用情景,官方文档也给出了相应说明:
Level | When it’s used |
---|---|
DEBUG
|
Detailed information, typically of interest only when diagnosing problems. |
INFO
|
Confirmation that things are working as expected. |
WARNING
|
An indication that something unexpected happened, or indicative of some problem in the near future (e.g. ‘disk space low’). The software is still working as expected. |
ERROR
|
Due to a more serious problem, the software has not been able to perform some function. |
CRITICAL
|
A serious error, indicating that the program itself may be unable to continue running. |
Handlers
Once a logger has determined that a message needs to be processed, it is passed to a Handler. The handler is the engine that determines what happens to each message in a logger. Like loggers, handlers also have a log level. If the log level of a log record doesn’t meet or exceed the level of the handler, the handler will ignore the message.
handler对象负责将适当的日志消息(基于日志消息的log level)分发给handler的指定目标,如文件、标准输出流等。logging模块中主要定义了以下Handlers,其中StreamHandler
和FileHandler
最为常用。
logging.StreamHandler # 日志输出到流,可以是 sys.stderr,sys.stdout 或者文件。
logging.FileHandler # 日志输出到文件。
logging.handlers.BaseRotatingHandler # 基本的日志回滚方式。
logging.handlers.RotatingHandler # 日志回滚方式,支持日志文件最大数量和日志文件回滚。
logging.handlers.TimeRotatingHandler # 日志回滚方式,在一定时间区域内回滚日志文件。
logging.handlers.SocketHandler # 远程输出日志到TCP/IP sockets。
logging.handlers.DatagramHandler # 远程输出日志到UDP sockets。
logging.handlers.SMTPHandler # 远程输出日志到邮件地址。
logging.handlers.SysLogHandler # 日志输出到syslog。
logging.handlers.NTEventLogHandler # 远程输出日志到Windows NT/2000/XP的事件日志。
logging.handlers.MemoryHandler # 日志输出到内存中的指定buffer。
logging.handlers.HTTPHandler # 通过”GET”或者”POST”远程输出到HTTP服务器。
logging.NullHandler
以FileHandler
为例,创建Handler对象并设置log Level
>>> handler = logging.FileHandler('result.log')
>>> handler.setLevel(logging.DEBUG)
>>> handler.setFormatter(fmt) # fmt是一个Formatter对象,下面再讲# Filter
addFilter(filter)
removeFilter(filter)
Filters
A filter is used to provide additional control over which log records are passed from logger to handler. Filters can be installed on loggers or on handlers; multiple filters can be used in a chain to perform multiple filtering actions.
logging
标准库只提供了一个base Filter,其构造函数__init__
接收name
参数,默认的行为是名为name
的logger极其子logger的消息能通过过滤,其余皆会被滤掉。当然我们也可以根据具体的业务逻辑自定义Filter,并且也非常简单,只需要继承logging.Filter
类重写filter
方法即可,filter
方法接收record
对象作为参数,返回True
代表通过过滤,返回False
表示该record
被过滤。
import logginglogger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
handler.set_name('output-log')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s',datefmt='%Y/%m/%d %H:%M:%S',style='%')class MyFilter(logging.Filter):def filter(self, record):if 'result' in record.msg:return Falsereturn Truehandler.setFormatter(formatter)
handler.addFilter(MyFilter('aa'))
logger.addHandler(handler)try:result = 10 / 0
except:logger.error('Faild to get result')logger.error('Faild')# 输出: 2019/07/13 20:28:51 - __main__ - ERROR - Faild
从python3.2起,自定义filter也可以不继承logging.Filter
,只需要定义一个函数并同样绑定即可:
def _filter(record):if 'result' in record.msg:return Falsereturn Truehandler.addFilter(_filter)
Formatters
Formatter
用来规定Log record文本的格式,其使用python formatting string来规定具体格式。在默认情况下,logging模块的输出格式如下:
import logging
logging.warning('%s before you %s', 'Look', 'leap!')# WARNING:root:Look before you leap!
但是,默认的输出格式不一定能满足我们的需求,我们可以通过Formatter
自定义输出格式,在日志中添加更多丰富的信息,一些常见的项(实际上以下都是Log Record的属性)如下所示:
%(levelno)s:打印日志级别的数值。%(levelname)s:打印日志级别的名称。%(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0]。%(filename)s:打印当前执行程序名。%(funcName)s:打印日志的当前函数。%(lineno)d:打印日志的当前行号。%(asctime)s:打印日志的时间。%(thread)d:打印线程ID。%(threadName)s:打印线程名称。%(process)d:打印进程ID。%(processName)s:打印线程名称。%(module)s:打印模块名称。%(message)s:打印日志信息。
设置Formatter
主要包括两种方式,一种是通过Formatter
类构建Formatter实例
,并将其绑定到特定的handler
上;一种是通过logging.basicConfig
设置:
import loging
import time# 1
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(funcName)s - %(message)s',datefmt='%Y/%m/%d %H:%M:%S',style='%' # '%', ‘{‘ or ‘$’)
# 2
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s',datefmt='%Y/%m/%d %H:%M:%S',style='%')
formatter.converter = time.localtime()
formatter.converter = time.gmtime()
datefmt
的设置,请参考time.strftime()
logging config
basicConfig
使用logging模块的接口函数basicConfig
可以非常方便的进行基本的配置。其中需要注意两点
- 该函数
stream
、filename
以及handlers
这三个参数是互斥的。 logging.basicConfig
函数是一个one-off simple configuration facility,只有第一次调用会有效,并且它的调用应该在任何日志记录事件之前。
import logginglogging.basicConfig(filename='./log.log',filemode='w',format='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(funcName)s - %(message)s',datefmt='%Y/%m/%d %H:%M:%S',style='%',level=logging.DEBUG)
logger = logging.getLogger(__name__)try:result = 10 / 0
except:logger.error('Faild to get result', exc_info=True)logger.log(50, 'logging critical test')
stepByStepConfig
这种设计方式条理清晰,但是会但麻烦一点点,配置的逻辑就是:一个logger可以有多个handler;每个handler可以有一个Formatter;handler和logger都需要设置一个log Level;根据需要logger和handler都可以添加多个Filter。
import logginglogger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)handler = logging.FileHandler('output.log', mode='a', encoding=None, delay=False)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s',datefmt='%Y/%m/%d %H:%M:%S',style='%')
handler.setFormatter(formatter)
handler.setLevel(logging.DEBUG)
handler.set_name('output-log')
logger.addHandler(handler)
try:result = 10 / 0
except:logger.error('Faild to get result', exc_info=True)
fileConfig
通过配置文件来配置logging模块,这是web应用中比较常见的一种设置方式
import logging
import logging.configlogging.config.fileConfig('logging.conf', disable_existing_loggers=True)# create logger
logger = logging.getLogger('simpleExample')# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
[loggers]
keys=root,simpleExample[handlers]
keys=consoleHandler[formatters]
keys=simpleFormatter[logger_root]
level=DEBUG # can be one of DEBUG, INFO, WARNING, ERROR, CRITICAL or NOTSET
handlers=consoleHandler[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=
fileConfig
文件的解读主要基于configparser
,它必须包含 [loggers]
, [handlers]
和 [formatters]
section。这是一个比较老的API,不支持配置Filter,估计后面也不会更新了,所以建议大家使用dictConfig
。
dictConfig
通过dictConfig
配置即可以通过python代码构建一个dict
对象,也可以通过yaml、JSON文件来进行配置。字典中必须传入的参数是version
,而且目前有效的值也只有1
。其他可选的参数包括以下:
formatters
对于formatter,主要搜寻
format
和datefmt
参数,用来构造Formatter实例。filters
对于filter,主要搜寻
name
参数(默认为空字符串),用来构造Formatter实例。handlers
对于handlers,主要包括以下参数,其他的参数将作为关键字参数传递到handlers的构造函数:
class
(mandatory). This is the fully qualified name of the handler class.level
(optional). The level of the handler.formatter
(optional). The id of the formatter for this handler.filters
(optional). A list of ids of the filters for this handler.
handlers:console:class : logging.StreamHandlerformatter: brieflevel : INFOfilters: [allow_foo]stream : ext://sys.stdoutfile:class : logging.handlers.RotatingFileHandlerformatter: precisefilename: logconfig.logmaxBytes: 1024backupCount: 3
loggers
对于loggers,主要包括以下参数:
level
(optional). The level of the logger.propagate
(optional). The propagation setting of the logger.filters
(optional). A list of ids of the filters for this logger.handlers
(optional). A list of ids of the handlers for this logger.
root
这是给root logger的配置项
incremental
是否将此配置文件解释为现有配置的增量, 默认为
False
disable_existing_loggers
是否要禁用现有的非 root logger,默认为True
以下给出一个较为完成的YAML示例,注意体会loggers, handlers, formatters, filters之间的关联性,该配置文件定义了brief和simple两种formatter;定义了console、file、error三个handler,其中console使用brief formatter,file和error使用simple formatter;main.core logger使用file和error handler,root logger使用console handler:
version: 1
formatters:brief:format: "%(asctime)s - %(message)s"simple:format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
handlers:console:class : logging.StreamHandlerformatter: brieflevel : INFOstream : ext://sys.stdoutfile:class : logging.FileHandlerformatter: simplelevel: DEBUGfilename: debug.logerror:class: logging.handlers.RotatingFileHandlerlevel: ERRORformatter: simplefilename: error.logmaxBytes: 10485760backupCount: 20encoding: utf8
loggers:main.core:level: DEBUGhandlers: [file, error]
root:level: DEBUGhandlers: [console]
将上面的configuration setup可以通过以下方法:
import logging
import yaml
import logging.config
import osdef setup_logging(default_path='config.yaml', default_level=logging.INFO):if os.path.exists(default_path):with open(default_path, 'r', encoding='utf-8') as f:config = yaml.load(f)logging.config.dictConfig(config)else:logging.basicConfig(level=default_level)
dictConfig
通过将dict数据传递给dictConfigClass
,然后返回对象调用configure函数使配置生效。
def dictConfig(config):"""Configure logging using a dictionary."""dictConfigClass(config).configure()
User-defined objects
logging模块 dictConfig
为了支持handlers,filters和formatters的用户自定义对象,可以通过助记符()
指定一个工厂函数来实例化自定义对象,下面定义的 custom formatter相当于my.package.customFormatterFactory(bar='baz', spam=99.9, answer=42)
formatters:brief:format: '%(message)s'default:format: '%(asctime)s %(levelname)-8s %(name)-15s %(message)s'datefmt: '%Y-%m-%d %H:%M:%S'custom:(): my.package.customFormatterFactorybar: bazspam: 99.9answer: 42
Access to external objects
logging模块 dictConfig
为了支持链接外部objects,如sys.stdout
,可以使用ext://sys.stderr
。内部原理是进行正则匹配^(?P<prefix>[a-z]+)://(?P<suffix>.*)$
,如果prefix
有意义,则会按照prefix预定义的方式处理suffix
;否则保持字符串原样。
Access to internal objects
logging模块 dictConfig
为了支持链接配置文件内部objects,首先,比如logger或则handler中的level值设置DEBUG,配置系统会自动地将其转换成logging.DEBUG;但是,对于logging模块不知道的用户定义对象,需要一种更通用的机制来完成,比如我自定义一个handler,这个handler又需要另一个handler来代理,那么可以这样定义:
handlers:file:# configuration of file handler goes herecustom:(): my.package.MyHandleralternate: cfg://handlers.file
举一个更复杂的例子,如下面文件所定义YAML配置文件,则cfg://handlers.email.toaddrs[0]
会被解析到其值为support_team@domain.tld
;subject
的值可以通过cfg://handlers.email.subject
或者cfg://handlers.email[subject]
拿到。
handlers:email:class: logging.handlers.SMTPHandlermailhost: localhostfromaddr: my_app@domain.tldtoaddrs:- support_team@domain.tld- dev_team@domain.tldsubject: Houston, we have a problem.
Optimization
当你不想收集以下信息时,你可以对你的日志记录系统进行一定的优化:
你不想收集的内容 | 如何避免收集它 |
---|---|
有关调用来源的信息 |
将 logging._srcfile 设置为 None 。这避免了调用 sys._getframe() ,如果 PyPy 支持 Python 3.x ,这可能有助于加速 PyPy (无法加速使用了sys._getframe() 的代码)等环境中的代码.
|
线程信息 |
将 logging.logThreads 置为 0 。
|
进程信息 |
将 logging.logProcesses 置为 0 。
|
另外,核心的logging模块只包含基本的handlers,如果你不显式导入 logging.handlers
和 logging.config
,它们将不会占用任何内存。
实例运用
logging
模块的落脚点当然是实际项目中的运用,比如对于简单的程序可以参考以下的使用方式,先在一个模块中创建并定义好root logger,在其他模块中调用get_logger
函数创建其子logger。
import logginglogger = logging.getLogger('logger')
logger.propagate = False # wii not pass log messages on to logging.root and its handler
logger.setLevel('INFO')
logger.addHandler(logging.StreamHandler()) # Logs go to stderr
logger.handlers[-1].setFormatter(logging.Formatter('%(message)s'))
logger.handlers[-1].setLevel('INFO')def get_logger(name):"""Creates a child logger that delegates to anndata_logger instead to logging.root"""return logger.manager.getLogger(name)
对于更加复杂的场景,用户也可以重载logging模块提供的logger类logging.Logger
或者logging.RootLogger
的自定义logging Level,实现更加灵活的日志记录功能。比如,以下定义一个新的HINT
log Level,通过继承重写logging.RootLogger
获得自定义的Logger类。
import loggingHINT = (INFO + DEBUG) // 2
logging.addLevelName(HINT, 'HINT')class RootLogger(logging.RootLogger):def __init__(self, level):super().__init__(level)self.propagate = FalseRootLogger.manager = logging.Manager(self)def log(self,level: int,msg: str,*,extra: Optional[dict] = None,time: datetime = None,deep: Optional[str] = None,) -> datetime:now = datetime.now(timezone.utc)time_passed: timedelta = None if time is None else now - timeextra = {**(extra or {}),'deep': deep,'time_passed': time_passed}super().log(level, msg, extra=extra)return nowdef critical(self, msg, *, time=None, deep=None, extra=None) -> datetime:return self.log(CRITICAL, msg, time=time, deep=deep, extra=extra)def error(self, msg, *, time=None, deep=None, extra=None) -> datetime:return self.log(ERROR, msg, time=time, deep=deep, extra=extra)def warning(self, msg, *, time=None, deep=None, extra=None) -> datetime:return self.log(WARNING, msg, time=time, deep=deep, extra=extra)def info(self, msg, *, time=None, deep=None, extra=None) -> datetime:return self.log(INFO, msg, time=time, deep=deep, extra=extra)def hint(self, msg, *, time=None, deep=None, extra=None) -> datetime:return self.log(HINT, msg, time=time, deep=deep, extra=extra)def debug(self, msg, *, time=None, deep=None, extra=None) -> datetime:return self.log(DEBUG, msg, time=time, deep=deep, extra=extra)
写在篇后
本篇博客基本上涵盖了python logging模块大部分的功能,但是也有一些尚未cover。比如logging模块会默认吞噬除了SystemExit
和KeyboardInterrupt
的一切异常,因为logging.raiseExceptions
默认为True
(生产环境也推荐设置为True);logging.captureWarnings(capture=True)
会重定向warning信息到logging模块;另外,可以通过logging.setLoggerClass()
决定初始化logger的类型,与之对应的有logging.getLoggerClass()
等等,更多的用法在实践中再慢慢总结经验,吸取教训。
python Logging日志记录模块详解相关推荐
- Python之ruamel.yaml模块详解(三)| ruamel.yaml与pyyaml的区别
Python之ruamel.yaml模块详解(三)| ruamel.yaml与pyyaml的区别 12 默认支持Yaml1.2 13 Py2和Py3重新整合 14 修复 15 测试 16 API 接前 ...
- python中 xlrd/xlwt模块详解
python中 xlrd/xlwt模块详解 1.什么是xlrd模块 python操作excel主要用到xlrd和xlwt两个库,即xlrd是读excel,xlwt是写excel库 一.安装xlrd模块 ...
- python之sys模块详解_(转)python之os,sys模块详解
python之sys模块详解 原文:http://www.cnblogs.com/cherishry/p/5725184.html sys模块功能多,我们这里介绍一些比较实用的功能,相信你会喜欢的,和 ...
- 操作系统服务:logging日志记录模块
许多应用程序中都会有日志模块,用于记录系统在运行过程中的一些关键信息,以便于对系统的运行状况进行跟踪. 在.NET平台中,有非常著名的第三方开源日志组件log4net,c++中,有人们熟悉的log4c ...
- python中的logging记录日志_[ Python入门教程 ] Python中日志记录模块logging使用实例...
python中的logging模块用于记录日志.用户可以根据程序实现需要自定义日志输出位置.日志级别以及日志格式. 将日志内容输出到屏幕 一个最简单的logging模块使用样例,直接打印显示日志内容到 ...
- python supervisor 日志_Supervisor使用详解
supervisor配置文件 ; Sample supervisor config file. [unix_http_server] file=/var/run/supervisor/supervis ...
- (4.7)mysql备份还原——深入解析二进制日志(3)binlog的三种日志记录模式详解...
关键词:binlog模式,binlog,二进制日志,binlog日志 目录概述 0.binlog概述 查看binlog日志参数设置: show variables like '%log_bin%'; ...
- 刻意练习:Python基础 -- Task13. datetime模块详解
datetime模块 datetime 是 Python 中处理日期的标准模块,它提供了 4 种对日期和时间进行处理的类:datetime.date.time 和 timedelta. datetim ...
- Python 正则表达式使用--Re 模块详解
目录 1. 常用正则表达式符号和特殊字符 ?2.? Re 模块:核心函数和方法 2.1 re.compile() 编译函数 2.1.1?re.compile() 语法格式 2.1.2?re.compi ...
最新文章
- 电子书格式怎么在线转换为PDF格式
- 自动驾驶规划方法综述
- python中没有switch-case_Python为什么没有switch/case语句?
- java序列化和反序列化_Java恶意序列化背后的历史和动机
- envoy api 网关_为Envoy构建控制平面的指南-特定于域的配置API
- php.ini settimelimit,PHP-set_time_limit()和ini_set('max_execution_time',...)之间的区别...
- 计算机考研985院校不歧视,考研最不歧视的985大学有哪些
- nebula模拟器_nebula模拟器中文金手指版本
- python deepcopy_轻轻吐槽下python的deepcopy居然一点不deep
- 五,通道之间的数据传输
- php mysql 获取排名,Mysql排序获取排名的实例代码
- Linux内存buffer与cache区别
- JavaScript学习初步
- nike tiempo ylak raoh fmtp
- Android小白从零开始学Android开发的要点总结(内含福利)
- day09、1 - 简单渗透测试流程
- 英语单词音节拆分程序
- cmd看excel有多少个子表_如何将一个 Excel 工作簿中的多个工作表合并成一个工作表?...
- android矢量地图画法_Android 百度地图,手绘图形
- 解决Visio另存为(或者导出)pdf字符间距变化/不均等字母间距的问题
热门文章
- 记一次 @Transactional不生效的问题
- JavaScript的运动——模拟重力场
- 【简洁+注释】剑指 Offer 32 - II. 从上到下打印二叉树 II
- QT Creator应用程序开发——01简单按钮显示
- Failed to start SYSV: HA-Proxy is a TCP/HTTP reverse proxy which is particularl
- 传统公司部署OpenStack(t版)简易介绍(三)——Glance组件部署
- 城市地区级联二级下拉选择菜单js特效
- c语言简单的模拟坐标,C语言模拟实现简单扫雷游戏
- 怎么损坏mysql_如何修复MySQL中损坏的表
- java构造器调用构造器_java中构造器内部调用构造器实例详解