1 """!Configures logging.
3 This module configures logging for stdout, stderr and the jlogfile.
4 It also contains the jlogger, a logger.Logger object that is used to
5 log directly to the jlogfile, and jlogdomain: a string name of the
6 logger domain for the jlogfile."""
10 __all__ = [
'configureLogging',
'jlogger',
'jlogdomain',
'postmsg',
11 'MasterLogFormatter',
'JLogFormatter',
'stdout_is_stderr',
12 'MasterLogHandler',
'JLogHandler',
'set_jlogfile' ]
14 import logging, os, sys, traceback, threading
27 jlogger=logging.getLogger(jlogdomain)
34 """!Custom logging.Logger that inserts thread information."""
36 """!Replaces the logging.Logger.makeRecord() with a new
37 implementation that inserts thread information from
38 threading.current_thread()
39 @param name,lvl,fn,lno,msg,args,kwargs Log message information.
40 See the Python logging module documentation for details."""
41 ct=threading.current_thread()
42 msg=
'[%s] %s'%(str(ct.name),str(msg))
43 x=logging.Logger.makeRecord(self,name,lvl,fn,lno,msg,*args,**kwargs)
47 """!Sends the message to the jlogfile logging stream at level INFO.
51 jlogger.info(message).
53 @param message the message to log."""
54 return jlogger.info(message)
57 """!Tells the jlogger to log to the specified file instead of the
58 current jlogfile. Also updates the jlogfile environment variable.
59 The argument must be a filename.
60 @param filename the new jlogfile"""
61 jloghandler.set_jlogfile(filename)
62 os.environ[
'jlogfile']=filename
65 """!This is a custom log formatter that inserts the thread or
66 process (logthread) that generated the log message. Also, it
67 always directly calls formatException from format, ensuring that
68 cached information is not used. That allows a subclass
69 (JLogFormatter) to ignore exceptions."""
70 def __init__(self,fmt=None,datefmt=None,logthread=None):
71 """!MasterLogFormatter constructor
72 @param fmt the log message format
73 @param datefmt the date format
74 @param logthread the thread name for logging
75 @note See the Python logging module documentation for details."""
76 logging.Formatter.__init__(self,fmt=fmt,datefmt=datefmt)
80 """!The name of the batch thread or process that generated log
81 messages, if the LogRecord does not supply that already."""
86 """!Replaces the logging.Formatter.format() function.
88 We need to override this due to a "feature" in the
89 Formatter.format: It ignores formatException (never calls it)
90 and caches the exception info, even if the formatter is not
91 supposed to output it.
92 @param record the log record to format
93 @note See the Python logging module documentation for details."""
95 record.message = record.getMessage()
96 if self._fmt.find(
"%(asctime)") >= 0:
97 record.asctime = self.formatTime(record, self.datefmt)
98 if 'logthread' not in record.__dict__:
99 record.__dict__[
'logthread']=self.
logthread
100 s = self._fmt % record.__dict__
101 if 'exc_info' in record.__dict__
and record.exc_info
is not None:
102 e = self.formatException(record.exc_info)
104 rec2=dict(record.__dict__)
105 for line
in str(e).splitlines():
107 s=
"%s\n%s"%( s, self._fmt % rec2 )
111 """!This subclass of MasterLogFormatter does not include exception
112 information in the log file. This is done to prevent cluttering
115 """!Returns nothing to indicate no exception information should
117 @param ei the exception information to ignore"""
120 """!Returns True if it can determine that stdout and stderr are the
121 same file or terminal. Returns False if it can determine they are
122 not, or if the result is inconclusive."""
124 if os.fstat(sys.stdout.fileno()) == os.fstat(sys.stderr.fileno()):
126 if sys.stdout.isatty()
and sys.stderr.isatty():
128 except Exception
as e:
133 """!Custom LogHandler for the master process of a multi-process job.
135 This is a custom logging Handler class used for multi-process or
136 multi-job batch scripts. It has a higher minimum log level for
137 messages not sent to the jlogfile domain. Also, for every log
138 message, the log file is opened, the message is written and the
139 file is closed. This is done to mimic the postmsg command.
140 Exception information is never sent to the log file."""
141 def __init__(self,logger,jlogdomain,otherlevels,joformat,jformat):
142 """!MasterLogHandler constructor
143 @param logger The logging.Logger for the master process.
144 @param jlogdomain The logging domain for the jlogfile.
145 @param otherlevels Log level for any extrema to go to the jlogfile.
146 @param joformat Log format for other streams.
147 @param jformat Log format for the jlogfile stream."""
148 logging.Handler.__init__(self)
155 """!Convert a log record to a string.
156 @note See the Python logging module documentation for details.
157 @returns a string message to print"""
158 assert(isinstance(self.
_joformat,MasterLogFormatter))
159 assert(isinstance(self.
_jformat,MasterLogFormatter))
163 message=self._jformat.format(record)
167 message=self._joformat.format(record)
171 """!Write a log message.
172 @param record the log record
173 @note See the Python logging module documentation for details."""
175 self._logger.write(message)
178 """!Custom LogHandler for the jlogfile.
180 This is a custom logging Handler class for the jlogfile. It has a
181 higher minimum log level for messages not sent to the jlogfile
182 domain. Also, for every log message, the log file is opened, the
183 message is written and the file is closed. This is done to mimic
184 the postmsg command. Exception information is never sent to the
187 """!Write a log message.
188 @param record the log record
189 @note See the Python logging module documentation for details."""
191 if message
is None:
return
192 if isinstance(self.
_logger,basestring):
194 dirn=os.path.dirname(self.
_logger)
195 if not os.path.isdir(dirn):
202 except EnvironmentError
as e:
203 if os.path.isdir(dirn):
205 elif os.path.exists(dirn):
210 with open(self.
_logger,
'at')
as f:
213 self._logger.write(message)
215 """!Set the location of the jlogfile
216 @param filename The path to the jlogfile."""
217 if not isinstance(filename,basestring):
219 'In JLogHandler.set_jlogfile, the filename must be a '
220 'string. You passed a %s %s.'
221 %(type(filename).__name__,repr(filename)))
225 masterlevel=logging.WARNING,
226 openmode=
None,logger=
None):
227 """!Used to split to multiple logging streams.
229 When the Python script splits itself into multiple processes via
230 MPI, this function is called to redirect stdout to stdoutfile,
231 stderr to stderrfile, and produce a new logging stream to the
232 original stderr, with a logging level set to masterlevel. That
233 new logging stream is called the "master log" and will receive any
234 messages at level masterlevel or higher, and any messages sent to
237 This can also be used to redirect ONLY stdout, in which case no
238 master logging stream is set up. That is requested by
240 @param threadname the name of this process for logging purposes
241 @param stderrfile file to receive stderr
242 @param stdoutfile file to receive stdout
243 @param masterlevel log level to send to master log stream
244 @param openmode integer mode to use when opening files
245 @param logger a logging.Logger for logging errors while splitting
247 if logger
is None: logger=logging.getLogger(
'produtil')
249 openmode=os.O_CREAT|os.O_WRONLY|os.O_APPEND
250 elif not isinstance(openmode,int):
252 "In mpiRedirect, the openmode must be an int, not a "
253 +type(openmode).__name__)
254 if not isinstance(stdoutfile,basestring):
256 "In mpiRedirect, the stdoutfile must be a string, not a "
257 +type(stdoutfile).__name__)
258 if stderrfile
is not None and not isinstance(stderrfile,basestring):
260 "In mpiRedirect, the stderrfile must be a string or None, not a "
261 +type(stderrfile).__name__)
262 if not isinstance(threadname,basestring):
264 "In mpiRedirect, the threadname must be a string, not a "
268 logthread=
'['+str(threadname)+
']'
270 logger.warning(
'Redirecting stdout to "%s" for thread %s'
271 %(stdoutfile,logthread))
272 fd=os.open(stdoutfile,openmode)
275 if stderrfile
is not None:
276 logger.warning(
'Redirecting stderr to "%s" for thread %s'
277 %(stderrfile,logthread))
280 olderr=os.fdopen(olderrfd,
'at',0)
282 if(stdoutfile!=stderrfile):
284 fd=os.open(str(stderrfile),openmode)
288 "%(asctime)s.%(msecs)03d %(name)s %(logthread)s (%(filename)s:"
289 "%(lineno)d) %(levelname)s: %(message)s",
299 logger.warning(
'Turning on logging of high priority messages to '
300 'original stderr stream.')
301 if masterlevel!=logging.NOTSET: mloghandler.setLevel(masterlevel)
302 logging.getLogger().addHandler(mloghandler)
306 jloglevel=logging.INFO,
307 japplevel=logging.ERROR,
308 eloglevel=logging.WARNING,
309 ologlevel=logging.NOTSET,
310 thread_logger=
False):
311 """!Configures log output to stderr, stdout and the jlogfile
313 Configures log file locations and logging levels for all streams.
315 @note Important notes when choosing levels:
316 * level - sets the global minimum log level. Anything below this
317 level will be discarded regardless of other settings.
318 * jloglevel - this limit is applied before japplevel
320 @param jlogfile path to the jlogfile. Default: use
321 os.environ('jlogfile') if set. Otherwise, stderr.
322 @param level minimum logging level globally. Set to INFO by default.
323 Change this to logging.DEBUG if you're debugging the program.
324 @param jloglevel minimum logging level to send to jlogfile
325 @param japplevel minimum logging level to send to jlogfile from all
326 domains except that specified in jlogdomain. Be careful
327 when changing this as it logs directly to the WCOSS-wide
328 jlogfile in operations.
329 @param eloglevel minimum logging level to send to stderr from ALL logs
330 Set to None to disable stderr logging
331 @param ologlevel minimum logging level to send to stdout from ALL logs
332 Default: logging.NOTSET (no filtering)
333 Set to None to disable stdout logging.
334 @param thread_logger True to include the thread name in log messages."""
341 root=logging.getLogger()
342 if level!=logging.NOTSET:
346 jlog=logging.getLogger(
'jlogfile')
347 jobstr=os.environ.get(
'job',
None)
350 jobstr=str(jobstr).replace(
'(',
'_').replace(
')',
'_').replace(
'%',
'_')
353 "%(asctime)sZ "+jobstr+
"-%(levelname)s: %(logthread)s %(message)s",
358 "%(asctime)sZ "+jobstr+
359 "-%(name)s: %(levelname)s: %(logthread)s %(message)s",
363 oformat=logging.Formatter(
364 "%(asctime)s.%(msecs)03d %(name)s (%(filename)s:%(lineno)d) "
365 "%(levelname)s: %(message)s",
370 loglevel=min(ologlevel,eloglevel)
371 logstream=logging.StreamHandler(sys.stderr)
372 logstream.setFormatter(oformat)
373 if loglevel!=logging.NOTSET: logstream.setLevel(loglevel)
374 root.addHandler(logstream)
377 if ologlevel
is not None:
378 ologstream=logging.StreamHandler(sys.stdout)
379 ologstream.setFormatter(oformat)
380 if ologlevel!=logging.NOTSET: ologstream.setLevel(ologlevel)
381 root.addHandler(ologstream)
384 if eloglevel
is not None:
385 elogstream=logging.StreamHandler(sys.stderr)
386 elogstream.setFormatter(oformat)
387 if eloglevel!=logging.NOTSET: elogstream.setLevel(eloglevel)
388 root.addHandler(elogstream)
395 var=str(os.environ.get(
'jlogfile',
''))
396 if len(var)>0: jlogfile=var
398 jlogfile=str(jlogfile)
if jlogfile
is not None else sys.stderr
399 jloghandler=
JLogHandler(jlogfile,jlogdomain,japplevel,joformat,jformat)
400 if jloglevel!=logging.NOTSET: jloghandler.setLevel(jloglevel)
402 root.addHandler(jloghandler)
def set_jlogfile(self, filename)
Set the location of the jlogfile.
def set_jlogfile(filename)
Tells the jlogger to log to the specified file instead of the current jlogfile.
def stringify_record(self, record)
Convert a log record to a string.
def __init__(self, logger, jlogdomain, otherlevels, joformat, jformat)
Custom LogHandler for the master process of a multi-process job.
def postmsg(message)
Sends the message to the jlogfile logging stream at level INFO.
def configureLogging
Configures log output to stderr, stdout and the jlogfile.
def stdout_is_stderr()
Returns True if it can determine that stdout and stderr are the same file or terminal.
def mpi_redirect
Used to split to multiple logging streams.
def emit(self, record)
Write a log message.
Provides information about the batch system.
def makeRecord(self, name, lvl, fn, lno, msg, args, kwargs)
Replaces the logging.Logger.makeRecord() with a new implementation that inserts thread information fr...
Custom LogHandler for the master process of a multi-process job.
def jobname
Get the batch job name.
Custom LogHandler for the jlogfile.
def emit(self, record)
Write a log message.
Custom logging.Logger that inserts thread information.