Changeset 6578
- Timestamp:
- 6 Aug 2011, 11:11:41 (13 years ago)
- Location:
- main/waeup.sirp/trunk/src/waeup/sirp
- Files:
-
- 7 edited
Legend:
- Unmodified
- Added
- Removed
-
main/waeup.sirp/trunk/src/waeup/sirp/app.py
r6522 r6578 1 1 import grok 2 import logging3 import os4 2 from hurry.file.interfaces import IFileRetrieval 5 3 from zope.authentication.interfaces import IAuthentication … … 11 9 from waeup.sirp.imagestorage import ImageStorageFileRetrieval, ImageStorage 12 10 from waeup.sirp.interfaces import ( 13 IUniversity, IDataCenter, IWAeUPSIRPPluggable, 14 IDataCenterStorageMovedEvent, IObjectUpgradeEvent, ) 11 IUniversity, IWAeUPSIRPPluggable, IObjectUpgradeEvent, ) 15 12 from waeup.sirp.users import UserContainer 13 from waeup.sirp.utils.logger import Logger 16 14 from waeup.sirp.utils.helpers import attrs_to_fields 17 15 18 class University(grok.Application, grok.Container ):16 class University(grok.Application, grok.Container, Logger): 19 17 """A university. 20 18 """ … … 29 27 grok.local_utility( 30 28 ImageStorageFileRetrieval, provides = IFileRetrieval) 31 32 @property33 def logger(self):34 """The application logger.35 36 Returns a standard logger object as provided by :mod:`logging`37 module from the standard library.38 39 Other components can use this logger to perform log entries40 into the 'main' logfile.41 42 The logger is initialized the first time, it is called.43 """44 sitename = self.__name__45 loggername = 'waeup.sirp.%s' % sitename46 logger = logging.getLogger(loggername)47 if not logger.handlers:48 logger = self._setupLogger(logger)49 return logger50 29 51 30 def __init__(self, name=None, skin=None, title=None, … … 95 74 self.logger.info('Plugin update finished.') 96 75 return 97 98 def _setupLogger(self, logger):99 """Setup general application logger.100 101 The logfile will be stored in the datacenter logs/ dir.102 """103 logdir = os.path.join(self['datacenter'].storage, 'logs')104 if not os.path.exists(logdir):105 os.mkdir(logdir)106 filename = os.path.join(logdir, 'main.log')107 108 # Create a rotating file handler logger for application.109 handler = logging.handlers.RotatingFileHandler(110 filename, maxBytes=5*1024**1, backupCount=7)111 formatter = logging.Formatter(112 '%(asctime)s - %(levelname)s - %(message)s')113 handler.setFormatter(formatter)114 115 # Don't send log msgs to ancestors. This stops displaying116 # logmessages on the commandline.117 logger.propagate = False118 logger.addHandler(handler)119 return logger120 76 attrs_to_fields(University) 121 122 @grok.subscribe(IDataCenter, IDataCenterStorageMovedEvent)123 def handle_storage_move(obj, event):124 """Event handler, in case datacenter storage moves.125 126 We initialize the application log again, then.127 """128 app = grok.getSite()129 if app is None:130 return131 if obj is not app['datacenter']:132 return133 logger = app.logger134 logger.warn('Log Dir moved. Closing.')135 handlers = logger.handlers136 for handler in handlers:137 logger.removeHandler(handler)138 app._setupLogger(logger)139 logger.warn('Log file moved. Opening.')140 77 141 78 class ObjectUpgradeEvent(ObjectEvent): … … 143 80 """ 144 81 grok.implements(IObjectUpgradeEvent) 82 83 @grok.subscribe(University, grok.IObjectAddedEvent) 84 def handle_university_add(app, event): 85 app.logger.info('University `%s` added.' % getattr(app, '__name__', None)) 86 return -
main/waeup.sirp/trunk/src/waeup/sirp/applicants/root.py
r6500 r6578 27 27 from hurry.query.interfaces import IQuery 28 28 from zope.component import getUtility 29 from waeup.sirp.interfaces import IWAeUPSIRPPluggable , ISiteLoggers29 from waeup.sirp.interfaces import IWAeUPSIRPPluggable 30 30 from waeup.sirp.applicants.interfaces import IApplicantsRoot 31 31 from waeup.sirp.utils.helpers import get_current_principal 32 from waeup.sirp.utils.logger import Logger 32 33 33 class ApplicantsRoot(grok.Container ):34 class ApplicantsRoot(grok.Container, Logger): 34 35 """The root of applicants-related components. It contains only 35 36 containers for applicants. … … 39 40 local_roles = ['waeup.ApplicationsOfficer'] 40 41 41 @property 42 def logger(self): 43 """Get a logger for applicants. 44 """ 45 loggers = getUtility(ISiteLoggers).getLoggers() 46 if 'waeup.sirp.%s.applicants' not in loggers.keys(): 47 loggers.register('waeup.sirp.%s.applicants') 48 return loggers.getLogger('waeup.sirp.%s.applicants') 42 logger_name = 'waeup.sirp.${sitename}.applicants' 43 logger_filename = 'applicants.log' 49 44 50 45 def logger_info(self, target, ob_class, comment=None): -
main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_root.py
r6443 r6578 28 28 import unittest 29 29 from StringIO import StringIO 30 from zope.app.testing.functional import FunctionalTestCase 31 from zope.component.hooks import setSite, clearSite 30 from zope.component.hooks import setSite 32 31 from zope.interface.verify import verifyClass, verifyObject 33 from zope.site import LocalSiteManager34 32 from waeup.sirp.app import University 35 33 from waeup.sirp.applicants import ( … … 40 38 ApplicantsRoot, ApplicantsPlugin, 41 39 ) 42 from waeup.sirp.testing import FunctionalLayer 40 from waeup.sirp.testing import ( 41 FunctionalLayer, FunctionalTestCase, get_all_loggers, remove_new_loggers, 42 remove_logger) 43 43 44 44 … … 54 54 def setUp(self): 55 55 super(ApplicantsRootTestCase, self).setUp() 56 56 remove_logger('waeup.sirp.app.applicants') 57 self.old_loggers = get_all_loggers() 57 58 # Setup a sample site for each test 59 60 # Prepopulate the ZODB... 58 61 app = University() 59 self.dc_root = tempfile.mkdtemp()60 app['datacenter'].setStoragePath(self.dc_root)61 62 # Prepopulate the ZODB...63 62 self.getRootFolder()['app'] = app 64 63 self.app = self.getRootFolder()['app'] 65 64 setSite(self.app) 65 66 self.dc_root = tempfile.mkdtemp() 67 app['datacenter'].setStoragePath(self.dc_root) 68 66 69 return 67 70 68 71 def tearDown(self): 69 72 super(ApplicantsRootTestCase, self).tearDown() 73 remove_new_loggers(self.old_loggers) 70 74 shutil.rmtree(self.dc_root) 71 75 return -
main/waeup.sirp/trunk/src/waeup/sirp/datacenter.py
r6286 r6578 13 13 IDataCenterStorageMovedEvent) 14 14 from waeup.sirp.utils.helpers import copyFileSystemTree 15 16 class DataCenter(grok.Container): 15 from waeup.sirp.utils.logger import Logger 16 17 class DataCenter(grok.Container, Logger): 17 18 """A data center contains CSV files. 18 19 """ … … 20 21 storage = os.path.join(os.path.dirname(__file__), 'files') 21 22 22 @property 23 def logger(self): 24 """Get a logger for datacenter actions. 25 """ 26 # We need a different logger for every site... 27 site = grok.getSite() 28 sitename = getattr(site, '__name__', 'app') 29 loggername = 'waeup.sirp.%s.datacenter' % sitename 30 logger = logging.getLogger(loggername) 31 if not logger.handlers: 32 logger = self._setupLogger(logger) 33 return logger 23 logger_name = 'waeup.sirp.${sitename}.datacenter' 24 logger_filename = 'datacenter.log' 34 25 35 26 def __init__(self, *args, **kw): … … 62 53 """Create standard subdirs. 63 54 """ 64 for name in ['finished', 'unfinished' ]:55 for name in ['finished', 'unfinished', 'logs']: 65 56 path = os.path.join(self.storage, name) 66 57 if os.path.exists(path): … … 116 107 self._createSubDirs() 117 108 # Adjust logger... 118 logger = self.logger119 handlers = logger.handlers120 for handler in handlers:121 logger.removeHandler(handler)122 self._setupLogger(logger)109 #logger = self.logger 110 #handlers = logger.handlers 111 #for handler in handlers: 112 # logger.removeHandler(handler) 113 #self._setupLogger(logger) 123 114 grok.notify(DataCenterStorageMovedEvent(self)) 124 115 return not_copied -
main/waeup.sirp/trunk/src/waeup/sirp/testing.py
r6577 r6578 36 36 if key not in old_loggers] 37 37 for key in new_loggers: 38 logger = logging.getLogger(key) 39 for handler in logger.handlers: 40 handler.close() 41 del logger 38 42 del logging.root.manager.loggerDict[key] 39 43 return 44 45 def remove_logger(name): 46 """Remove logger with name `name`. 47 48 Use is safe. If the logger does not exist nothing happens. 49 """ 50 if name in logging.root.manager.loggerDict.keys(): 51 del logging.root.manager.loggerDict[name] 52 return 53 40 54 41 55 def setUpZope(test=None): … … 265 279 """ 266 280 281 def setUp(self): 282 super(FunctionalTestCase, self).setUp() 283 self.functional_old_loggers = get_all_loggers() 284 return 285 286 def tearDown(self): 287 super(FunctionalTestCase, self).tearDown() 288 remove_new_loggers(self.functional_old_loggers) 289 return 290 267 291 def assertMatches(self, want, got, checker=checker, 268 292 optionflags=optionflags): -
main/waeup.sirp/trunk/src/waeup/sirp/utils/logger.py
r6481 r6578 22 22 """ 23 23 Convenience stuff for logging. 24 25 Main component of :mod:`waeup.sirp.utils.logging` is a mix-in class 26 :class:`waeup.sirp.utils.logging.Logger`. Classes derived (also) from 27 that mix-in provide a `logger` attribute that returns a regular Python 28 logger logging to a rotating log file stored in the datacenter storage 29 path. 30 31 Deriving components (classes) should set their own `logger_name` and 32 `logger_filename` attribute. 33 34 The `logger_name` tells under which name the logger should be 35 registered Python-wise. This is usually a dotted name string like 36 ``waeup.sirp.${sitename}.mycomponent`` which should be unique. If you 37 pick a name already used by another component, trouble is ahead. The 38 ``${sitename}`` chunk of the name can be set literally like this. The 39 logger machinery will turn it into some real site name at time of 40 logging. 41 42 The `logger_filename` attribute tells how the logfile should be 43 named. This should be some base filename like 44 ``mycomponent.log``. Please note, that some logfile names are already 45 used: ``main.log``, ``applications.log``, and ``datacenter.log``. 46 47 The `Logger` mix-in also cares for updating the logging handlers when 48 a datacenter location moves. That means you do not have to write your 49 own event handlers for the purpose. Just derive from `Logger`, set 50 your `logger_name` and `logger_filename` attribute and off you go:: 51 52 from waeup.sirp.utils.logger import Logger 53 54 class MyComponent(object, Logger): 55 # Yes that's a complete working class 56 logger_name = 'waeup.sirp.${sitename}.mycomponent 57 logger_filename = 'mycomponent.log' 58 59 def do_something(self): 60 # demomstrate how to use logging from methods 61 self.logger.info('About to do something') 62 try: 63 # Do something here 64 except IOError: 65 self.logger.warn('Something went wrong') 66 return 67 self.logger.info('I did it') 68 69 As you can see from that example, methods of the class can log 70 messages simply by calling `self.logger`. 71 72 The datacenter and its storage are created automatically when you 73 create a :class:`waeup.sirp.app.University`. This also means that 74 logging with the `Logger` mix-in will work only inside so-called sites 75 (`University` instances put into ZODB are such `sites`). 76 77 Other components in this module help to make everything work. 24 78 """ 25 79 import os 26 80 import grok 27 81 import logging 28 from zope.interface import Interface, Attribute 29 from waeup.sirp.interfaces import ISiteLoggers, ILogger 30 31 class SiteLoggers(object): 32 33 grok.implements(ISiteLoggers) 34 35 @property 36 def loggers(self): 37 return self._loggers.values() 38 39 def __init__(self): 40 self._loggers = dict() 41 42 def getLogger(self, filename): 43 waeup_logger = self._loggers.get(filename, None) 44 return waeup_logger.logger 45 46 def register(self, loggername, filename=None, site=None, **kw): 47 if loggername in self._loggers.keys(): 48 raise KeyError('Logger "%s" exists already.' % loggername) 49 self._loggers[loggername] = WAeUPLogger( 50 loggername, filename=filename, site=site, **kw) 51 return 52 53 def unregister(self, name): 54 if name in self._loggers.keys(): 55 del self._loggers[name] 56 return 57 58 def keys(self): 59 return self._loggers.keys() 60 61 class WAeUPLogger(object): 62 63 grok.implements(ILogger) 64 65 _logger = None 66 67 def __init__(self, name, filename=None, site=None, **options): 68 self.name = name 69 self.filename = filename 70 if filename is None: 71 if '.' in name: 72 name = name.rsplit('.', 1)[1] 73 self.filename = "%s.log" % name 74 self.site = site 75 self.max_bytes = options.get('maxBytes', 5 * 1024 ** 2) 76 self.backup_count = options.get('backupCount', 5) 77 self.options = options 78 return 82 from string import Template 83 from zope import schema 84 from zope.component import queryUtility 85 from zope.interface import Interface, Attribute, implements 86 from waeup.sirp.interfaces import IDataCenter, IDataCenterStorageMovedEvent 87 88 #: Default logfile size 89 MAX_BYTES = 5 * 1024 ** 2 90 91 #: Default num of backup files 92 BACKUP_COUNT = 5 93 94 class ILogger(Interface): 95 logger_name = schema.TextLine( 96 title = u'A Python logger name') 97 logger_filename = schema.TextLine( 98 title = u'A filename for the log file to use (basename)') 99 logger = Attribute("Get a Python logger instance already set up") 100 def logger_setup(logger): 101 """Setup a logger. 102 103 `logger` is an instance of :class:`logging.Logger`. 104 """ 105 106 def logger_get_logfile_path(): 107 """Get path to logfile used. 108 109 Return `None` if the file path cannot be computed. 110 """ 111 112 def logger_get_logdir(): 113 """Get the directory of the logfile. 114 115 Return `None` if the directory path cannot be computed. 116 """ 117 118 class Logger(object): 119 """Mixin-class that for logging support. 120 121 Classes that (also) inherit from this class provide support for 122 logging with waeup sites. 123 124 By default a `logger` attribute is provided which returns a 125 regular Python logger. This logger has already registered a file 126 rotating log handler that writes log messages to a file `main.log` 127 in datacenters ``log/`` directory. This is the main log file also 128 used by other components. Therefore you can pick another filename 129 by setting the `logger_filename` attribute. 130 131 All methods and attributes of this mix-in start with ``logger_`` 132 in order not to interfere with already existing names of a class. 133 134 Method names do not follow the usual Zope habit (CamelCase) but 135 PEP8 convention (lower_case_with_underscores). 136 """ 137 138 #: The filename to use when logging. 139 logger_filename = 'main.log' 140 141 #: The Python logger name used when 142 #: logging. ``'waeup.sirp.${sitename}'`` by default. You can use the 143 #: ``${sitename}`` placeholder in that string, which will be 144 #: replaced by the actual used site name. 145 logger_name = 'waeup.sirp.${sitename}' 146 implements(ILogger) 79 147 80 148 @property 81 149 def logger(self): 82 if self._needSetup(): 83 self._setup() 84 return logging.getLogger(self._loggername) 85 86 def setup(self): 87 return self._setup() 88 89 def update(self, **options): 90 self._logger = None # unset to be set up again on next access. 91 # XXX: set options 92 pass 93 94 def _needSetup(self): 95 if self._logger is None: 96 return True 97 if not '__app' in self._logger.name: 98 return False 99 if getattr(self.site, '__name__', None) is not None: 100 return True 101 return False 102 103 def _removeHandlers(self): 104 handlers = self._logger.handlers 150 """Get a logger instance. 151 152 Returns a standard logger object as provided by :mod:`logging` 153 module from the standard library. 154 155 Other components can use this logger to perform log entries 156 into the logfile of this component. 157 158 The logger is initialized the first time it is called. 159 160 The logger is only available when used inside a site. 161 162 .. note:: The logger default level is 163 :data:`logging.WARN`. Use 164 :meth:`logging.Logger.setLevel` to set a different level. 165 """ 166 site = grok.getSite() 167 sitename = '%s' % getattr(site, '__name__', None) 168 loggername = Template(self.logger_name).substitute( 169 dict(sitename=sitename)) 170 logger = logging.getLogger(loggername) 171 if site is None: 172 return logger 173 if len(logger.handlers) != 1: 174 handlers = [x for x in logger.handlers] 175 for handler in handlers: 176 handler.flush() 177 handler.close() 178 logger.removeHandler(handler) 179 logger = self.logger_setup(logger) 180 return logger 181 182 def logger_setup(self, logger): 183 """Setup logger. 184 185 The logfile will be stored in the datacenter logs/ dir. 186 """ 187 filename = self.logger_get_logfile_path() 188 if filename is None: 189 return 190 collector = queryUtility(ILoggerCollector) 191 if collector is not None: 192 site = grok.getSite() 193 collector.registerLogger(site, self) 194 195 # Create a rotating file handler logger. 196 handler = logging.handlers.RotatingFileHandler( 197 filename, maxBytes=MAX_BYTES, backupCount=BACKUP_COUNT) 198 formatter = logging.Formatter( 199 '%(asctime)s - %(levelname)s - %(message)s') 200 handler.setFormatter(formatter) 201 202 # Don't send log msgs to ancestors. This stops displaying 203 # logmessages on the commandline. 204 logger.propagate = False 205 logger.addHandler(handler) 206 return logger 207 208 def logger_get_logfile_path(self): 209 """Get the path to the logfile used. 210 211 Returns the path to a file in local sites datacenter ``log/`` 212 directory (dependent on :meth:`logger_get_logdir`) and with 213 :attr:`logger_filename` as basename. 214 215 Override this method if you want a complete different 216 computation of the logfile path. If you only want a different 217 logfile name, set :attr:`logger_filename`. If you only want a 218 different path to the logfile override 219 :meth:`logger_get_logdir` instead. 220 221 Returns ``None`` if no logdir can be fetched. 222 223 .. note:: creates the logfile dir if it does not exist. 224 225 """ 226 logdir = self.logger_get_logdir() 227 if logdir is None: 228 return None 229 return os.path.join(logdir, self.logger_filename) 230 231 def logger_get_logdir(self): 232 """Get log dir where logfile should be put. 233 234 Returns the path to the logfile directory. If no site is set, 235 ``None`` is returned. The same applies, if the site has no 236 datacenter. 237 238 If the dir dies not exist already it will be created. Only the 239 last part of the directory path will be created. 240 """ 241 site = grok.getSite() 242 if site is None: 243 return None 244 datacenter = site.get('datacenter', None) 245 if datacenter is None: 246 return None 247 logdir = os.path.join(datacenter.storage, 'logs') 248 if not os.path.exists(logdir): 249 os.mkdir(logdir) 250 return logdir 251 252 def logger_logfile_changed(self): 253 """React on logfile location change. 254 255 If the logfile changed, we can set a different logfile. While 256 changing the logfile is a rather critical operation you might 257 not do often in production use, we have to cope with that 258 especially in tests. 259 260 What this method does by default (unless you override it): 261 262 - It fetches the current logger and 263 264 - Removes flushes, closes, and removes all handlers 265 266 - Sets up new handler(s). 267 268 All this, of course, requires to be 'in a site'. 269 270 Use this method to handle moves of datacenters, for instance 271 by writing an appropriate event handler. 272 """ 273 logger = self.logger 274 self.logger_shutdown() 275 self.logger_setup(logger) 276 return 277 278 def logger_shutdown(self): 279 """Remove all specific logger setup. 280 """ 281 logger = self.logger 282 handlers = [x for x in logger.handlers] 105 283 for handler in handlers: 106 self._logger.removeHandler(handler) 107 108 def _setup(self): 109 if self.site is None: 110 self.site = grok.getSite() 111 self._loggername = self.name 112 if not '.' in self._loggername: 113 self._loggername = 'waeup.sirp.%s.%s' % ('%s', self._loggername) 114 if '%s' in self._loggername: 115 site_name = getattr(self.site, '__name__', '__app') 116 if site_name is None: 117 site_name = '__app' 118 self._loggername = self._loggername % (site_name,) 119 self._logger = logging.getLogger(self._loggername) 120 return self._update() 121 122 def _update(self): 123 self._removeHandlers() # Remove any old handlers 124 self._logdir = os.path.join(self.site['datacenter'].storage, 'logs') 125 if not os.path.exists(self._logdir): 126 os.mkdir(self._logdir) 127 filename = os.path.join(self._logdir, self.filename) 128 129 # Create a rotating file handler logger for datacenter. 130 default_handler = logging.handlers.RotatingFileHandler( 131 filename, maxBytes=self.max_bytes, backupCount=self.backup_count) 132 self.handler = self.options.get('handler', default_handler) 133 134 default_formatter = logging.Formatter( 135 '%(asctime)s - %(levelname)s - %(message)s') 136 formatter = self.options.get('formatter', default_formatter) 137 self.handler.setFormatter(formatter) 138 139 self._logger.addHandler(self.handler) 140 self._logger.propagate = self.options.get('propagate', False) 141 self._logger.setLevel(self.options.get('level', logging.DEBUG)) 142 return self._logger 143 144 #: A place where we store seen loggers. 145 SiteLoggersRepository = dict() 146 147 class SiteLoggersFinder(object): 148 grok.provides(ISiteLoggers) 149 150 def getLoggers(self, site=None): 151 if site is None: 152 site = grok.getSite() 153 if site not in SiteLoggersRepository.keys(): 154 SiteLoggersRepository[site] = SiteLoggers() 155 return SiteLoggersRepository[site] 156 157 grok.global_utility(SiteLoggersFinder, provides=ISiteLoggers) 284 handler.flush() 285 handler.close() 286 logger.removeHandler(handler) 287 collector = queryUtility(ILoggerCollector) 288 if collector is not None: 289 collector.unregisterLogger(grok.getSite(), self) 290 return 291 292 293 class ILoggerCollector(Interface): 294 295 def getLoggers(site): 296 """Return all loggers registered for `site`. 297 """ 298 299 def registerLogger(site, logging_component): 300 """Register a logging component residing in `site`. 301 """ 302 303 def unregisterLogger(site, logging_component): 304 """Unregister a logger. 305 """ 306 307 class LoggerCollector(dict, grok.GlobalUtility): 308 """A global utility providing `ILoggerCollector`. 309 310 A logging collector collects logging components. This helps to 311 inform them when a logfile location changes. 312 313 Logging components are registered per site they belong to. 314 """ 315 316 implements(ILoggerCollector) 317 318 def getLoggers(self, site): 319 name = getattr(site, '__name__', None) 320 if name is None: 321 return [] 322 if name not in self.keys(): 323 return [] 324 return self[name] 325 326 def registerLogger(self, site, logging_component): 327 name = getattr(site, '__name__', None) 328 if name is None: 329 return 330 if not name in self.keys(): 331 # new component 332 self[name] = [] 333 if logging_component in self[name]: 334 # already registered 335 return 336 self[name].append(logging_component) 337 return 338 339 def unregisterLogger(self, site, logging_component): 340 name = getattr(site, '__name__', None) 341 if name is None or name not in self.keys(): 342 return 343 if logging_component not in self[name]: 344 return 345 self[name].remove(logging_component) 346 return 347 348 @grok.subscribe(IDataCenter, IDataCenterStorageMovedEvent) 349 def handle_datacenter_storage_move(obj, event): 350 """Event handler, in case datacenter storage moves. 351 352 By default all our logfiles (yes, we produce a whole bunch of it) 353 are located in a ``log/`` dir of a local datacenter, the 354 datacenter 'storage'. If this path changes because the datacenter 355 is moved an appropriate event is triggered and we can react. 356 357 Via the global ILoggerCollector utility, a small piece that allows 358 self-registering of logging components, we can lookup components 359 whose logfile path has to be set up anew. 360 361 Each component we call has to provide ILogger or, more specific, 362 the :meth:`logger_logfile_changed` method of this interface. 363 """ 364 site = grok.getSite() 365 if site is None: 366 return 367 collector = queryUtility(ILoggerCollector) 368 loggers = collector.getLoggers(site) 369 for logger in loggers: 370 if hasattr(logger, 'logger_logfile_changed'): 371 logger.logger_logfile_changed() 372 return -
main/waeup.sirp/trunk/src/waeup/sirp/utils/tests/test_logger.py
r6481 r6578 1 ## 2 ## test_logger.py 3 ## Login : <uli@pu.smp.net> 4 ## Started on Mon Jun 13 14:40:04 2011 Uli Fouquet 5 ## $Id$ 6 ## 7 ## Copyright (C) 2011 Uli Fouquet 8 ## This program is free software; you can redistribute it and/or modify 9 ## it under the terms of the GNU General Public License as published by 10 ## the Free Software Foundation; either version 2 of the License, or 11 ## (at your option) any later version. 12 ## 13 ## This program is distributed in the hope that it will be useful, 14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of 15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 ## GNU General Public License for more details. 17 ## 18 ## You should have received a copy of the GNU General Public License 19 ## along with this program; if not, write to the Free Software 20 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 ## 1 # Tests for waeup.sirp.utils.logger 22 2 import logging 23 3 import os 24 import re25 4 import shutil 26 5 import tempfile 27 from zope.app.testing.functional import FunctionalTestCase28 6 from zope.component import queryUtility 29 7 from zope.component.hooks import setSite, clearSite 30 8 from zope.interface.verify import verifyClass, verifyObject 31 9 from waeup.sirp.app import University 32 from waeup.sirp.interfaces import ISiteLoggers, ILogger 33 from waeup.sirp.testing import FunctionalLayer, doctestsuite_for_module 10 from waeup.sirp.testing import ( 11 FunctionalLayer, FunctionalTestCase, get_all_loggers, 12 remove_new_loggers, remove_logger) 13 34 14 from waeup.sirp.utils.logger import ( 35 SiteLoggers, WAeUPLogger, SiteLoggersRepository) 36 37 class SiteLoggersTests(FunctionalTestCase): 15 Logger, MAX_BYTES, BACKUP_COUNT, ILoggerCollector, LoggerCollector, 16 ILogger) 17 18 class FakeComponent(object): 19 # A component that can use the Logger mixin-class 20 def do_something(self): 21 self.logger.info('msg from do_something') 22 23 class LoggingComponent(FakeComponent, Logger): 24 # A component that supports logging by mixing in Logger 25 logger_name = 'sample.${sitename}' 26 logger_filename = 'sample.log' 27 28 class LoggersTests(FunctionalTestCase): 38 29 39 30 layer = FunctionalLayer 40 31 41 32 def setUp(self): 42 super(SiteLoggersTests, self).setUp() 33 super(LoggersTests, self).setUp() 34 self.old_loggers = get_all_loggers() 35 # Setup a sample site for each test 36 app = University() 37 self.dc_root = tempfile.mkdtemp() 38 self.dc_root2 = None 39 app['datacenter'].setStoragePath(self.dc_root) 40 41 # Prepopulate the ZODB... 42 self.getRootFolder()['app'] = app 43 self.app = self.getRootFolder()['app'] 44 self.workdir = tempfile.mkdtemp() 45 return 46 47 def tearDown(self): 48 super(LoggersTests, self).tearDown() 49 remove_new_loggers(self.old_loggers) 50 shutil.rmtree(self.workdir) 51 shutil.rmtree(self.dc_root) 52 clearSite() 53 return 54 55 def clear_logger(self, logger): 56 del_list = [x for x in logger.handlers] 57 for handler in del_list: 58 logger.removeHandler(handler) 59 return 60 61 def test_iface(self): 62 setSite(self.app) 63 logger = LoggingComponent() 64 verifyClass(ILogger, Logger) 65 verifyObject(ILogger, logger) 66 return 67 68 def test_logger_prop_no_site(self): 69 # If not in a site, we will get a logger without handlers 70 logger = Logger().logger 71 self.assertTrue(isinstance(logger, logging.Logger)) 72 self.assertEqual(len(logger.handlers), 0) 73 return 74 75 def test_site_no_site_name(self): 76 # If a site has not name we will get a logger anyway 77 setSite(self.app) 78 del self.app.__name__ 79 logger = Logger().logger 80 self.assertTrue(logger is not None) 81 return 82 83 def test_get_logdir(self): 84 # We can get a logdir 85 setSite(self.app) 86 logger = Logger() 87 logdir = logger.logger_get_logdir() 88 expected_logdir = os.path.join( 89 self.app['datacenter'].storage, 'logs') 90 self.assertEqual(logdir, expected_logdir) 91 return 92 93 def test_get_logdir_no_site(self): 94 # We cannot get a logdir without a site 95 logger = Logger() 96 logdir = logger.logger_get_logdir() 97 self.assertTrue(logdir is None) 98 return 99 100 def test_get_logdir_no_datacenter(self): 101 # Having a site, is not enough. We also need a datacenter in it. 102 setSite(self.app) 103 del self.app['datacenter'] 104 logger = Logger() 105 logdir = logger.logger_get_logdir() 106 self.assertTrue(logdir is None) 107 return 108 109 def test_get_logdir_create_dir(self): 110 # If the logdirectory does not exist, we will create it 111 setSite(self.app) 112 logger = Logger() 113 expected_logdir = os.path.join( 114 self.app['datacenter'].storage, 'logs') 115 if os.path.exists(expected_logdir): 116 shutil.rmtree(expected_logdir) 117 logdir = logger.logger_get_logdir() 118 exists_after = os.path.exists(expected_logdir) 119 self.assertEqual(logdir, expected_logdir) 120 self.assertTrue(exists_after is True) 121 return 122 123 def test_get_logfile(self): 124 # We can get a logfile 125 setSite(self.app) 126 logger = Logger() 127 expected_filepath = os.path.join( 128 self.app['datacenter'].storage, 'logs', 'main.log') 129 path = logger.logger_get_logfile_path() 130 self.assertEqual(expected_filepath, path) 131 return 132 133 def test_get_logfile_no_dir(self): 134 # We cannot get a logfilepath if there is no dir for it 135 setSite(self.app) 136 logger = Logger() 137 del self.app['datacenter'] 138 path = logger.logger_get_logfile_path() 139 self.assertTrue(path is None) 140 return 141 142 def test_setup(self): 143 # We can setup a logger. 144 mylogger = logging.getLogger('test.sample') 145 setSite(self.app) 146 logger = Logger() 147 result = logger.logger_setup(mylogger) 148 self.assertEqual(len(result.handlers), 1) 149 handler = result.handlers[0] 150 self.assertEqual(handler.maxBytes, MAX_BYTES) 151 self.assertEqual(handler.backupCount, BACKUP_COUNT) 152 self.assertTrue(result.propagate is False) 153 return 154 155 def test_setup_no_site(self): 156 # Without a site we get no logger from setup 157 mylogger = logging.getLogger('test.sample') 158 logger = Logger() 159 result = logger.logger_setup(mylogger) 160 self.assertTrue(result is None) 161 return 162 163 def test_logfile_change(self): 164 # When the logfile location changes, logger_logfile_changed can react 165 setSite(self.app) 166 logger = Logger() 167 logger.logger.warn('Warning 1') # Log something to old logfile 168 filename1 = logger.logger.handlers[0].baseFilename 169 content1 = open(filename1, 'r').read() 170 # Now move the logfile 171 self.dc_root2 = tempfile.mkdtemp() 172 self.app['datacenter'].setStoragePath(self.dc_root2) 173 logger.logger_logfile_changed() 174 logger.logger.warn('Warning 2') # Log something to new logfile 175 filename2 = logger.logger.handlers[0].baseFilename 176 content2 = open(filename2, 'r').read() 177 self.assertTrue('Warning 1' in content1) 178 self.assertTrue('Warning 2' in content2) 179 self.assertTrue('Warning 1' not in content2) 180 self.assertTrue('Warning 2' not in content1) 181 self.assertTrue(filename1 != filename2) 182 return 183 184 185 class LoggersFunctionalTests(FunctionalTestCase): 186 # Check loggers with real components using them 187 188 layer = FunctionalLayer 189 190 def setUp(self): 191 super(LoggersFunctionalTests, self).setUp() 43 192 44 193 # Setup a sample site for each test … … 50 199 self.getRootFolder()['app'] = app 51 200 self.app = self.getRootFolder()['app'] 52 53 201 self.workdir = tempfile.mkdtemp() 54 SiteLoggersRepository.clear() 202 203 self.app['mycomponent'] = LoggingComponent() 204 self.component = self.app['mycomponent'] 205 setSite(self.app) 206 self.old_loggers = get_all_loggers() 55 207 return 56 208 57 209 def tearDown(self): 58 super(SiteLoggersTests, self).tearDown() 210 super(LoggersFunctionalTests, self).tearDown() 211 remove_new_loggers(self.old_loggers) 59 212 shutil.rmtree(self.workdir) 60 213 shutil.rmtree(self.dc_root) … … 62 215 return 63 216 64 def test_iface(self): 65 loggers = SiteLoggers() 66 verifyClass(ISiteLoggers, SiteLoggers) 67 verifyObject(ISiteLoggers, loggers) 68 return 69 70 def test_loggers(self): 71 loggers = SiteLoggers() 72 loggers.register('sample1') 73 loggers.register('sample2') 74 result = loggers.loggers 75 assert len(result) == 2 76 77 def test_register(self): 78 loggers = SiteLoggers() 79 loggers.register('sample') 80 assert 'sample' in loggers._loggers.keys() 81 82 def test_register_duplicate(self): 83 loggers = SiteLoggers() 84 loggers.register('sample') 85 self.assertRaises( 86 KeyError, 87 loggers.register, 'sample') 88 89 def test_unregister(self): 90 loggers = SiteLoggers() 91 loggers.register('sample') 92 loggers.unregister('sample') 93 self.assertEqual(loggers._loggers, {}) 94 95 def test_utility_no_site(self): 96 # make sure we get loggers even if no site is defined/set 97 util = queryUtility(ISiteLoggers) 98 loggers = util.getLoggers() 99 assert util is not None 100 assert loggers is not None 101 assert None in SiteLoggersRepository.keys() 102 103 def test_utility(self): 104 # make sute we get loggers for a certain site 105 setSite(self.app) 106 util = queryUtility(ISiteLoggers) 107 loggers = util.getLoggers() 108 assert util is not None 109 assert loggers is not None 110 assert self.app in SiteLoggersRepository.keys() 111 112 def test_utility_multiple(self): 113 # make sure we get the same loggers for same site 114 setSite(self.app) 115 self.assertEqual(SiteLoggersRepository.keys(), []) 116 util = queryUtility(ISiteLoggers) 117 loggers1 = util.getLoggers() 118 loggers2 = util.getLoggers() 119 assert loggers1 is loggers2 120 121 def test_utility_given_site(self): 122 # make sure a passed site is respected 123 util = queryUtility(ISiteLoggers) 124 loggers = util.getLoggers(site=self.app) 125 assert loggers is not None 126 assert self.app in SiteLoggersRepository.keys() 127 128 class LoggerTests(FunctionalTestCase): 217 def test_component_can_get_logger(self): 218 # Components can get a logger 219 logger = self.component.logger 220 self.assertTrue(logger is not None) 221 self.assertEqual(len(logger.handlers), 1) 222 self.assertEqual(logger.name, 'sample.app') 223 return 224 225 def test_component_handler_setup(self): 226 # handlers of components are setup 227 handler = self.component.logger.handlers[0] 228 self.assertTrue(isinstance( 229 handler, logging.handlers.RotatingFileHandler)) 230 self.assertEqual(handler.maxBytes, MAX_BYTES) 231 self.assertEqual(handler.backupCount, BACKUP_COUNT) 232 self.assertTrue(handler.baseFilename.endswith('logs/sample.log')) 233 return 234 235 def test_component_can_log(self): 236 # a method of some 'loggerized' instance can log like this. 237 self.component.logger.setLevel(logging.INFO) 238 self.component.do_something() 239 handler = logging.getLogger('sample.app').handlers[0] 240 logfile = handler.baseFilename 241 content = open(logfile, 'rb').read() 242 self.assertTrue(content.endswith(' - INFO - msg from do_something\n')) 243 return 244 245 class LoggerCollectorTests(FunctionalTestCase): 129 246 130 247 layer = FunctionalLayer 131 248 132 249 def setUp(self): 133 super(Logger Tests, self).setUp()134 250 super(LoggerCollectorTests, self).setUp() 251 self.old_loggers = get_all_loggers() 135 252 # Setup a sample site for each test 136 253 app = University() … … 141 258 self.getRootFolder()['app'] = app 142 259 self.app = self.getRootFolder()['app'] 143 144 260 self.workdir = tempfile.mkdtemp() 261 262 self.app['mycomponent'] = LoggingComponent() 263 self.component = self.app['mycomponent'] 264 setSite(self.app) 265 266 self.component = LoggingComponent() 267 268 145 269 return 146 270 147 271 def tearDown(self): 148 super(LoggerTests, self).tearDown() 272 super(LoggerCollectorTests, self).tearDown() 273 remove_new_loggers(self.old_loggers) 149 274 shutil.rmtree(self.workdir) 150 275 shutil.rmtree(self.dc_root) … … 152 277 return 153 278 154 def test_iface(self): 155 setSite(self.app) 156 logger = WAeUPLogger('sample') 157 verifyClass(ILogger, WAeUPLogger) 158 verifyObject(ILogger, logger) 159 return 160 161 def test_logger_attr(self): 162 setSite(self.app) 163 logger = WAeUPLogger('sample') 164 raw_logger = logger.logger 165 assert isinstance(raw_logger, logging.Logger) 166 167 def test_site_name(self): 168 setSite(self.app) 169 logger = WAeUPLogger('waeup.sirp.%s.sample', site=self.app) 170 self.assertEqual(logger.logger.name, 'waeup.sirp.app.sample') 171 172 def test_site_no_name(self): 173 # While a site has no name, we get __app as placeholder 174 setSite(self.app) 175 self.app.__name__ = None 176 logger = WAeUPLogger('waeup.sirp.%s.sample', site=self.app) 177 self.assertEqual(logger.logger.name, 'waeup.sirp.__app.sample') 178 179 def test_site_changing_name(self): 180 # Changing the sitename (from None to a value) will be reflected 181 setSite(self.app) 182 self.app.__name__ = None 183 logger = WAeUPLogger('waeup.sirp.%s.sample', site=self.app) 184 self.assertEqual(logger.logger.name, 'waeup.sirp.__app.sample') 185 self.app.__name__ = 'myapp' 186 self.assertEqual(logger.logger.name, 'waeup.sirp.myapp.sample') 187 assert len(logger.logger.handlers) > 0 188 189 def test_loggername_expansion(self): 190 setSite(self.app) 191 logger = WAeUPLogger('sample', site=self.app) 192 logger.logger # Trigger setup 193 self.assertEqual(logger._loggername, 'waeup.sirp.app.sample') 194 195 def test_filename(self): 196 setSite(self.app) 197 logger = WAeUPLogger('sample') 198 logger.logger # Trigger setup 199 self.assertEqual(logger.filename, 'sample.log') 200 201 def test_filename_from_dotted_name(self): 202 setSite(self.app) 203 logger = WAeUPLogger('waeup.sirp.%s.sample') 204 logger.logger # Trigger setup 205 logfile = os.path.join(self.dc_root, 'logs', 'sample.log') 206 self.assertEqual(logger.filename, 'sample.log') 207 assert os.path.isfile(logfile) 208 209 def test_option_filename(self): 210 setSite(self.app) 211 logger = WAeUPLogger('sample', filename='my.log') 212 logger.logger # Trigger setup 213 logfile = os.path.join(self.dc_root, 'logs', 'my.log') 214 self.assertEqual(logger.filename, 'my.log') 215 assert os.path.isfile(logfile) 216 217 def test_option_propagate(self): 218 logger = WAeUPLogger('sample', site=self.app) 219 assert logger.logger.propagate is False # default 220 logger = WAeUPLogger('sample', site=self.app, propagate=True) 221 assert logger.logger.propagate is True 222 logger = WAeUPLogger('sample', site=self.app, propagate=False) 223 assert logger.logger.propagate is False 224 225 def test_logging(self): 226 logger = WAeUPLogger('sample', site=self.app) 227 logger.logger.info('Hi there!') 228 logfile = os.path.join(self.dc_root, 'logs', 'sample.log') 229 assert os.path.isfile(logfile) 230 contents = open(logfile, 'rb').read() 231 self.assertTrue(re.match( 232 '^....-..-.. ..:..:..,... - INFO - Hi there!\n$', contents)) 279 def test_ifaces(self): 280 collector = LoggerCollector() 281 verifyClass(ILoggerCollector, LoggerCollector) 282 verifyObject(ILoggerCollector, collector) 283 return 284 285 def test_get_utility(self): 286 # We can get a logger collector via utility lookup 287 util1 = queryUtility(ILoggerCollector, default=None) 288 util2 = queryUtility(ILoggerCollector, default=None) 289 self.assertTrue(util1 is util2) 290 self.assertTrue(isinstance(util1, LoggerCollector)) 291 return 292 293 def test_get_loggers(self): 294 # We can get loggers from a logger collector 295 collector = LoggerCollector() 296 result0 = collector.getLoggers(None) 297 result1 = collector.getLoggers('not-a-site') 298 result2 = collector.getLoggers(self.app) 299 collector.registerLogger(self.app, self.component) 300 result3 = collector.getLoggers(self.app) 301 self.assertEqual(result0, []) 302 self.assertEqual(result1, []) 303 self.assertEqual(result2, []) 304 self.assertEqual(result3, [self.component]) 305 self.assertTrue(result3[0] is self.component) 306 return 307 308 def test_register_logger(self): 309 # We can register loggers by name 310 collector = LoggerCollector() 311 collector.registerLogger(None, self.component) # Should have no effect 312 collector.registerLogger(object(), self.component) # Same here 313 collector.registerLogger(self.app, self.component) 314 self.assertEqual(['app'], collector.keys()) 315 return 316 317 def test_register_logger_double(self): 318 # Registering the same component twice gives one entry, not two 319 collector = LoggerCollector() 320 collector.registerLogger(self.app, self.component) 321 collector.registerLogger(self.app, self.component) 322 self.assertEqual(len(collector['app']), 1) 323 return 324 325 def test_unregister_logger(self): 326 # We can also unregister loggers 327 collector = LoggerCollector() 328 collector.registerLogger(self.app, self.component) 329 collector.unregisterLogger(self.app, self.component) 330 result = collector.getLoggers(self.app) 331 self.assertEqual(result, []) 332 return 333 334 def test_unregister_logger_invalid(self): 335 # We can cope with unregistration of non-existing entries. 336 collector = LoggerCollector() 337 collector.registerLogger(self.app, self.component) 338 collector.unregisterLogger(None, self.component) 339 collector.unregisterLogger(self.app, 'nonsense') 340 self.assertEqual(len(collector), 1) 341 return 342 343 class LogfileChangeTests(FunctionalTestCase): 344 345 layer = FunctionalLayer 346 347 def setUp(self): 348 super(LogfileChangeTests, self).setUp() 349 remove_logger('waeup.sirp.app') # In case it lingers around 350 self.old_loggers = get_all_loggers() 351 # Setup a sample site for each test 352 app = University() 353 self.dc_root = tempfile.mkdtemp() 354 self.dc_root_new = None 355 app['datacenter'].setStoragePath(self.dc_root) 356 357 # Prepopulate the ZODB... 358 self.getRootFolder()['app'] = app 359 self.app = self.getRootFolder()['app'] 360 self.workdir = tempfile.mkdtemp() 361 362 self.app['mycomponent'] = LoggingComponent() 363 self.component = self.app['mycomponent'] 364 setSite(self.app) 365 366 self.component = LoggingComponent() 367 return 368 369 def tearDown(self): 370 super(LogfileChangeTests, self).tearDown() 371 remove_new_loggers(self.old_loggers) 372 shutil.rmtree(self.workdir) 373 shutil.rmtree(self.dc_root) 374 if self.dc_root_new is not None: 375 shutil.rmtree(self.dc_root_new) 376 clearSite() 377 return 378 379 def test_storage_move(self): 380 # When a datacenter storage (its path) changes, we have to 381 # move the logfiles as well. 382 #return 383 filename1 = self.component.logger.handlers[0].baseFilename 384 self.dc_root_new = tempfile.mkdtemp() 385 self.app['datacenter'].setStoragePath(self.dc_root_new) 386 filename2 = self.component.logger.handlers[0].baseFilename 387 self.assertTrue(filename1 != filename2) 388 self.assertTrue(filename2.startswith(self.dc_root_new)) 389 return
Note: See TracChangeset for help on using the changeset viewer.