add mongolog
This commit is contained in:
parent
4e8f1430c4
commit
2f729bfdbc
54
oyster/mongolog.py
Normal file
54
oyster/mongolog.py
Normal file
@ -0,0 +1,54 @@
|
||||
"""
|
||||
MongoDB handler for Python Logging
|
||||
|
||||
inspired by https://github.com/andreisavu/mongodb-log
|
||||
"""
|
||||
|
||||
import logging
|
||||
import datetime
|
||||
import socket
|
||||
import pymongo
|
||||
|
||||
|
||||
class MongoFormatter(logging.Formatter):
|
||||
|
||||
def format(self, record):
|
||||
""" turn a LogRecord into something mongo can store """
|
||||
data = record.__dict__.copy()
|
||||
|
||||
data.update(
|
||||
# format message
|
||||
message=record.getMessage(),
|
||||
# overwrite created (float) w/ a mongo-compatible datetime
|
||||
created=datetime.datetime.utcnow(),
|
||||
host=socket.gethostname(),
|
||||
args=tuple(unicode(arg) for arg in record.args)
|
||||
)
|
||||
data.pop('msecs') # not needed, stored in created
|
||||
|
||||
# TODO: ensure everything in 'extra' is MongoDB-ready
|
||||
exc_info = data.get('exc_info')
|
||||
if exc_info:
|
||||
data['exc_info'] = self.formatException(exc_info)
|
||||
return data
|
||||
|
||||
|
||||
class MongoHandler(logging.Handler):
|
||||
def __init__(self, db, collection='logs', host='localhost', port=None,
|
||||
capped_size=100000000, level=logging.NOTSET, async=True):
|
||||
db = pymongo.connection.Connection(host, port)[db]
|
||||
# try and create the capped log collection
|
||||
if capped_size:
|
||||
try:
|
||||
db.create_collection(collection, capped=True, size=capped_size)
|
||||
except pymongo.errors.CollectionInvalid:
|
||||
pass
|
||||
self.collection = db[collection]
|
||||
self.async = async
|
||||
super(MongoHandler, self).__init__(level)
|
||||
self.formatter = MongoFormatter()
|
||||
|
||||
def emit(self, record):
|
||||
# explicitly set safe=False to get async insert
|
||||
# TODO: what to do if an error occurs? not safe to log-- ignore?
|
||||
self.collection.save(self.format(record), safe=not self.async)
|
53
oyster/tests/test_mongolog.py
Normal file
53
oyster/tests/test_mongolog.py
Normal file
@ -0,0 +1,53 @@
|
||||
import unittest
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
import pymongo
|
||||
from ..mongolog import MongoHandler
|
||||
|
||||
class TestMongoLog(unittest.TestCase):
|
||||
|
||||
DB_NAME = 'oyster_test'
|
||||
|
||||
def setUp(self):
|
||||
pymongo.Connection().drop_database(self.DB_NAME)
|
||||
self.log = logging.getLogger('mongotest')
|
||||
self.log.setLevel(logging.DEBUG)
|
||||
self.logs = pymongo.Connection()[self.DB_NAME]['logs']
|
||||
# clear handlers upon each setup
|
||||
self.log.handlers = []
|
||||
# async = False for testing
|
||||
self.log.addHandler(MongoHandler(self.DB_NAME, capped_size=4000,
|
||||
async=False))
|
||||
|
||||
def tearDown(self):
|
||||
pymongo.Connection().drop_database(self.DB_NAME)
|
||||
|
||||
def test_basic_write(self):
|
||||
self.log.debug('test')
|
||||
self.assertEqual(self.logs.count(), 1)
|
||||
self.log.debug('test')
|
||||
self.assertEqual(self.logs.count(), 2)
|
||||
# capped_size will limit these
|
||||
self.log.debug('test'*200)
|
||||
self.log.debug('test'*200)
|
||||
self.assertEqual(self.logs.count(), 1)
|
||||
|
||||
def test_attributes(self):
|
||||
self.log.debug('pi=%s', 3.14, extra={'pie':'pizza'})
|
||||
logged = self.logs.find_one()
|
||||
self.assertEqual(logged['message'], 'pi=3.14')
|
||||
self.assertTrue(isinstance(logged['created'], datetime.datetime))
|
||||
self.assertTrue('host' in logged)
|
||||
self.assertEqual(logged['name'], 'mongotest')
|
||||
self.assertEqual(logged['levelname'], 'DEBUG')
|
||||
self.assertEqual(logged['pie'], 'pizza')
|
||||
|
||||
# and exc_info
|
||||
try:
|
||||
raise Exception('error!')
|
||||
except:
|
||||
self.log.warning('oh no!', exc_info=True)
|
||||
logged = self.logs.find_one(sort=[('$natural', -1)])
|
||||
self.assertEqual(logged['levelname'], 'WARNING')
|
||||
self.assertTrue('error!' in logged['exc_info'])
|
Loading…
Reference in New Issue
Block a user