diff --git a/openerp/http.py b/openerp/http.py index ea1e82ed4c6..92fe03a6232 100644 --- a/openerp/http.py +++ b/openerp/http.py @@ -111,15 +111,9 @@ def dispatch_rpc(service_name, method, params): log(rpc_request, logging.DEBUG, logline, replace_request_password(params), depth=1) return result - except openerp.osv.orm.except_orm: - raise - except openerp.exceptions.AccessError: - raise - except openerp.exceptions.AccessDenied: - raise - except openerp.exceptions.Warning: - raise - except openerp.exceptions.RedirectWarning: + except (openerp.osv.orm.except_orm, openerp.exceptions.AccessError, \ + openerp.exceptions.AccessDenied, openerp.exceptions.Warning, \ + openerp.exceptions.RedirectWarning): raise except openerp.exceptions.DeferredException, e: _logger.exception(openerp.tools.exception_to_unicode(e)) diff --git a/openerp/loggers/__init__.py b/openerp/loggers/__init__.py new file mode 100644 index 00000000000..19b76b55b64 --- /dev/null +++ b/openerp/loggers/__init__.py @@ -0,0 +1 @@ +from handlers import PostgreSQLHandler \ No newline at end of file diff --git a/openerp/loggers/handlers.py b/openerp/loggers/handlers.py new file mode 100644 index 00000000000..763aa079195 --- /dev/null +++ b/openerp/loggers/handlers.py @@ -0,0 +1,100 @@ +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2014 OpenERP SA () +# +# 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 . +# +############################################################################## + +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() diff --git a/openerp/netsvc.py b/openerp/netsvc.py index f19162cde5a..250cd52a1d1 100644 --- a/openerp/netsvc.py +++ b/openerp/netsvc.py @@ -29,6 +29,7 @@ import threading import tools import openerp +import openerp.loggers _logger = logging.getLogger(__name__) @@ -85,6 +86,19 @@ class ColoredFormatter(DBFormatter): record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname) 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(): from tools.translate import resetlocale resetlocale() @@ -94,24 +108,32 @@ def init_logger(): if tools.config['syslog']: # SysLog Handler - if os.name == 'nt': + if is_windows_operating_system(): handler = logging.handlers.NTEventLogHandler("%s %s" % (release.description, release.version)) - else: + elif is_linux_operating_system(): handler = logging.handlers.SysLogHandler('/dev/log') - format = '%s %s' % (release.description, release.version) \ - + ':%(dbname)s:%(levelname)s:%(name)s:%(message)s' + elif is_macosx_operating_system(): # There is no /dev/log on OSX + 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']: # LogFile Handler logf = tools.config['logfile'] try: + # We check we have the right location for the log files dirname = os.path.dirname(logf) if dirname and not os.path.isdir(dirname): os.makedirs(dirname) + if tools.config['logrotate'] is not False: - handler = logging.handlers.TimedRotatingFileHandler(logf,'D',1,30) - elif os.name == 'posix': + handler = logging.handlers.TimedRotatingFileHandler(filename=logf, when='D', interval=1, backupCount=30) + + elif is_posix_operating_system(): handler = logging.handlers.WatchedFileHandler(logf) + else: handler = logging.handlers.FileHandler(logf) except Exception: @@ -125,12 +147,14 @@ def init_logger(): # 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 # sys.stderr when the logging.StreamHandler is being constructed above.) - if isinstance(handler, logging.StreamHandler) \ - and hasattr(handler.stream, 'fileno') \ - and os.isatty(handler.stream.fileno()): + def has_fileno(stream): + return hasattr(stream, 'fileno') and os.isatty(stream.fileno()) + + if isinstance(handler, logging.StreamHandler) and has_fileno(handler.stream): formatter = ColoredFormatter(format) else: formatter = DBFormatter(format) + handler.setFormatter(formatter) # Configure handlers @@ -141,7 +165,9 @@ def init_logger(): logging_configurations = DEFAULT_LOG_CONFIGURATION + pseudo_config + logconfig for logconfig_item in logging_configurations: loggername, level = logconfig_item.split(':') + level = getattr(logging, level, logging.INFO) + logger = logging.getLogger(loggername) logger.handlers = [] logger.setLevel(level) @@ -149,6 +175,13 @@ def init_logger(): if loggername != '': 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: _logger.debug('logger level set: "%s"', logconfig_item) diff --git a/openerp/tools/config.py b/openerp/tools/config.py index 07435075f8d..ab1eab8a52a 100644 --- a/openerp/tools/config.py +++ b/openerp/tools/config.py @@ -72,9 +72,10 @@ class configmanager(object): } # Not exposed in the configuration file. - self.blacklist_for_save = set( - ['publisher_warranty_url', 'load_language', 'root_path', - 'init', 'save', 'config', 'update', 'stop_after_init']) + self.blacklist_for_save = set([ + 'publisher_warranty_url', 'load_language', 'root_path', + 'init', 'save', 'config', 'update', 'stop_after_init' + ]) # dictionary mapping option destination (keys in self.options) to MyOptions. self.casts = {} @@ -83,7 +84,10 @@ class configmanager(object): self.config_file = fname 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) 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-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-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 # quite close. - levels = ['info', 'debug_rpc', 'warn', 'test', 'critical', - 'debug_sql', 'error', 'debug', 'debug_rpc_answer', 'notset'] - group.add_option('--log-level', dest='log_level', type='choice', choices=levels, - my_default='info', help='specify the level of the logging. Accepted values: ' + str(levels) + ' (deprecated option).') + levels = [ + 'info', 'debug_rpc', 'warn', 'test', 'critical', + 'debug_sql', 'error', 'debug', 'debug_rpc_answer', 'notset' + ] + 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)