[FIX] web_kanban: parent can be undefined in some cases
[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 class PostgreSQLHandler(logging.Handler):
72     """ PostgreSQL Loggin Handler will store logs in the database, by default
73     the current database, can be set using --log-db=DBNAME
74     """
75     def emit(self, record):
76         ct = threading.current_thread()
77         ct_db = getattr(ct, 'dbname', None)
78         dbname = tools.config['log_db'] or ct_db
79         if not dbname:
80             return
81         with tools.ignore(Exception), tools.mute_logger('openerp.sql_db'), sql_db.db_connect(dbname).cursor() as cr:
82             msg = tools.ustr(record.msg)
83             if record.args:
84                 msg = msg % record.args
85             traceback = getattr(record, 'exc_text', '')
86             if traceback:
87                 msg = "%s\n%s" % (msg, traceback)
88             # we do not use record.levelname because it may have been changed by ColoredFormatter.
89             levelname = logging.getLevelName(record.levelno)
90             val = ('server', ct_db, record.name, levelname, msg, record.pathname, record.lineno, record.funcName)
91             cr.execute("""
92                 INSERT INTO ir_logging(create_date, type, dbname, name, level, message, path, line, func)
93                 VALUES (NOW() at time zone 'UTC', %s, %s, %s, %s, %s, %s, %s, %s)
94             """, val)
95
96 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, _NOTHING, DEFAULT = range(10)
97 #The background is set with 40 plus the number of the color, and the foreground with 30
98 #These are the sequences need to get colored ouput
99 RESET_SEQ = "\033[0m"
100 COLOR_SEQ = "\033[1;%dm"
101 BOLD_SEQ = "\033[1m"
102 COLOR_PATTERN = "%s%s%%s%s" % (COLOR_SEQ, COLOR_SEQ, RESET_SEQ)
103 LEVEL_COLOR_MAPPING = {
104     logging.DEBUG: (BLUE, DEFAULT),
105     logging.INFO: (GREEN, DEFAULT),
106     logging.WARNING: (YELLOW, DEFAULT),
107     logging.ERROR: (RED, DEFAULT),
108     logging.CRITICAL: (WHITE, RED),
109 }
110
111 class DBFormatter(logging.Formatter):
112     def format(self, record):
113         record.pid = os.getpid()
114         record.dbname = getattr(threading.currentThread(), 'dbname', '?')
115         return logging.Formatter.format(self, record)
116
117 class ColoredFormatter(DBFormatter):
118     def format(self, record):
119         fg_color, bg_color = LEVEL_COLOR_MAPPING[record.levelno]
120         record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname)
121         return DBFormatter.format(self, record)
122
123 _logger_init = False
124 def init_logger():
125     global _logger_init
126     if _logger_init:
127         return
128     _logger_init = True
129
130     from tools.translate import resetlocale
131     resetlocale()
132
133     # create a format for log messages and dates
134     format = '%(asctime)s %(pid)s %(levelname)s %(dbname)s %(name)s: %(message)s'
135
136     if tools.config['syslog']:
137         # SysLog Handler
138         if os.name == 'nt':
139             handler = logging.handlers.NTEventLogHandler("%s %s" % (release.description, release.version))
140         else:
141             handler = logging.handlers.SysLogHandler()
142         format = '%s %s' % (release.description, release.version) \
143                 + ':%(dbname)s:%(levelname)s:%(name)s:%(message)s'
144
145     elif tools.config['logfile']:
146         # LogFile Handler
147         logf = tools.config['logfile']
148         try:
149             # We check we have the right location for the log files
150             dirname = os.path.dirname(logf)
151             if dirname and not os.path.isdir(dirname):
152                 os.makedirs(dirname)
153             if tools.config['logrotate'] is not False:
154                 handler = logging.handlers.TimedRotatingFileHandler(filename=logf, when='D', interval=1, backupCount=30)
155             elif os.name == 'posix':
156                 handler = logging.handlers.WatchedFileHandler(logf)
157             else:
158                 handler = logging.handlers.FileHandler(logf)
159         except Exception:
160             sys.stderr.write("ERROR: couldn't create the logfile directory. Logging to the standard output.\n")
161             handler = logging.StreamHandler(sys.stdout)
162     else:
163         # Normal Handler on standard output
164         handler = logging.StreamHandler(sys.stdout)
165
166     # Check that handler.stream has a fileno() method: when running OpenERP
167     # behind Apache with mod_wsgi, handler.stream will have type mod_wsgi.Log,
168     # which has no fileno() method. (mod_wsgi.Log is what is being bound to
169     # sys.stderr when the logging.StreamHandler is being constructed above.)
170     def is_a_tty(stream):
171         return hasattr(stream, 'fileno') and os.isatty(stream.fileno())
172
173     if isinstance(handler, logging.StreamHandler) and is_a_tty(handler.stream):
174         formatter = ColoredFormatter(format)
175     else:
176         formatter = DBFormatter(format)
177     handler.setFormatter(formatter)
178
179     logging.getLogger().addHandler(handler)
180
181     if tools.config['log_db']:
182         postgresqlHandler = PostgreSQLHandler()
183         postgresqlHandler.setLevel(logging.WARNING)
184         logging.getLogger().addHandler(postgresqlHandler)
185
186     # Configure loggers levels
187     pseudo_config = PSEUDOCONFIG_MAPPER.get(tools.config['log_level'], [])
188
189     logconfig = tools.config['log_handler']
190
191     logging_configurations = DEFAULT_LOG_CONFIGURATION + pseudo_config + logconfig
192     for logconfig_item in logging_configurations:
193         loggername, level = logconfig_item.split(':')
194         level = getattr(logging, level, logging.INFO)
195         logger = logging.getLogger(loggername)
196         logger.setLevel(level)
197
198     for logconfig_item in logging_configurations:
199         _logger.debug('logger level set: "%s"', logconfig_item)
200
201 DEFAULT_LOG_CONFIGURATION = [
202     'openerp.workflow.workitem:WARNING',
203     'openerp.http.rpc.request:INFO',
204     'openerp.http.rpc.response:INFO',
205     'openerp.addons.web.http:INFO',
206     'openerp.sql_db:INFO',
207     ':INFO',
208 ]
209 PSEUDOCONFIG_MAPPER = {
210     'debug_rpc_answer': ['openerp:DEBUG','openerp.http.rpc.request:DEBUG', 'openerp.http.rpc.response:DEBUG'],
211     'debug_rpc': ['openerp:DEBUG','openerp.http.rpc.request:DEBUG'],
212     'debug': ['openerp:DEBUG'],
213     'debug_sql': ['openerp.sql_db:DEBUG'],
214     'info': [],
215     'warn': ['openerp:WARNING'],
216     'error': ['openerp:ERROR'],
217     'critical': ['openerp:CRITICAL'],
218 }
219
220 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: