diff --git a/README.md b/README.md index 4734f20..987d2ab 100644 --- a/README.md +++ b/README.md @@ -63,17 +63,38 @@ Then, please create the events like this. This will send the event to fluent, wi ### Python logging.Handler interface This client-library also has FluentHandler class for Python logging module. +Custom default entries or [LogRecord attributes](https://docs.python.org/2/library/logging.html#logrecord-attributes) can be used when creating FORMAT dictionary. import logging + import socket from fluent import handler - + + FORMAT = { + 'host': socket.gethostname(), + 'error_line': '%(lineno)d', + 'error_file': '%(filename)s', + 'code': '%(levelno)s', + 'type': '%(levelname)s', + 'logger': '%(name)s', + 'module': '%(module)s', + 'function_name': '%(funcName)s', + 'stack_trace': '%(exc_text)s' + } + + formatter = handler.FluentRecordFormatter(FORMAT) + fluent_handler = handler.FluentHandler('app.follow', host='host', port=24224) + fluent_handler.setFormatter(formatter) + logging.basicConfig(level=logging.INFO) l = logging.getLogger('fluent.test') - l.addHandler(handler.FluentHandler('app.follow', host='host', port=24224)) + l.addHandler(fluent_handler) + + l.info("This log entry will be logged with key: 'message'.") l.info({ 'from': 'userA', 'to': 'userB' }) + l.info('{"from": "userC", "to": "userD"}') ## Testing diff --git a/fluent/handler.py b/fluent/handler.py index e14c036..c68314d 100644 --- a/fluent/handler.py +++ b/fluent/handler.py @@ -16,35 +16,33 @@ from fluent import sender -class FluentRecordFormatter(object): - def __init__(self): - self.hostname = socket.gethostname() +class FluentRecordFormatter(logging.Formatter): + def __init__(self, fmt=None, datefmt=None): + super(FluentRecordFormatter, self).__init__(fmt, datefmt) + if isinstance(fmt, dict): + pass + elif isinstance(fmt, str): + try: + self._fmt = json.loads(str(fmt)) + except ValueError: + self._fmt = self.default_format() + else: + self._fmt = self.default_format() def format(self, record): - data = {'sys_host': self.hostname, - 'sys_name': record.name, - 'sys_module': record.module, - # 'sys_lineno': record.lineno, - # 'sys_levelno': record.levelno, - # 'sys_levelname': record.levelname, - # 'sys_filename': record.filename, - # 'sys_funcname': record.funcName, - # 'sys_exc_info': record.exc_info, - } - # if 'sys_exc_info' in data and data['sys_exc_info']: - # data['sys_exc_info'] = self.formatException(data['sys_exc_info']) - + self.format_data(record) + data = dict([(key, value % record.__dict__) for key, value in self._fmt.items()]) self._structuring(data, record.msg) return data def _structuring(self, data, msg): if isinstance(msg, dict): self._add_dic(data, msg) - elif isinstance(msg, str): + elif isinstance(msg, basestring): try: self._add_dic(data, json.loads(str(msg))) except ValueError: - pass + self._add_dic(data, {'message': msg}) @staticmethod def _add_dic(data, dic): @@ -52,6 +50,28 @@ def _add_dic(data, dic): if isinstance(key, basestring): data[str(key)] = value + def format_data(self, record): + record.message = record.getMessage() + if self.usesTime(): + record.asctime = self.formatTime(record, self.datefmt) + if record.exc_info: + # Cache the traceback text to avoid converting it multiple times + # (it's constant anyway) + if not record.exc_text: + record.exc_text = self.formatException(record.exc_info) + + def usesTime(self): + for _, value in self._fmt.items(): + if value.find('%(asctime)') >= 0: + return True + return False + + def default_format(self): + return { + 'sys_host': socket.gethostname(), + 'sys_name': '%(name)s', + 'sys_module': '%(module)s' + } class FluentHandler(logging.Handler): ''' diff --git a/tests/test_handler.py b/tests/test_handler.py index 7f944a8..182917e 100644 --- a/tests/test_handler.py +++ b/tests/test_handler.py @@ -27,20 +27,27 @@ def test_simple(self): logging.basicConfig(level=logging.INFO) log = logging.getLogger('fluent.test') - handler.setFormatter(fluent.handler.FluentRecordFormatter()) + formatter = fluent.handler.FluentRecordFormatter() + handler.setFormatter(formatter) log.addHandler(handler) log.info({ 'from': 'userA', 'to': 'userB' }) + log.info('{"from": "userC", "to": "userD"}') + log.info("Test log message") handler.close() data = self.get_data() eq = self.assertEqual - eq(1, len(data)) + eq(3, len(data)) eq(3, len(data[0])) eq('app.follow', data[0][0]) eq('userA', data[0][2]['from']) eq('userB', data[0][2]['to']) + eq('userC', data[1][2]['from']) + eq('userD', data[1][2]['to']) + eq('Test log message', data[2][2]['message']) + self.assertFalse(formatter.usesTime()) self.assertTrue(data[0][1]) self.assertTrue(isinstance(data[0][1], int))