2 # -*- coding: utf-8 -*-
3 ##############################################################################
5 # OpenERP, Open Source Management Solution
6 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
7 # The refactoring about the OpenSSL support come from Tryton
8 # Copyright (C) 2007-2009 Cédric Krier.
9 # Copyright (C) 2007-2009 Bertrand Chenal.
10 # Copyright (C) 2008 B2CK SPRL.
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ##############################################################################
28 import logging.handlers
34 from pprint import pformat
37 class Service(object):
38 """ Base class for *Local* services
40 Functionality here is trusted, no authentication.
43 def __init__(self, name, audience=''):
44 Service._services[name] = self
48 def joinGroup(self, name):
49 raise Exception("No group for local services")
50 #GROUPS.setdefault(name, {})[self.__name] = self
53 def exists(cls, name):
54 return name in cls._services
57 def remove(cls, name):
59 cls._services.pop(name)
61 def exportMethod(self, method):
63 self._methods[method.__name__] = method
65 def abortResponse(self, error, description, origin, details):
66 if not tools.config['debug_mode']:
67 raise Exception("%s -- %s\n\n%s"%(origin, description, details))
71 class LocalService(object):
72 """ Proxy for local services.
74 Any instance of this class will behave like the single instance
77 __logger = logging.getLogger('service')
78 def __init__(self, name):
81 self._service = Service._services[name]
82 for method_name, method_definition in self._service._methods.items():
83 setattr(self, method_name, method_definition)
84 except KeyError, keyError:
85 self.__logger.error('This service does not exist: %s' % (str(keyError),) )
88 def __call__(self, method, *params):
89 return getattr(self, method)(*params)
91 class ExportService(object):
92 """ Proxy for exported services.
94 All methods here should take an AuthProxy as their first parameter. It
95 will be appended by the calling framework.
97 Note that this class has no direct proxy, capable of calling
98 eservice.method(). Rather, the proxy should call
99 dispatch(method,auth,params)
105 def __init__(self, name, audience=''):
106 ExportService._services[name] = self
109 def joinGroup(self, name):
110 ExportService._groups.setdefault(name, {})[self.__name] = self
113 def getService(cls,name):
114 return cls._services[name]
116 def dispatch(self, method, auth, params):
117 raise Exception("stub dispatch at %s" % self.__name)
119 def new_dispatch(self,method,auth,params):
120 raise Exception("stub dispatch at %s" % self.__name)
122 def abortResponse(self, error, description, origin, details):
123 if not tools.config['debug_mode']:
124 raise Exception("%s -- %s\n\n%s"%(origin, description, details))
128 LOG_NOTSET = 'notset'
129 LOG_DEBUG_RPC = 'debug_rpc'
135 LOG_CRITICAL = 'critical'
137 logging.DEBUG_RPC = logging.DEBUG - 2
138 logging.addLevelName(logging.DEBUG_RPC, 'DEBUG_RPC')
140 logging.TEST = logging.INFO - 5
141 logging.addLevelName(logging.TEST, 'TEST')
143 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, _NOTHING, DEFAULT = range(10)
144 #The background is set with 40 plus the number of the color, and the foreground with 30
145 #These are the sequences need to get colored ouput
146 RESET_SEQ = "\033[0m"
147 COLOR_SEQ = "\033[1;%dm"
149 COLOR_PATTERN = "%s%s%%s%s" % (COLOR_SEQ, COLOR_SEQ, RESET_SEQ)
150 LEVEL_COLOR_MAPPING = {
151 logging.DEBUG_RPC: (BLUE, WHITE),
152 logging.DEBUG: (BLUE, DEFAULT),
153 logging.INFO: (GREEN, DEFAULT),
154 logging.TEST: (WHITE, BLUE),
155 logging.WARNING: (YELLOW, DEFAULT),
156 logging.ERROR: (RED, DEFAULT),
157 logging.CRITICAL: (WHITE, RED),
160 class ColoredFormatter(logging.Formatter):
161 def format(self, record):
162 fg_color, bg_color = LEVEL_COLOR_MAPPING[record.levelno]
163 record.levelname = COLOR_PATTERN % (30 + fg_color, 40 + bg_color, record.levelname)
164 return logging.Formatter.format(self, record)
169 from tools.translate import resetlocale
172 logger = logging.getLogger()
173 # create a format for log messages and dates
174 format = '[%(asctime)s] %(levelname)s:%(name)s:%(message)s'
176 if tools.config['syslog']:
179 handler = logging.handlers.NTEventLogHandler("%s %s" % (release.description, release.version))
181 handler = logging.handlers.SysLogHandler('/dev/log')
182 format = '%s %s' % (release.description, release.version) \
183 + ':%(levelname)s:%(name)s:%(message)s'
185 elif tools.config['logfile']:
187 logf = tools.config['logfile']
189 dirname = os.path.dirname(logf)
190 if dirname and not os.path.isdir(dirname):
192 if tools.config['logrotate'] is not False:
193 handler = logging.handlers.TimedRotatingFileHandler(logf,'D',1,30)
194 elif os.name == 'posix':
195 handler = logging.handlers.WatchedFileHandler(logf)
197 handler = logging.handlers.FileHandler(logf)
198 except Exception, ex:
199 sys.stderr.write("ERROR: couldn't create the logfile directory. Logging to the standard output.\n")
200 handler = logging.StreamHandler(sys.stdout)
202 # Normal Handler on standard output
203 handler = logging.StreamHandler(sys.stdout)
205 if isinstance(handler, logging.StreamHandler) and os.isatty(handler.stream.fileno()):
206 formatter = ColoredFormatter(format)
208 formatter = logging.Formatter(format)
209 handler.setFormatter(formatter)
211 # add the handler to the root logger
212 logger.addHandler(handler)
213 logger.setLevel(int(tools.config['log_level'] or '0'))
217 class Logger(object):
219 warnings.warn("The netsvc.Logger API shouldn't be used anymore, please "
220 "use the standard `logging.getLogger` API instead",
221 PendingDeprecationWarning, stacklevel=2)
222 super(Logger, self).__init__()
224 def notifyChannel(self, name, level, msg):
225 warnings.warn("notifyChannel API shouldn't be used anymore, please use "
226 "the standard `logging` module instead",
227 PendingDeprecationWarning, stacklevel=2)
228 from service.web_services import common
230 log = logging.getLogger(tools.ustr(name))
232 if level in [LOG_DEBUG_RPC, LOG_TEST] and not hasattr(log, level):
233 fct = lambda msg, *args, **kwargs: log.log(getattr(logging, level.upper()), msg, *args, **kwargs)
234 setattr(log, level, fct)
237 level_method = getattr(log, level)
239 if isinstance(msg, Exception):
240 msg = tools.exception_to_unicode(msg)
243 msg = tools.ustr(msg).strip()
244 if level in (LOG_ERROR, LOG_CRITICAL) and tools.config.get_misc('debug','env_info',False):
245 msg = common().exp_get_server_environment() + "\n" + msg
247 result = msg.split('\n')
248 except UnicodeDecodeError:
249 result = msg.strip().split('\n')
252 for idx, s in enumerate(result):
253 level_method('[%02d]: %s' % (idx+1, s,))
255 level_method(result[0])
257 # TODO: perhaps reset the logger streams?
258 #if logrotate closes our files, we end up here..
261 # better ignore the exception and carry on..
264 def set_loglevel(self, level):
265 log = logging.getLogger()
266 log.setLevel(logging.INFO) # make sure next msg is printed
267 log.info("Log level changed to %s" % logging.getLevelName(level))
280 __logger = logging.getLogger('timer')
282 def setAlarm(self, fn, dt, db_name, *args, **kwargs):
283 wait = dt - time.time()
285 self.__logger.debug("Job scheduled in %.3g seconds for %s.%s" % (wait, fn.im_class.__name__, fn.func_name))
286 timer = threading.Timer(wait, fn, args, kwargs)
288 self._timers.setdefault(db_name, []).append(timer)
290 for db in self._timers:
291 for timer in self._timers[db]:
292 if not timer.isAlive():
293 self._timers[db].remove(timer)
296 def cancel(cls, db_name):
297 """Cancel all timers for a given database. If None passed, all timers are cancelled"""
298 for db in cls._timers:
299 if db_name is None or db == db_name:
300 for timer in cls._timers[db]:
310 """ Generic interface for all servers with an event loop etc.
311 Override this to impement http, net-rpc etc. servers.
313 Servers here must have threaded behaviour. start() must not block,
320 __logger = logging.getLogger('server')
323 if Server.__is_started:
324 raise Exception('All instances of servers must be inited before the startAll()')
325 Server.__servers.append(self)
328 self.__logger.debug("called stub Server.start")
331 self.__logger.debug("called stub Server.stop")
334 """ This function should return statistics about the server """
335 return "%s: No statistics" % str(self.__class__)
341 cls.__logger.info("Starting %d services" % len(cls.__servers))
342 for srv in cls.__servers:
344 cls.__is_started = True
348 if not cls.__is_started:
350 cls.__logger.info("Stopping %d services" % len(cls.__servers))
351 for srv in cls.__servers:
353 cls.__is_started = False
357 res = ["Servers %s" % ('stopped', 'started')[cls.__is_started]]
358 res.extend(srv.stats() for srv in cls.__servers)
359 return '\n'.join(res)
361 class OpenERPDispatcherException(Exception):
362 def __init__(self, exception, traceback):
363 self.exception = exception
364 self.traceback = traceback
366 class OpenERPDispatcher:
367 def log(self, title, msg):
368 Logger().notifyChannel('%s' % title, LOG_DEBUG_RPC, pformat(msg))
370 def dispatch(self, service_name, method, params):
372 self.log('service', service_name)
373 self.log('method', method)
374 self.log('params', params)
375 auth = getattr(self, 'auth_provider', None)
376 result = ExportService.getService(service_name).dispatch(method, auth, params)
377 self.log('result', result)
378 # We shouldn't marshall None,
383 self.log('exception', tools.exception_to_unicode(e))
384 tb = getattr(e, 'traceback', sys.exc_info())
385 tb_s = "".join(traceback.format_exception(*tb))
386 if tools.config['debug_mode']:
388 pdb.post_mortem(tb[2])
389 raise OpenERPDispatcherException(e, tb_s)
391 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: