[IMP] Add a new Logging Handler, where we will store in the database of OpenERP.

this database can be overrided via the --log-pgsql-database

bzr revid: stw@openerp.com-20140228161147-s9nnrfq2tc94vq5p
This commit is contained in:
Stephane Wirtel 2014-02-28 17:11:47 +01:00
parent 3f779a5cae
commit 047d071a48
5 changed files with 162 additions and 26 deletions

View File

@ -111,15 +111,9 @@ def dispatch_rpc(service_name, method, params):
log(rpc_request, logging.DEBUG, logline, replace_request_password(params), depth=1) log(rpc_request, logging.DEBUG, logline, replace_request_password(params), depth=1)
return result return result
except openerp.osv.orm.except_orm: except (openerp.osv.orm.except_orm, openerp.exceptions.AccessError, \
raise openerp.exceptions.AccessDenied, openerp.exceptions.Warning, \
except openerp.exceptions.AccessError: openerp.exceptions.RedirectWarning):
raise
except openerp.exceptions.AccessDenied:
raise
except openerp.exceptions.Warning:
raise
except openerp.exceptions.RedirectWarning:
raise raise
except openerp.exceptions.DeferredException, e: except openerp.exceptions.DeferredException, e:
_logger.exception(openerp.tools.exception_to_unicode(e)) _logger.exception(openerp.tools.exception_to_unicode(e))

View File

@ -0,0 +1 @@
from handlers import PostgreSQLHandler

100
openerp/loggers/handlers.py Normal file
View File

@ -0,0 +1,100 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2014 OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import contextlib
import datetime
import logging
import threading
import psycopg2
from openerp import tools
# The PostgreSQL Handler for the logging module, will be used by OpenERP to store the logs
# in the database, --log-pgsql-database=YOUR_DBNAME
# By default the system will use the current database
class PostgreSQLHandler(logging.Handler):
@contextlib.contextmanager
def create_connection(self):
db_name = None
db_name_from_cli = tools.config['log_pgsql_database']
if not db_name_from_cli:
# If there is no database, and only in this case, we are going to use the database
# from the current thread and create a connection to this database.
current_thread = threading.current_thread()
db_name_from_thread = getattr(current_thread, 'dbname', None)
if isinstance(db_name_from_thread, basestring):
db_name = db_name_from_thread
else:
db_name = db_name_from_cli
if not db_name:
return
parameters = {
'user': tools.config['db_user'] or None,
'password': tools.config['db_password'] or None,
'host': tools.config['db_host'] or None,
'port': tools.config['db_port'] or None,
'database': db_name,
}
try:
connection = psycopg2.connect(**parameters)
if connection:
yield connection
except Exception, ex: # Use a specific exception
print ex
def emit(self, record):
# We use a context manager to be tolerant to the errors (error of connections,...)
with self.create_connection() as conn:
exception = False
if record.exc_info:
exception = record.exc_text
now = datetime.datetime.utcnow()
current_thread = threading.current_thread()
uid = getattr(current_thread, 'uid', False)
dbname = getattr(current_thread, 'dbname', False)
parameters = (
now, uid, now, uid, 'server', dbname, record.name,
logging.getLevelName(record.levelno), record.msg, exception,
record.filename, record.funcName, record.lineno
)
with conn.cursor() as cursor:
cursor.execute("""
INSERT INTO ir_logging(
create_date, create_uid, write_date, write_uid,
type, dbname, name, level, message, exception, path, func,
line
)
VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""",
parameters
)
conn.commit()

View File

