Suppose you want to develop an automated scripting tool, the engineering structure is as follows,
Common
This one
package
is the implementation of the framework function,
Scripts
The catalog is a test case script that we write (please ignore other unrelated directories).
Our requirements for the logging feature are as follows:
1 in order to facilitate the view of the log, each script corresponds to a log file, the log file is named after the name of the script
2nd log path and the journal capacity saved per script can be set, such as set to 5MB, then the oldest log is automatically overwritten
3rd log function to be easy to use, reduce the coupling with the framework business functions
Now, let's analyze each of these requirements individually.
1 To implement a log file for each script , you need to generate a log file in the log module, based on the name of the use case script, the key question here is how to get the name of the use case script in the log module.
A common way to get filenames is os.getcwd()
to sys.argv[0], __file__,
look at the various effects:
First write the following code in a file (suppose test.py
):
Then in another file (assumed script1.py
) import test
, then call the func
method:
Run script1.py
, the result is:
Visible is the os.getcwd()
directory that executes the script, the sys.argv[0]
absolute path name of the execution script, and the __file__
absolute pathname of the file where the code is executed.
Now it's clear that we should use it sys.argv[0]
to get the name of the execution script, because getting an absolute path requires a bit of processing:sys.argv[0].split('/')[-1].split('.')[0]
2nd Log capacity Problem , to achieve more than capacity to automatically overwrite the oldest logs, logging
in the RotatingFileHandler
class can be set to the size of the log file, as well as the number of backups.
So where does the log path and capacity configuration fit? Let the user directly modify RotatingFileHandler
the parameters obviously not good, it is best not to let the user modify the framework file, the user simply call the interface to write their own script.
The scenario used here is to write the configuration information to a file that the XML file is more appropriate to use as a configuration file, the user modifies the XML file to make the configuration, and the log module reads the parameters from the XML file.
Here for the convenience of putting the XML file Common
below, named config.xml
, the content is:
<?xml version= "1.0" encoding= "Utf-8"?><config> <!--log save path-- <logpath>e:\ Pythonlog</logpath> <!--the log file size for each script, in megabytes--- <logsize>8</logsize> <!-- Number of log files saved per script- <lognum>3</lognum></config>
To read the contents of an XML file, it lxml
is very simple to use the library, followed by the code.
3rd log function to be easy to use , reduce the coupling with the framework business functions, it is best to encapsulate the logging function, only provide logging interface.
Log interface in the form of a class method can meet the above requirements, the user only need to call through the class logging interface, anywhere call, easy to use, and no need to define class instances, and framework business is not coupled.
With the above analysis, we will implement the log module.
Because the log function is also part of the Framework Foundation, we put the log module in Common
this package
, Common
under the new log.py
file, the code is as follows:
# coding:utf-8from lxml import etreeimport logging.handlersimport loggingimport osimport sys# provides logging function class logger: # Read X First The configuration data in the ML file # because CONFIG. xml is placed in the same directory as the current file, so __file__ to get the directory of the file and then stitching it into an absolute path # Here we use the lxml library to parse the XML root = Etree.parse (os.p Ath.join (Os.path.dirname (__file__), ' config. * '). Getroot () # Read the log file save path LogPath = Root.find (' LogPath '). Text # Read log file Capacity, converted to bytes logsize = 1024*1024*int (Root.find (' logsize '). Text) # read log file save number lognum = Int (root.find (' Lognum '). Text) # log file Name: The absolute path of the log file is obtained by the names of the use case script, combined with the log save path logname = Os.path.join (LogPath, Sys.argv[0].split ('/') [ -1].split ('. ') [0]) # Initialize Logger log = Logging.getlogger () # Log format, you can set FMT = logging as needed. Formatter (' [% (asctime) s][% (filename) s][line:% (Lineno) d][% (levelname) s]% (message) s ', '%y-%m-%d%h:%m:%s ') # Log output to file , here used to get the above log name, size, save number Handle1 = Logging.handlers.RotatingFileHandler (logname, Maxbytes=logsize, backupcount= Lognum) Handle1.setformatter (FMT) # Output to the screen at the same time for easy implementation observation Handle2 = logging. Streamhandler (stream=sys.stdout) HAndle2.setformatter (FMT) Log.addhandler (handle1) Log.addhandler (handle2) # Set the log basic, here is set to info, indicating that only the info level and above will print Log.setlevel (logging.info) # Log interface, the user only need to call the interface here, here only the information, WARNING, error three levels of log, you can define more interfaces as needed @classmethod def INFO ( CLS, msg): Cls.log.info (msg) return @classmethod def warning (CLS, msg): Cls.log.warning (msg) return @classm Ethod def error (CLS, msg): Cls.log.error (msg) return
To test it, write the script1
script2
following code separately in the script and:
From Common.log import *logger.info ("This is Info") logger.warning (' This was warning ') Logger.error (' This is the error ')
Run two scripts separately, console output is:
The resulting log file:
File contents: