Merge branch 'master' of https://github.com/odoo/odoo
[odoo/odoo.git] / openerp / netsvc.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2014 OpenERP SA (<http://www.openerp.com>)
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import logging
23 import logging.handlers
24 import os
25 import pprint
26 import release
27 import sys
28 import threading
29
30 import psycopg2
31
32 import openerp
33 import sql_db
34 import tools
35
36 _logger = logging.getLogger(__name__)
37
38 def log(logger, level, prefix, msg, depth=None):
39     indent=''
40     indent_after=' '*len(prefix)
41     for line in (prefix + pprint.pformat(msg, depth=depth)).split('\n'):
42         logger.log(level, indent+line)
43         indent=indent_after
44
45 def LocalService(name):
46     """
47     The openerp.netsvc.LocalService() function is deprecated. It still works
48     in two cases: workflows and reports. For workflows, instead of using
49     LocalService('workflow'), openerp.workflow should be used (better yet,
50     methods on openerp.osv.orm.Model should be used). For reports,
51     openerp.report.render_report() should be used (methods on the Model should
52     be provided too in the future).
53     """
54     assert openerp.conf.deprecation.allow_local_service
55     _logger.warning("LocalService() is deprecated since march 2013 (it was called with '%s')." % name)
56
57     if name == 'workflow':
58         return openerp.workflow
59
60     if name.startswith('report.'):
61         report = openerp.report.interface.report_int._reports.get(name)
62         if report:
63             return report
64         else:
65             dbname = getattr(threading.currentThread(), 'dbname', None)
66             if dbname:
67                 registry = openerp.modules.registry.RegistryManager.get(dbname)
68                 with registry.cursor() as cr:
69                     return registry['ir.actions.report.xml']._lookup_report(cr, name[len('report.'):])
70
71 path_prefix = os.path.realpath(os.path.dirname(os.path.dirname(__file__)))
72
73 class PostgreSQLHandler(logging.Handler):
74     """ PostgreSQL Loggin Handler will store logs in the database, by default
75     the current database, can be set using --log-db=DBNAME
76     """
77     def emit(self, record):
78         ct = threading.current_thread()
79         ct_db = getattr(ct, 'dbname', None)
80         dbname = tools.config['log_db'] or ct_db
81         if not dbname:
82             return
83         with tools.ignore(Exception), tools.mute_logger('openerp.sql_db'), sql_db.db_connect(dbname, allow_uri=True).cursor() as cr:
84             msg = tools.ustr(record.msg)
85             if record.args:
86                 msg = msg % record.args
87             traceback = getattr(record, 'exc_text', '')
88             if traceback:
89                 msg = "%s\n%s" % (msg, traceback)
90             # we do not use record.levelname because it may have been changed by ColoredFormatter.
91             levelname = logging.getLevelName(record.levelno)
92
93             val = ('server', ct_db, record.name, levelname, msg, record.pathname[len(path_prefix)+1:], record.lineno, record.funcName)
94             cr.execute("""
95                 INSERT INTO ir_logging(create_date, type, dbname, name, level, message, path, line, func)
96                 VALUES (NOW() at time zone 'UTC', %s, %s, %s, %s, %s, %s, %s, %s)
97             """, val)
98
99 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, _NOTHING, DEFAULT = range(10)
100 #The background is set with 40 plus the number of the color, and the foreground with 30
101 #These are the sequences need to get colored ouput
102 RESET_SEQ = "\033[0m"
103 COLOR_SEQ = "\033[1;%dm"
104 BOLD_SEQ = "\033[1m"
105 COLOR_PATTERN = "%s%s%%s%s" % (COLOR_SEQ, COLOR_SEQ, RESET_SEQ)
106 LEVEL_COLOR_MAPPING = {
107     logging.DEBUG: (BLUE, DEFAULT),
108     logging.INFO: (GREEN, DEFAULT),
109     logging.WARNING: (YELLOW, DEFAULT),
110     logging.ERROR: (RED, DEFAULT),
111     logging.CRITICAL: (WHITE, RED),
112 }
113
114 class DBFormatter(logging.Formatter):
115     def format(self, record):
116         record.pid = os.getpid()
117         record.dbname = getattr(threading.currentThread(), 'dbname', '?')
118         return logging.Formatter.format(self, record)
119
120 class ColoredFormatter(DBFormatter):
121     def format(self, record):
122         fg_color, bg_color = LEVEL_COLOR_MAPPING.get(record.levelno, (GREEN, DEFAULT))
123         record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname)
124         return DBFormatter.format(self, record)
125
126 _logger_init = False
127 def init_logger():
128     global _logger_init
129     if _logger_init:
130         return
131     _logger_init = True
132
133     logging.addLevelName(25, "INFO")
134
135     from tools.translate import resetlocale
136     resetlocale()
137
138     # create a format for log messages and dates
139     format = '%(asctime)s %(pid)s %(levelname)s %(dbname)s %(name)s: %(message)s'
140
141     if tools.config['syslog']:
142         # SysLog Handler
143         if os.name == 'nt':
144             handler = logging.handlers.NTEventLogHandler("%s %s" % (release.description, release.version))
145         else:
146             handler = logging.handlers.SysLogHandler()
147         format = '%s %s' % (release.description, release.version) \
148                 + ':%(dbname)s:%(levelname)s:%(name)s:%(message)s'
149
150     elif tools.config['logfile']:
151         # LogFile Handler
152         logf = tools.config['logfile']
153         try:
154             # We check we have the right location for the log files
155             dirname = os.path.dirname(logf)
156             if dirname and not os.path.isdir(dirname):
157                 os.makedirs(dirname)
158             if tools.config['logrotate'] is not False:
159                 handler = logging.handlers.TimedRotatingFileHandler(filename=logf, when='D', interval=1, backupCount=30)
160             elif os.name == 'posix':
161                 handler = logging.handlers.WatchedFileHandler(logf)
162             else:
163                 handler = logging.FileHandler(logf)
164         except Exception:
165             sys.stderr.write("ERROR: couldn't create the logfile directory. Logging to the standard output.\n")
166             handler = logging.StreamHandler(sys.stdout)
167     else:
168         # Normal Handler on standard output
169         handler = logging.StreamHandler(sys.stdout)
170
171     # Check that handler.stream has a fileno() method: when running OpenERP
172     # behind Apache with mod_wsgi, handler.stream will have type mod_wsgi.Log,
173     # which has no fileno() method. (mod_wsgi.Log is what is being bound to
174     # sys.stderr when the logging.StreamHandler is being constructed above.)
175     def is_a_tty(stream):
176         return hasattr(stream, 'fileno') and os.isatty(stream.fileno())
177
178     if isinstance(handler, logging.StreamHandler) and is_a_tty(handler.stream):
179         formatter = ColoredFormatter(format)
180     else:
181         formatter = DBFormatter(format)
182     handler.setFormatter(formatter)
183
184     logging.getLogger().addHandler(handler)
185
186     if tools.config['log_db']:
187         postgresqlHandler = PostgreSQLHandler()
188         postgresqlHandler.setLevel(25)
189         logging.getLogger().addHandler(postgresqlHandler)
190
191     # Configure loggers levels
192     pseudo_config = PSEUDOCONFIG_MAPPER.get(tools.config['log_level'], [])
193
194     logconfig = tools.config['log_handler']
195
196     logging_configurations = DEFAULT_LOG_CONFIGURATION + pseudo_config + logconfig
197     for logconfig_item in logging_configurations:
198         loggername, level = logconfig_item.split(':')
199         level = getattr(logging, level, logging.INFO)
200         logger = logging.getLogger(loggername)
201         logger.setLevel(level)
202
203     for logconfig_item in logging_configurations:
204         _logger.debug('logger level set: "%s"', logconfig_item)
205
206 DEFAULT_LOG_CONFIGURATION = [
207     'openerp.workflow.workitem:WARNING',
208     'openerp.http.rpc.request:INFO',
209     'openerp.http.rpc.response:INFO',
210     'openerp.addons.web.http:INFO',
211     'openerp.sql_db:INFO',
212     ':INFO',
213 ]
214 PSEUDOCONFIG_MAPPER = {
215     'debug_rpc_answer': ['openerp:DEBUG','openerp.http.rpc.request:DEBUG', 'openerp.http.rpc.response:DEBUG'],
216     'debug_rpc': ['openerp:DEBUG','openerp.http.rpc.request:DEBUG'],
217     'debug': ['openerp:DEBUG'],
218     'debug_sql': ['openerp.sql_db:DEBUG'],
219     'info': [],
220     'warn': ['openerp:WARNING', 'werkzeug:WARNING'],
221     'error': ['openerp:ERROR', 'werkzeug:ERROR'],
222     'critical': ['openerp:CRITICAL', 'werkzeug:CRITICAL'],
223 }
224
225 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: