Pythonのloggingで独自ハンドラを作りたいとき
Pythonのloggingを利用していると、独自のハンドラを作りたくなるときがある。
そういう時には、すでにloggingに定義されているハンドラのコードを参考にすることができる。
例えば、logging.handlers.SMTPHandler
は下記のように定義されている。
https://github.com/python/cpython/blob/master/Lib/logging/handlers.py
# ソースコード内のコメントは削除 class SMTPHandler(logging.Handler): def __init__(self, mailhost, fromaddr, toaddrs, subject, credentials=None, secure=None, timeout=5.0): logging.Handler.__init__(self) if isinstance(mailhost, (list, tuple)): self.mailhost, self.mailport = mailhost else: self.mailhost, self.mailport = mailhost, None if isinstance(credentials, (list, tuple)): self.username, self.password = credentials else: self.username = None self.fromaddr = fromaddr if isinstance(toaddrs, str): toaddrs = [toaddrs] self.toaddrs = toaddrs self.subject = subject self.secure = secure self.timeout = timeout def getSubject(self, record): return self.subject def emit(self, record): try: import smtplib from email.message import EmailMessage import email.utils port = self.mailport if not port: port = smtplib.SMTP_PORT smtp = smtplib.SMTP(self.mailhost, port, timeout=self.timeout) msg = EmailMessage() msg['From'] = self.fromaddr msg['To'] = ','.join(self.toaddrs) msg['Subject'] = self.getSubject(record) msg['Date'] = email.utils.localtime() msg.set_content(self.format(record)) if self.username: if self.secure is not None: smtp.ehlo() smtp.starttls(*self.secure) smtp.ehlo() smtp.login(self.username, self.password) smtp.send_message(msg) smtp.quit() except Exception: self.handleError(record)
これを参考にすると、独自ハンドラを作るには、logging.Handler
クラスを継承して、必要に応じて、親クラスのメソッドをオーバーライド、もしくはメソッド追加を行えばよいということが分かる。
その際、emit
メソッドだけは、子クラスでオーバーライドして実装してあげる必要がある。
もし、これを実装しなければ、必ずNotImplementedError
が発生するようになっている。
emit
は、ログの書き込み処理や送信処理を行うメソッドであるので、実装が必要なのはまあ当然である。
ちなみに、親クラスとなるlogging.Handler
は、下記のようになっている。
https://github.com/python/cpython/blob/master/Lib/logging/init.py
# ソースコード内のコメントは削除 class Handler(Filterer): def __init__(self, level=NOTSET): Filterer.__init__(self) self._name = None self.level = _checkLevel(level) self.formatter = None _addHandlerRef(self) self.createLock() def get_name(self): return self._name def set_name(self, name): _acquireLock() try: if self._name in _handlers: del _handlers[self._name] self._name = name if name: _handlers[name] = self finally: _releaseLock() name = property(get_name, set_name) def createLock(self): self.lock = threading.RLock() _register_at_fork_reinit_lock(self) def acquire(self): if self.lock: self.lock.acquire() def release(self): if self.lock: self.lock.release() def setLevel(self, level): self.level = _checkLevel(level) def format(self, record): if self.formatter: fmt = self.formatter else: fmt = _defaultFormatter return fmt.format(record) def emit(self, record): raise NotImplementedError('emit must be implemented ' 'by Handler subclasses') def handle(self, record): rv = self.filter(record) if rv: self.acquire() try: self.emit(record) finally: self.release() return rv def setFormatter(self, fmt): self.formatter = fmt def flush(self): pass def close(self): _acquireLock() try: if self._name and self._name in _handlers: del _handlers[self._name] finally: _releaseLock() def handleError(self, record): if raiseExceptions and sys.stderr: t, v, tb = sys.exc_info() try: sys.stderr.write('--- Logging error ---\n') traceback.print_exception(t, v, tb, None, sys.stderr) sys.stderr.write('Call stack:\n') frame = tb.tb_frame while (frame and os.path.dirname(frame.f_code.co_filename) == __path__[0]): frame = frame.f_back if frame: traceback.print_stack(frame, file=sys.stderr) else: sys.stderr.write('Logged from file %s, line %s\n' % ( record.filename, record.lineno)) try: sys.stderr.write('Message: %r\n' 'Arguments: %s\n' % (record.msg, record.args)) except RecursionError: raise except Exception: sys.stderr.write('Unable to print the message and arguments' ' - possible formatting error.\nUse the' ' traceback above to help find the error.\n' ) except OSError: pass finally: del t, v, tb def __repr__(self): level = getLevelName(self.level) return '<%s (%s)>' % (self.__class__.__name__, level)
自分の場合、DBのメール機能を使ってログを送りたかったので、DBMailHandler
クラスを作ったのである。