| 1 | diff --git a/mythtv/bindings/python/MythTV/logging.py b/mythtv/bindings/python/MythTV/logging.py
|
|---|
| 2 | index 63cac19aff..102e2a83eb 100644
|
|---|
| 3 | --- a/mythtv/bindings/python/MythTV/logging.py
|
|---|
| 4 | +++ b/mythtv/bindings/python/MythTV/logging.py
|
|---|
| 5 | @@ -7,7 +7,12 @@ from MythTV.exceptions import MythError
|
|---|
| 6 | import os
|
|---|
| 7 | import syslog
|
|---|
| 8 | import codecs
|
|---|
| 9 | -
|
|---|
| 10 | +try:
|
|---|
| 11 | + # needs python-systemd package installed
|
|---|
| 12 | + from systemd import journal
|
|---|
| 13 | +except:
|
|---|
| 14 | + # bail only when '--systemd-journal' is selected
|
|---|
| 15 | + journal = None
|
|---|
| 16 | from sys import version_info, stdout, argv
|
|---|
| 17 | from datetime import datetime
|
|---|
| 18 | try:
|
|---|
| 19 | @@ -102,8 +107,9 @@ class MythLog( LOGLEVEL, LOGMASK, LOGFACILITY ):
|
|---|
| 20 | cls._LOGFILE = stdout
|
|---|
| 21 | cls._logwrite = cls._logfile
|
|---|
| 22 | cls._QUIET = 0
|
|---|
| 23 | - cls._DBLOG = True
|
|---|
| 24 | + cls._DBLOG = False
|
|---|
| 25 | cls._SYSLOG = None
|
|---|
| 26 | + cls._JOURNALLOG = False
|
|---|
| 27 | cls._lock = allocate_lock()
|
|---|
| 28 | cls._parseinput()
|
|---|
| 29 |
|
|---|
| 30 | @@ -111,6 +117,7 @@ class MythLog( LOGLEVEL, LOGMASK, LOGFACILITY ):
|
|---|
| 31 | def _parseinput(cls):
|
|---|
| 32 | args = iter(argv)
|
|---|
| 33 | next(args)
|
|---|
| 34 | + n = 0
|
|---|
| 35 | try:
|
|---|
| 36 | while True:
|
|---|
| 37 | arg = next(args)
|
|---|
| 38 | @@ -118,57 +125,89 @@ class MythLog( LOGLEVEL, LOGMASK, LOGFACILITY ):
|
|---|
| 39 | cls._QUIET += 1
|
|---|
| 40 | elif arg == '--nodblog':
|
|---|
| 41 | cls._DBLOG = False
|
|---|
| 42 | + elif arg == '--enable-dblog':
|
|---|
| 43 | + cls._DBLOG = True
|
|---|
| 44 | elif arg == '--loglevel':
|
|---|
| 45 | cls._setlevel(next(args))
|
|---|
| 46 | elif arg == '--verbose':
|
|---|
| 47 | cls._setmask(next(args))
|
|---|
| 48 | elif arg == '--logfile':
|
|---|
| 49 | cls._setfile(next(args))
|
|---|
| 50 | + n += 1
|
|---|
| 51 | elif arg == '--logpath':
|
|---|
| 52 | cls._setpath(next(args))
|
|---|
| 53 | + n += 1
|
|---|
| 54 | elif arg == '--syslog':
|
|---|
| 55 | cls._setsyslog(next(args))
|
|---|
| 56 | + n += 1
|
|---|
| 57 | + elif arg == '--systemd-journal':
|
|---|
| 58 | + cls._setjournallog()
|
|---|
| 59 | + n += 1
|
|---|
| 60 | elif arg == '--':
|
|---|
| 61 | break
|
|---|
| 62 | -
|
|---|
| 63 | except StopIteration:
|
|---|
| 64 | pass
|
|---|
| 65 | + # only allow one logging method
|
|---|
| 66 | + if (n > 1):
|
|---|
| 67 | + raise MythError("Error: These logging options are mutually exclusive: " + \
|
|---|
| 68 | + "'logfile', 'logpath', 'syslog' or 'systemd-journal'!")
|
|---|
| 69 |
|
|---|
| 70 | @classmethod
|
|---|
| 71 | def _optparseinput(cls):
|
|---|
| 72 | opts, args = cls._parser.parse_args()
|
|---|
| 73 | + n = 0
|
|---|
| 74 | if opts.quiet:
|
|---|
| 75 | cls._QUIET = opts.quiet
|
|---|
| 76 | if opts.dblog:
|
|---|
| 77 | - cls._DBLOG = False
|
|---|
| 78 | + cls._DBLOG = True
|
|---|
| 79 | if opts.loglevel:
|
|---|
| 80 | cls._setlevel(opts.loglevel)
|
|---|
| 81 | if opts.verbose:
|
|---|
| 82 | cls._setmask(opts.verbose)
|
|---|
| 83 | if opts.logfile:
|
|---|
| 84 | cls._setfile(opts.logfile)
|
|---|
| 85 | + n += 1
|
|---|
| 86 | if opts.logpath:
|
|---|
| 87 | cls._setpath(opts.logpath)
|
|---|
| 88 | + n += 1
|
|---|
| 89 | if opts.syslog:
|
|---|
| 90 | cls._setsyslog(opts.syslog)
|
|---|
| 91 | + n += 1
|
|---|
| 92 | + if opts.journallog:
|
|---|
| 93 | + cls._setjournallog()
|
|---|
| 94 | + n += 1
|
|---|
| 95 | + if (n > 1):
|
|---|
| 96 | + cls._parser.error("These logging options are mutually exclusive: " + \
|
|---|
| 97 | + "'logfile', 'logpath', 'syslog' or 'systemd-journal'!")
|
|---|
| 98 | +
|
|---|
| 99 |
|
|---|
| 100 | @classmethod
|
|---|
| 101 | def _argparseinput(cls):
|
|---|
| 102 | opts = cls._parser.parse_args()
|
|---|
| 103 | + n = 0
|
|---|
| 104 | if opts.quiet:
|
|---|
| 105 | cls._QUIET = opts.quiet
|
|---|
| 106 | if opts.dblog:
|
|---|
| 107 | - cls._DBLOG = False
|
|---|
| 108 | + cls._DBLOG = True
|
|---|
| 109 | if opts.loglevel:
|
|---|
| 110 | cls._setlevel(opts.loglevel)
|
|---|
| 111 | if opts.verbose:
|
|---|
| 112 | cls._setmask(opts.verbose)
|
|---|
| 113 | if opts.logfile:
|
|---|
| 114 | cls._setfile(opts.logfile)
|
|---|
| 115 | + n += 1
|
|---|
| 116 | if opts.logpath:
|
|---|
| 117 | cls._setpath(opts.logpath)
|
|---|
| 118 | + n += 1
|
|---|
| 119 | if opts.syslog:
|
|---|
| 120 | cls._setsyslog(opts.syslog)
|
|---|
| 121 | + n += 1
|
|---|
| 122 | + if opts.journallog:
|
|---|
| 123 | + cls._setjournallog()
|
|---|
| 124 | + n += 1
|
|---|
| 125 | + if (n > 1):
|
|---|
| 126 | + cls._parser.error("These logging options are mutually exclusive: " + \
|
|---|
| 127 | + "'logfile', 'logpath', 'syslog' or 'systemd-journal'!")
|
|---|
| 128 |
|
|---|
| 129 | @classmethod
|
|---|
| 130 | def loadOptParse(cls, parser):
|
|---|
| 131 | @@ -176,8 +215,10 @@ class MythLog( LOGLEVEL, LOGMASK, LOGFACILITY ):
|
|---|
| 132 | cls._parseinput = cls._optparseinput
|
|---|
| 133 | parser.add_option('--quiet', action="count", dest="quiet",
|
|---|
| 134 | help="Run quiet. One use squelches terminal, two stops all logging.")
|
|---|
| 135 | - parser.add_option('--nodblog', action="store_true", dest="dblog",
|
|---|
| 136 | - help="Prevent logging to the database.")
|
|---|
| 137 | + parser.add_option('--nodblog', action="store_true", dest="nodblog",
|
|---|
| 138 | + help="Prevent logging to the database (legacy: disabled by default).")
|
|---|
| 139 | + parser.add_option('--enable-dblog', action="store_true", dest="dblog",
|
|---|
| 140 | + help="Enable logging to the database.")
|
|---|
| 141 | parser.add_option('--loglevel', type="string", action="store", dest="loglevel",
|
|---|
| 142 | help="Specify log verbosity, using standard syslog levels.")
|
|---|
| 143 | parser.add_option('--verbose', type="string", action="store", dest="verbose",
|
|---|
| 144 | @@ -188,6 +229,8 @@ class MythLog( LOGLEVEL, LOGMASK, LOGFACILITY ):
|
|---|
| 145 | help="Specify directory to log to, filename will be automatically decided.")
|
|---|
| 146 | parser.add_option('--syslog', type="string", action="store", dest="syslog",
|
|---|
| 147 | help="Specify syslog facility to log to.")
|
|---|
| 148 | + parser.add_option('--systemd-journal', action="store_true", dest="journallog",
|
|---|
| 149 | + help="Specify systemd-journal to log to.")
|
|---|
| 150 |
|
|---|
| 151 | @classmethod
|
|---|
| 152 | def loadArgParse(cls, parser):
|
|---|
| 153 | @@ -204,8 +247,10 @@ class MythLog( LOGLEVEL, LOGMASK, LOGFACILITY ):
|
|---|
| 154 | cls._parseinput = cls._argparseinput
|
|---|
| 155 | parser.add_argument('--quiet', action=Count, nargs=0, dest="quiet",
|
|---|
| 156 | help="Run quiet. One use squelches terminal, two stops all logging.")
|
|---|
| 157 | - parser.add_argument('--nodblog', action="store_true", dest="dblog",
|
|---|
| 158 | - help="Prevent logging to the database.")
|
|---|
| 159 | + parser.add_argument('--nodblog', action="store_true", dest="nodblog",
|
|---|
| 160 | + help="Prevent logging to the database (legacy: disabled by default).")
|
|---|
| 161 | + parser.add_argument('--enable-dblog', action="store_true", dest="dblog",
|
|---|
| 162 | + help="Enable logging to the database.")
|
|---|
| 163 | parser.add_argument('--loglevel', action="store", dest="loglevel",
|
|---|
| 164 | help="Specify log verbosity, using standard syslog levels.")
|
|---|
| 165 | parser.add_argument('--verbose', action="store", dest="verbose",
|
|---|
| 166 | @@ -216,6 +261,8 @@ class MythLog( LOGLEVEL, LOGMASK, LOGFACILITY ):
|
|---|
| 167 | help="Specify directory to log to, filename will be automatically decided.")
|
|---|
| 168 | parser.add_argument('--syslog', action="store", dest="syslog",
|
|---|
| 169 | help="Specify syslog facility to log to.")
|
|---|
| 170 | + parser.add_argument('--systemd-journal', action="store_true", dest="journallog",
|
|---|
| 171 | + help="Specify systemd-journal to log to.")
|
|---|
| 172 |
|
|---|
| 173 | def __repr__(self):
|
|---|
| 174 | return "<%s '%s','%s' at %s>" % \
|
|---|
| 175 | @@ -275,9 +322,12 @@ class MythLog( LOGLEVEL, LOGMASK, LOGFACILITY ):
|
|---|
| 176 | cls._LOGFILE.close()
|
|---|
| 177 | cls._LOGFILE = fileobject
|
|---|
| 178 | cls._logwrite = cls._logfile
|
|---|
| 179 | + # clear other logging options:
|
|---|
| 180 | if cls._SYSLOG:
|
|---|
| 181 | cls._SYSLOG = None
|
|---|
| 182 | syslog.closelog()
|
|---|
| 183 | + if cls._JOURNALLOG:
|
|---|
| 184 | + cls._JOURNALLOG = False
|
|---|
| 185 |
|
|---|
| 186 | @classmethod
|
|---|
| 187 | def _setsyslog(cls, facility=LOGFACILITY.USER):
|
|---|
| 188 | @@ -300,14 +350,37 @@ class MythLog( LOGLEVEL, LOGMASK, LOGFACILITY ):
|
|---|
| 189 | raise MythError("Invalid syslog facility")
|
|---|
| 190 |
|
|---|
| 191 | cls._SYSLOG = facility
|
|---|
| 192 | - syslog.openlog(argv[0].rsplit('/', 1)[1],
|
|---|
| 193 | - syslog.LOG_NDELAY|syslog.LOG_PID,
|
|---|
| 194 | - getattr(syslog, facility))
|
|---|
| 195 | + application = argv[0]
|
|---|
| 196 | + if '/' in application:
|
|---|
| 197 | + application = application.rsplit('/', 1)[1]
|
|---|
| 198 | + syslog.openlog(application, syslog.LOG_NDELAY|syslog.LOG_PID,
|
|---|
| 199 | + getattr(syslog, facility))
|
|---|
| 200 | cls._logwrite = cls._logsyslog
|
|---|
| 201 | + # clear other logging options:
|
|---|
| 202 | if cls._LOGFILE:
|
|---|
| 203 | if cls._LOGFILE.fileno() != 1:
|
|---|
| 204 | cls._LOGFILE.close()
|
|---|
| 205 | cls._LOGFILE = None
|
|---|
| 206 | + if cls._JOURNALLOG:
|
|---|
| 207 | + cls._JOURNALLOG = False
|
|---|
| 208 | +
|
|---|
| 209 | + @classmethod
|
|---|
| 210 | + def _setjournallog(cls):
|
|---|
| 211 | + cls._initlogger()
|
|---|
| 212 | + if journal:
|
|---|
| 213 | + cls._JOURNALLOG = True
|
|---|
| 214 | + cls._logwrite = cls._logjournallog
|
|---|
| 215 | + # clear other logging options:
|
|---|
| 216 | + if cls._LOGFILE:
|
|---|
| 217 | + if cls._LOGFILE.fileno() != 1:
|
|---|
| 218 | + cls._LOGFILE.close()
|
|---|
| 219 | + cls._LOGFILE = None
|
|---|
| 220 | + if cls._SYSLOG:
|
|---|
| 221 | + cls._SYSLOG = None
|
|---|
| 222 | + syslog.closelog()
|
|---|
| 223 | + else:
|
|---|
| 224 | + raise MythError("Error: Python module 'systemd.journal' not available! " + \
|
|---|
| 225 | + "Please install 'python-systemd' module.")
|
|---|
| 226 |
|
|---|
| 227 | @classmethod
|
|---|
| 228 | def _parsemask(cls, mstr=None):
|
|---|
| 229 | @@ -412,6 +485,17 @@ class MythLog( LOGLEVEL, LOGMASK, LOGFACILITY ):
|
|---|
| 230 | syslog.syslog(level,
|
|---|
| 231 | message + (' -- {0}'.format(detail) if detail else ''))
|
|---|
| 232 |
|
|---|
| 233 | + def _logjournallog(self, mask, level, message, detail):
|
|---|
| 234 | + if detail:
|
|---|
| 235 | + detail = ' -- {0}'.format(detail)
|
|---|
| 236 | + else:
|
|---|
| 237 | + detail = ''
|
|---|
| 238 | + application = argv[0]
|
|---|
| 239 | + if '/' in application:
|
|---|
| 240 | + application = application.rsplit('/', 1)[1]
|
|---|
| 241 | + msg = ("[{0}]: {1}{2}".format(self.module, message, detail))
|
|---|
| 242 | + journal.send(msg, PRIORITY=level, SYSLOG_IDENTIFIER=application )
|
|---|
| 243 | +
|
|---|
| 244 | def _logdatabase(self, mask, level, message, detail):
|
|---|
| 245 | if self.db and self._DBLOG:
|
|---|
| 246 | with self.db.cursor(DummyLogger()) as cursor:
|
|---|