@ -29,6 +29,7 @@ import threading
import tools import tools
import openerp import openerp
import openerp.loggers
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@ -85,6 +86,19 @@ class ColoredFormatter(DBFormatter):
record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname) record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname)
return DBFormatter.format(self, record) return DBFormatter.format(self, record)
import platform
def is_posix_operating_system():
return os.name == 'posix'
def is_windows_operating_system():
return os.name == 'nt'
def is_linux_operating_system():
return is_posix_operating_system() and platform.system() == 'Linux'
def is_macosx_operating_system():
return is_posix_operating_system() and platform.system() == 'Darwin'
def init_logger(): def init_logger():
from tools.translate import resetlocale from tools.translate import resetlocale
resetlocale() resetlocale()
@ -94,24 +108,32 @@ def init_logger():
if tools.config['syslog']: if tools.config['syslog']:
# SysLog Handler # SysLog Handler
if os.name == 'nt': if is_windows_operating_system():
handler = logging.handlers.NTEventLogHandler("%s %s" % (release.description, release.version)) handler = logging.handlers.NTEventLogHandler("%s %s" % (release.description, release.version))
else: elif is_linux_operating_system():
handler = logging.handlers.SysLogHandler('/dev/log') handler = logging.handlers.SysLogHandler('/dev/log')
format = '%s %s' % (release.description, release.version) \ elif is_macosx_operating_system(): # There is no /dev/log on OSX
+ ':%(dbname)s:%(levelname)s:%(name)s:%(message)s' handler = logging.handlers.SysLogHandler('/var/run/log')
else:
raise Exception("There is no syslog handler for this Operating System: %s", platform.system())
format = '%s %s' % (release.description, release.version) + ':%(dbname)s:%(levelname)s:%(name)s:%(message)s'
elif tools.config['logfile']: elif tools.config['logfile']:
# LogFile Handler # LogFile Handler
logf = tools.config['logfile'] logf = tools.config['logfile']
try: try:
# We check we have the right location for the log files
dirname = os.path.dirname(logf) dirname = os.path.dirname(logf)
if dirname and not os.path.isdir(dirname): if dirname and not os.path.isdir(dirname):
os.makedirs(dirname) os.makedirs(dirname)
if tools.config['logrotate'] is not False: if tools.config['logrotate'] is not False:
handler = logging.handlers.TimedRotatingFileHandler(logf,'D',1,30) handler = logging.handlers.TimedRotatingFileHandler(filename=logf, when='D', interval=1, backupCount=30)
elif os.name == 'posix':
elif is_posix_operating_system():
handler = logging.handlers.WatchedFileHandler(logf) handler = logging.handlers.WatchedFileHandler(logf)
else: else:
handler = logging.handlers.FileHandler(logf) handler = logging.handlers.FileHandler(logf)
except Exception: except Exception:
@ -125,12 +147,14 @@ def init_logger():
# behind Apache with mod_wsgi, handler.stream will have type mod_wsgi.Log, # behind Apache with mod_wsgi, handler.stream will have type mod_wsgi.Log,
# which has no fileno() method. (mod_wsgi.Log is what is being bound to # which has no fileno() method. (mod_wsgi.Log is what is being bound to
# sys.stderr when the logging.StreamHandler is being constructed above.) # sys.stderr when the logging.StreamHandler is being constructed above.)
if isinstance(handler, logging.StreamHandler) \ def has_fileno(stream):
and hasattr(handler.stream, 'fileno') \ return hasattr(stream, 'fileno') and os.isatty(stream.fileno())
and os.isatty(handler.stream.fileno()):
if isinstance(handler, logging.StreamHandler) and has_fileno(handler.stream):
formatter = ColoredFormatter(format) formatter = ColoredFormatter(format)
else: else:
formatter = DBFormatter(format) formatter = DBFormatter(format)
handler.setFormatter(formatter) handler.setFormatter(formatter)
# Configure handlers # Configure handlers
@ -141,7 +165,9 @@ def init_logger():
logging_configurations = DEFAULT_LOG_CONFIGURATION + pseudo_config + logconfig logging_configurations = DEFAULT_LOG_CONFIGURATION + pseudo_config + logconfig
for logconfig_item in logging_configurations: for logconfig_item in logging_configurations:
loggername, level = logconfig_item.split(':') loggername, level = logconfig_item.split(':')
level = getattr(logging, level, logging.INFO) level = getattr(logging, level, logging.INFO)
logger = logging.getLogger(loggername) logger = logging.getLogger(loggername)
logger.handlers = [] logger.handlers = []
logger.setLevel(level) logger.setLevel(level)
@ -149,6 +175,13 @@ def init_logger():
if loggername != '': if loggername != '':
logger.propagate = False logger.propagate = False
# magic ;-)
# we manage the connection in the postgresqlhandler
postgresqlHandler = openerp.loggers.handlers.PostgreSQLHandler()
postgresqlHandler.setLevel(logging.WARNING)
logger = logging.getLogger()
logger.addHandler(postgresqlHandler)
for logconfig_item in logging_configurations: for logconfig_item in logging_configurations:
_logger.debug('logger level set: "%s"', logconfig_item) _logger.debug('logger level set: "%s"', logconfig_item)

