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
33 from pprint import pformat
36 class Service(object):
37 """ Base class for *Local* services
39 Functionality here is trusted, no authentication.
42 def __init__(self, name, audience=''):
43 Service._services[name] = self
47 def joinGroup(self, name):
48 raise Exception("No group for local services")
49 #GROUPS.setdefault(name, {})[self.__name] = self
52 def exists(cls, name):
53 return name in cls._services
56 def remove(cls, name):
58 cls._services.pop(name)
60 def exportMethod(self, method):
62 self._methods[method.__name__] = method
64 def abortResponse(self, error, description, origin, details):
65 if not tools.config['debug_mode']:
66 raise Exception("%s -- %s\n\n%s"%(origin, description, details))
70 class LocalService(object):
71 """ Proxy for local services.
73 Any instance of this class will behave like the single instance
76 __logger = logging.getLogger('service')
77 def __init__(self, name):
80 self._service = Service._services[name]
81 for method_name, method_definition in self._service._methods.items():
82 setattr(self, method_name, method_definition)
83 except KeyError, keyError:
84 self.__logger.error('This service does not exist: %s' % (str(keyError),) )
87 def __call__(self, method, *params):
88 return getattr(self, method)(*params)
90 class ExportService(object):
91 """ Proxy for exported services.
93 All methods here should take an AuthProxy as their first parameter. It
94 will be appended by the calling framework.
96 Note that this class has no direct proxy, capable of calling
97 eservice.method(). Rather, the proxy should call
98 dispatch(method,auth,params)
104 def __init__(self, name, audience=''):
105 ExportService._services[name] = self
108 def joinGroup(self, name):
109 ExportService._groups.setdefault(name, {})[self.__name] = self
112 def getService(cls,name):
113 return cls._services[name]
115 def dispatch(self, method, auth, params):
116 raise Exception("stub dispatch at %s" % self.__name)
118 def new_dispatch(self,method,auth,params):
119 raise Exception("stub dispatch at %s" % self.__name)
121 def abortResponse(self, error, description, origin, details):
122 if not tools.config['debug_mode']:
123 raise Exception("%s -- %s\n\n%s"%(origin, description, details))
127 LOG_NOTSET = 'notset'
128 LOG_DEBUG_RPC = 'debug_rpc'
133 LOG_CRITICAL = 'critical'
135 logging.DEBUG_RPC = logging.DEBUG - 2
136 logging.addLevelName(logging.DEBUG_RPC, 'DEBUG_RPC')
140 from tools.translate import resetlocale
143 logger = logging.getLogger()
144 # create a format for log messages and dates
145 formatter = logging.Formatter('[%(asctime)s] %(levelname)s:%(name)s:%(message)s')
147 if tools.config['syslog']:
150 handler = logging.handlers.NTEventLogHandler("%s %s" %
151 (release.description,
154 handler = logging.handlers.SysLogHandler('/dev/log')
155 formatter = logging.Formatter("%s %s" % (release.description, release.version) + ':%(levelname)s:%(name)s:%(message)s')
157 elif tools.config['logfile']:
159 logf = tools.config['logfile']
161 dirname = os.path.dirname(logf)
162 if dirname and not os.path.isdir(dirname):
164 if tools.config['logrotate'] is not False:
165 handler = logging.handlers.TimedRotatingFileHandler(logf,'D',1,30)
166 elif os.name == 'posix':
167 handler = logging.handlers.WatchedFileHandler(logf)
169 handler = logging.handlers.FileHandler(logf)
170 except Exception, ex:
171 sys.stderr.write("ERROR: couldn't create the logfile directory. Logging to the standard output.\n")
172 handler = logging.StreamHandler(sys.stdout)
174 # Normal Handler on standard output
175 handler = logging.StreamHandler(sys.stdout)
178 # tell the handler to use this format
179 handler.setFormatter(formatter)
181 # add the handler to the root logger
182 logger.addHandler(handler)
183 logger.setLevel(int(tools.config['log_level'] or '0'))
185 if (not isinstance(handler, logging.FileHandler)) and os.name != 'nt':
186 # change color of level names
187 # uses of ANSI color codes
188 # see http://pueblo.sourceforge.net/doc/manual/ansi_color_codes.html
189 # maybe use http://code.activestate.com/recipes/574451/
190 colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', None, 'default']
191 foreground = lambda f: 30 + colors.index(f)
192 background = lambda f: 40 + colors.index(f)
195 'DEBUG_RPC': ('blue', 'white'),
196 'DEBUG': ('blue', 'default'),
197 'INFO': ('green', 'default'),
198 'WARNING': ('yellow', 'default'),
199 'ERROR': ('red', 'default'),
200 'CRITICAL': ('white', 'red'),
203 for level, (fg, bg) in mapping.items():
204 msg = "\x1b[%dm\x1b[%dm%s\x1b[0m" % (foreground(fg), background(bg), level)
205 logging.addLevelName(getattr(logging, level), msg)
208 class Logger(object):
210 warnings.warn("The netsvc.Logger API shouldn't be used anymore, please "
211 "use the standard `logging.getLogger` API instead",
212 PendingDeprecationWarning, stacklevel=2)
213 super(Logger, self).__init__()
215 def notifyChannel(self, name, level, msg):
216 warnings.warn("notifyChannel API shouldn't be used anymore, please use "
217 "the standard `logging` module instead",
218 PendingDeprecationWarning, stacklevel=2)
219 from service.web_services import common
221 log = logging.getLogger(tools.ustr(name))
223 if level == LOG_DEBUG_RPC and not hasattr(log, level):
224 fct = lambda msg, *args, **kwargs: log.log(logging.DEBUG_RPC, msg, *args, **kwargs)
225 setattr(log, LOG_DEBUG_RPC, fct)
227 level_method = getattr(log, level)
229 if isinstance(msg, Exception):
230 msg = tools.exception_to_unicode(msg)
233 msg = tools.ustr(msg).strip()
234 if level in (LOG_ERROR, LOG_CRITICAL) and tools.config.get_misc('debug','env_info',False):
235 msg = common().exp_get_server_environment() + "\n" + msg
237 result = msg.split('\n')
238 except UnicodeDecodeError:
239 result = msg.strip().split('\n')
242 for idx, s in enumerate(result):
243 level_method('[%02d]: %s' % (idx+1, s,))
245 level_method(result[0])
247 # TODO: perhaps reset the logger streams?
248 #if logrotate closes our files, we end up here..
251 # better ignore the exception and carry on..
254 def set_loglevel(self, level):
255 log = logging.getLogger()
256 log.setLevel(logging.INFO) # make sure next msg is printed
257 log.info("Log level changed to %s" % logging.getLevelName(level))
270 __logger = logging.getLogger('timer')
272 def setAlarm(self, fn, dt, db_name, *args, **kwargs):
273 wait = dt - time.time()
275 self.__logger.debug("Job scheduled in %.3g seconds for %s.%s" % (wait, fn.im_class.__name__, fn.func_name))
276 timer = threading.Timer(wait, fn, args, kwargs)
278 self._timers.setdefault(db_name, []).append(timer)
280 for db in self._timers:
281 for timer in self._timers[db]:
282 if not timer.isAlive():
283 self._timers[db].remove(timer)
286 def cancel(cls, db_name):
287 """Cancel all timers for a given database. If None passed, all timers are cancelled"""
288 for db in cls._timers:
289 if db_name is None or db == db_name:
290 for timer in cls._timers[db]:
300 """ Generic interface for all servers with an event loop etc.
301 Override this to impement http, net-rpc etc. servers.
303 Servers here must have threaded behaviour. start() must not block,
310 __logger = logging.getLogger('server')
313 if Server.__is_started:
314 raise Exception('All instances of servers must be inited before the startAll()')
315 Server.__servers.append(self)
318 self.__logger.debug("called stub Server.start")
321 self.__logger.debug("called stub Server.stop")
324 """ This function should return statistics about the server """
325 return "%s: No statistics" % str(self.__class__)
331 cls.__logger.info("Starting %d services" % len(cls.__servers))
332 for srv in cls.__servers:
334 cls.__is_started = True
338 if not cls.__is_started:
340 cls.__logger.info("Stopping %d services" % len(cls.__servers))
341 for srv in cls.__servers:
343 cls.__is_started = False
347 res = ["Servers %s" % ('stopped', 'started')[cls.__is_started]]
348 res.extend(srv.stats() for srv in cls.__servers)
349 return '\n'.join(res)
351 class OpenERPDispatcherException(Exception):
352 def __init__(self, exception, traceback):
353 self.exception = exception
354 self.traceback = traceback
356 class OpenERPDispatcher:
357 def log(self, title, msg):
358 Logger().notifyChannel('%s' % title, LOG_DEBUG_RPC, pformat(msg))
360 def dispatch(self, service_name, method, params):
362 self.log('service', service_name)
363 self.log('method', method)
364 self.log('params', params)
365 auth = getattr(self, 'auth_provider', None)
366 result = ExportService.getService(service_name).dispatch(method, auth, params)
367 self.log('result', result)
368 # We shouldn't marshall None,
373 self.log('exception', tools.exception_to_unicode(e))
374 tb = getattr(e, 'traceback', sys.exc_info())
375 tb_s = "".join(traceback.format_exception(*tb))
376 if tools.config['debug_mode']:
378 pdb.post_mortem(tb[2])
379 raise OpenERPDispatcherException(e, tb_s)
381 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: