@@ -1,15 +1,28 @@
# -*- coding: utf-8 -*-
"""Provides managed logging."""
from static import LOGLEVEL
from static import LOGLEVEL , LOGMASK , LOGFACILITY
from exceptions import MythError
from sys import version_info , stdout
import os
import syslog
from sys import version_info , stdout , argv
from datetime import datetime
from thread import allocate_lock
from cStringIO import StringIO
from traceback import format_exc
class MythLog ( LOGLEVEL ):
def _donothing (* args , ** kwargs ):
pass
class DummyLogger ( LOGLEVEL , LOGMASK , LOGFACILITY ):
def __init__ (self , module = None , db = None ): pass
def logTB (self , mask ): pass
def log (self , mask , level , message , detail = None ): pass
def __call__ (self , mask , level , message , detail = None ): pass
class MythLog ( LOGLEVEL , LOGMASK , LOGFACILITY ):
"""
MythLog(module='pythonbindings', lstr=None, lbit=None, \
db=None) -> logging object
Expand All
@@ -24,10 +37,6 @@ class MythLog( LOGLEVEL ):
The logging object is callable, and implements the MythLog.log() method.
"""
LEVEL = LOGLEVEL .IMPORTANT | LOGLEVEL .GENERAL
LOGFILE = stdout
_lock = allocate_lock ()
helptext = """Verbose debug levels.
Accepts any combination (separated by comma) of:
Expand Down
Expand Up
@@ -78,136 +87,242 @@ class MythLog( LOGLEVEL ):
Some debug levels may not apply to this program.
"""
@classmethod
def _initlogger (cls ):
cls ._initlogger = classmethod (_donothing )
cls ._MASK = LOGMASK .GENERAL
cls ._LEVEL = LOGLEVEL .INFO
cls ._LOGFILE = stdout
cls ._logwrite = cls ._logfile
cls ._QUIET = 0
cls ._DBLOG = True
cls ._SYSLOG = None
cls ._lock = allocate_lock ()
args = iter (argv )
args .next ()
try :
while True :
arg = args .next ()
if arg == '--quiet' :
cls ._QUIET += 1
elif arg == '--nodblog' :
cls ._DBLOG = False
elif arg == '--loglevel' :
cls ._setlevel (args .next ())
elif arg == '--verbose' :
cls ._setmask (args .next ())
elif arg == '--logfile' :
cls ._setfile (args .next ())
elif arg == '--logpath' :
cls ._setfile (os .path .join (args .next (), "{0}.{1}.{2}.log" \
.format (argv [0 ].rsplit ('/' , 1 )[1 ],
datetime .now ().strftime ('%Y%m%d%H%M%S' ),
os .getpid ())))
elif arg == '--syslog' :
cls ._setsyslog (args .next ())
elif arg == '--' :
break
except StopIteration :
pass
def __repr__ (self ):
return "<%s '%s','%s' at %s>" % \
(str (self .__class__ ).split ("'" )[1 ].split ("." )[- 1 ],
self .module , bin (self .LEVEL ), hex (id (self )))
self .module , bin (self ._MASK ), hex (id (self )))
def __init__ (self , module = 'pythonbindings' , lstr = None , lbit = None , db = None ):
if lstr or lbit :
self ._setlevel (lstr , lbit )
def __new__ (cls , * args , ** kwargs ):
# abuse the __new__ constructor to set some immutable class attributes
# before the class is instantiated
cls ._initlogger ()
return super (MythLog , cls ).__new__ (cls , * args , ** kwargs )
def __init__ (self , module = 'pythonbindings' , db = None ):
self .module = module
self .db = db
@classmethod
def _setlevel (cls , lstr = None , lbit = None ):
def _setlevel (cls , level ):
cls ._initlogger ()
try :
level = int (level )
cls ._LEVEL = level
except :
if level not in ('any' , 'emerg' , 'alert' , 'crit' , 'err' ,
'warning' , 'info' , 'notice' , 'debug' , 'unknown' ):
return
cls ._LEVEL = getattr (cls , level .upper ())
@classmethod
def _setmask (cls , mask ):
"""Manually set loglevel."""
if lstr :
cls .LEVEL = cls ._parselevel (lstr )
elif lbit :
cls .LEVEL = lbit
cls ._initlogger ()
try :
cls ._MASK = int (mask )
except :
cls ._MASK = cls ._parsemask (mask )
@classmethod
def _setfile (cls , filename ):
"""Redirect log output to a specific file."""
cls ._initlogger ()
cls ._setfileobject (open (filename , 'w' ))
@classmethod
def _setfileobject (cls , fileobject , close = True ):
"""Redirect log output to an opened file pointer."""
if (cls .LOGFILE .fileno () != 1 ) and close :
cls .LOGFILE .close ()
cls .LOGFILE = fileobject
cls ._initlogger ()
if (cls ._LOGFILE .fileno () != 1 ) and close :
cls ._LOGFILE .close ()
cls ._LOGFILE = fileobject
cls ._logwrite = cls ._logfile
if cls ._SYSLOG :
cls ._SYSLOG = None
syslog .closelog ()
@classmethod
def _setsyslog (cls , facility = LOGFACILITY .USER ):
cls ._initlogger ()
try :
facility = int (facility )
for fac in dir (LOGFACILITY ):
if '_' in fac :
continue
if getattr (LOGFACILITY , fac ) == facility :
facility = 'LOG_' + fac
break
else :
raise MythError ("Invalid syslog facility" )
except ValueError :
if not facility .startswith ('LOG_' ):
facility = 'LOG_' + facility .upper ()
if not hasattr (LOGFACILITY , facility [4 :]):
raise MythError ("Invalid syslog facility" )
cls ._SYSLOG = facility
syslog .openlog (argv [0 ].rsplit ('/' , 1 )[1 ],
syslog .LOG_NDELAY | syslog .LOG_PID ,
getattr (syslog , facility ))
cls ._logwrite = cls ._logsyslog
if cls ._LOGFILE :
if cls ._LOGFILE .fileno () != 1 :
cls ._LOGFILE .close ()
cls ._LOGFILE = None
@classmethod
def _parselevel (cls , lstr = None ):
def _parsemask (cls , mstr = None ):
bwlist = ( 'important' ,'general' ,'record' ,'playback' ,'channel' ,'osd' ,
'file' ,'schedule' ,'network' ,'commflag' ,'audio' ,'libav' ,
'jobqueue' ,'siparser' ,'eit' ,'vbi' ,'database' ,'dsmcc' ,
'mheg' ,'upnp' ,'socket' ,'xmltv' ,'dvbcam' ,'media' ,'idle' ,
'channelscan' ,'extra' ,'timestamp' )
if lstr :
level = cls .NONE
for l in lstr .split (',' ):
if mstr :
mask = cls .NONE
for m in mstr .split (',' ):
try :
if l in ('all' ,'most' ,'none' ):
if m in ('all' ,'most' ,'none' ):
# set initial bitfield
level = getattr (cls , l .upper ())
mask = getattr (cls , m .upper ())
elif l in bwlist :
# update bitfield OR
level |= getattr (cls , l .upper ())
elif len (l ) > 2 :
if l [0 :2 ] == 'no' :
if l [2 :] in bwlist :
mask |= getattr (cls , m .upper ())
elif len (m ) > 2 :
if m [0 :2 ] == 'no' :
if m [2 :] in bwlist :
# update bitfield NOT
level &= level ^ getattr (self , l [2 :].upper ())
mask &= mask ^ getattr (cls , m [2 :].upper ())
except AttributeError :
pass
return level
else :
level = []
for l ,v in enumerate (bwlist ):
if cls .LEVEL & 2 ** l :
level .append (v )
return ',' .join (level )
def time (self ): return datetime .now ().strftime ('%Y-%m-%d %H:%M:%S.%f' )[:- 3 ]
def _testLevel (self , level ):
if level & self .EXTRA :
if not self .LEVEL & self .EXTRA :
return False
return self ._testLevel (level & (self .ALL - self .EXTRA ))
return mask
else :
return bool (level & self .LEVEL )
cls ._initlogger ()
mask = []
for m ,v in enumerate (bwlist ):
if cls ._MASK & 2 ** l :
mask .append (v )
return ',' .join (mask )
def logTB (self , level ):
def time (self ): return datetime .now ().strftime ('%Y-%m-%d %H:%M:%S.%f' )
def logTB (self , mask ):
"""
MythLog.logTB(level ) -> None
MythLog.logTB(mask ) -> None
'level ' sets the bitwise log level , to be matched against the log
'mask ' sets the bitwise log mask , to be matched against the log
filter. If any bits match true, the message will be logged.
This will log the latest traceback.
"""
self .log (level , format_exc ())
self .log (mask , self . CRIT , format_exc ())
def log (self , level , message , detail = None ):
def log (self , mask , level , message , detail = None ):
"""
MythLog.log(level , message, detail=None) -> None
MythLog.log(mask , message, detail=None) -> None
'level ' sets the bitwise log level , to be matched against the log
'mask ' sets the bitwise log mask , to be matched against the log
filter. If any bits match true, the message will be logged.
'message' and 'detail' set the log message content using the format:
<timestamp> <module>: <message>
---- or ----
<timestamp> <module>: <message> -- <detail>
"""
if self ._testLevel (level ):
buff = StringIO ()
buff .write ('%s %s: ' % (self .time (), self .module ))
if level > self ._LEVEL :
return
if not mask & self ._MASK :
return
if self ._QUIET > 1 :
return
with self ._lock :
self ._logwrite (mask , level , message , detail )
self ._logdatabase (mask , level , message , detail )
multiline = False
if '\n ' in message :
def _logfile (self , mask , level , message , detail ):
if self ._QUIET and (self ._LOGFILE == stdout ):
return
buff = StringIO ()
buff .write ("{0} {3} [{1}] {2} " \
.format (self .time (), os .getpid (), self .module ,
['!' ,'A' ,'C' ,'E' ,'W' ,'N' ,'I' ,'D' ][level ]))
multiline = False
if '\n ' in message :
multiline = True
elif detail :
if '\n ' in detail :
multiline = True
elif detail :
if '\n ' in detail :
multiline = True
if multiline :
for line in message .split ('\n ' ):
buff .write ('\n %s' % line )
if detail :
for line in detail .split ('\n ' ):
buff .write ('\n %s' % line )
else :
buff .write (message )
if detail :
buff .write (' -- %s' % detail )
buff .write ('\n ' )
with self ._lock :
self .LOGFILE .write (buff .getvalue ())
self .LOGFILE .flush ()
# if (dblevel is not None) and (self.db is not None):
# c = self.db.cursor(self.log)
# c.execute("""INSERT INTO mythlog (module, priority, logdate,
# host, message, details)
# VALUES (%s, %s, %s, %s, %s, %s)""",
# (self.module, dblevel, now(),
# self.host, message, detail))
# c.close()
def __call__ (self , level , message , detail = None ):
self .log (level , message , detail )
if multiline :
for line in message .split ('\n ' ):
buff .write ('\n %s' % line )
if detail :
for line in detail .split ('\n ' ):
buff .write ('\n %s' % line )
else :
buff .write (message )
if detail :
buff .write (' -- %s' % detail )
buff .write ('\n ' )
self ._LOGFILE .write (buff .getvalue ())
self ._LOGFILE .flush ()
def _logsyslog (self , mask , level , message , detail ):
syslog .syslog (level ,
message + (' -- {0}' .format (detail ) if detail else '' ))
def _logdatabase (self , mask , level , message , detail ):
if self .db and self ._DBLOG :
with self .db .cursor (DummyLogger ()) as cursor :
cursor .execute ("""INSERT INTO logging
(host, application, pid, thread,
msgtime, level, message)
VALUES (?, ?, ?, ?, ?, ?, ?)""" ,
(self .db .gethostname (), argv [0 ].rsplit ('/' , 1 )[1 ],
os .getpid (), self .module , self .time (), level ,
message + (' -- {0}' .format (detail ) if detail else '' )))
def __call__ (self , mask , level , message , detail = None ):
self .log (mask , level , message , detail )