View File

@ -72,9 +72,10 @@ class configmanager(object):
} }
# Not exposed in the configuration file. # Not exposed in the configuration file.
self.blacklist_for_save = set( self.blacklist_for_save = set([
['publisher_warranty_url', 'load_language', 'root_path', 'publisher_warranty_url', 'load_language', 'root_path',
'init', 'save', 'config', 'update', 'stop_after_init']) 'init', 'save', 'config', 'update', 'stop_after_init'
])
# dictionary mapping option destination (keys in self.options) to MyOptions. # dictionary mapping option destination (keys in self.options) to MyOptions.
self.casts = {} self.casts = {}
@ -83,7 +84,10 @@ class configmanager(object):
self.config_file = fname self.config_file = fname
self.has_ssl = check_ssl() self.has_ssl = check_ssl()
self._LOGLEVELS = dict([(getattr(loglevels, 'LOG_%s' % x), getattr(logging, x)) for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET')]) self._LOGLEVELS = dict([
(getattr(loglevels, 'LOG_%s' % x), getattr(logging, x))
for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET')
])
version = "%s %s" % (release.description, release.version) version = "%s %s" % (release.description, release.version)
self.parser = parser = optparse.OptionParser(version=version, option_class=MyOption) self.parser = parser = optparse.OptionParser(version=version, option_class=MyOption)
@ -176,12 +180,16 @@ class configmanager(object):
group.add_option('--log-response', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.response:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.response:DEBUG') group.add_option('--log-response', action="append_const", dest="log_handler", const="openerp.netsvc.rpc.response:DEBUG", help='shortcut for --log-handler=openerp.netsvc.rpc.response:DEBUG')
group.add_option('--log-web', action="append_const", dest="log_handler", const="openerp.addons.web.http:DEBUG", help='shortcut for --log-handler=openerp.addons.web.http:DEBUG') group.add_option('--log-web', action="append_const", dest="log_handler", const="openerp.addons.web.http:DEBUG", help='shortcut for --log-handler=openerp.addons.web.http:DEBUG')
group.add_option('--log-sql', action="append_const", dest="log_handler", const="openerp.sql_db:DEBUG", help='shortcut for --log-handler=openerp.sql_db:DEBUG') group.add_option('--log-sql', action="append_const", dest="log_handler", const="openerp.sql_db:DEBUG", help='shortcut for --log-handler=openerp.sql_db:DEBUG')
group.add_option('--log-pgsql-database', dest='log_pgsql_database', help="database where OpenERP will store the exceptions", my_default=False)
# For backward-compatibility, map the old log levels to something # For backward-compatibility, map the old log levels to something
# quite close. # quite close.
levels = ['info', 'debug_rpc', 'warn', 'test', 'critical', levels = [
'debug_sql', 'error', 'debug', 'debug_rpc_answer', 'notset'] 'info', 'debug_rpc', 'warn', 'test', 'critical',
group.add_option('--log-level', dest='log_level', type='choice', choices=levels, 'debug_sql', 'error', 'debug', 'debug_rpc_answer', 'notset'
my_default='info', help='specify the level of the logging. Accepted values: ' + str(levels) + ' (deprecated option).') ]
group.add_option('--log-level', dest='log_level', type='choice',
choices=levels, my_default='info',
help='specify the level of the logging. Accepted values: %s (deprecated option).' % (levels,))
parser.add_option_group(group) parser.add_option_group(group)