Quellcode durchsuchen

Add settings & logger module

Yann Weber vor 9 Jahren
Ursprung
Commit
5d0ded158f
5 geänderte Dateien mit 332 neuen und 1 gelöschten Zeilen
  1. 2
    1
      lodel/leapi/datahandlers/base_classes.py
  2. 113
    0
      lodel/logger.py
  3. 145
    0
      lodel/settings.py
  4. 26
    0
      lodel/settings_format.py
  5. 46
    0
      settings.py

+ 2
- 1
lodel/leapi/datahandlers/base_classes.py Datei anzeigen

@@ -8,6 +8,7 @@ import copy
8 8
 import importlib
9 9
 import inspect
10 10
 
11
+from lodel import logger
11 12
 
12 13
 class FieldValidationError(Exception):
13 14
     pass
@@ -121,7 +122,7 @@ class DataHandler(object):
121 122
                 module = importlib.import_module('lodel.leapi.datahandlers.%s' % module_name)
122 123
                 for name, obj in inspect.getmembers(module):
123 124
                     if inspect.isclass(obj):
124
-                        print("Data handler found : %s in %s" % (name, module_name))
125
+                        logger.debug("Load data handler %s.%s" % (obj.__module__, obj.__name__))
125 126
                         cls.__base_handlers[name.lower()] = obj
126 127
         return copy.copy(cls.__base_handlers)
127 128
 

+ 113
- 0
lodel/logger.py Datei anzeigen

@@ -0,0 +1,113 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+import logging, logging.handlers
4
+import os.path
5
+from lodel.settings import Settings
6
+
7
+# Variables & constants definitions
8
+default_format = '%(asctime)-15s %(levelname)s %(_pathname)s:%(_lineno)s:%(_funcName)s() : %(message)s'
9
+simple_format = '%(asctime)-15s %(levelname)s : %(message)s'
10
+SECURITY_LOGLEVEL = 35
11
+logging.addLevelName(SECURITY_LOGLEVEL, 'SECURITY')
12
+handlers = dict() # Handlers list (generated from settings)
13
+
14
+# Fetching default root logger
15
+logger = logging.getLogger()
16
+
17
+# Setting options from Lodel.settings.Settings.logging
18
+def __init_from_settings():
19
+    # Disabled, because the custom format raises error (enable to give the _ preffixed arguments to logger resulting into a KeyError exception )
20
+    #logging.captureWarnings(True) # Log warnings
21
+
22
+    logger.setLevel(logging.DEBUG)
23
+    for name, logging_opt in Settings.logging.items():
24
+        add_handler(name, logging_opt)
25
+
26
+## @brief Add an handler, identified by a name, to a given logger 
27
+#
28
+# logging_opt is a dict with logger option. Allowed keys are : 
29
+# - filename : take a filepath as value and cause the use of a logging.handlers.RotatingFileHandler
30
+# - level : the minimum logging level for a logger, takes values [ 'DEBUG', 'INFO', 'WARNING', 'SECURITY', 'ERROR', 'CRITICAL' ]
31
+# - format : DONT USE THIS OPTION (or if you use it be sure to includes %(_pathname)s %(_lineno)s %(_funcName)s format variables in format string
32
+# - context : boolean, if True include the context (module:lineno:function_name) in the log format
33
+# @todo Move the logging_opt documentation somewhere related with settings
34
+# 
35
+# @param name str : The handler name
36
+# @param logging_opt dict : dict containing options ( see above )
37
+def add_handler(name, logging_opt):
38
+    logger = logging.getLogger()
39
+    if name in handlers:
40
+        raise KeyError("A handler named '%s' allready exists")
41
+
42
+    if 'filename' in logging_opt:
43
+        maxBytes = (1024 * 10) if 'maxBytes' not in logging_opt else logging_opt['maxBytes']
44
+        backupCount = 10 if 'backupCount' not in logging_opt else logging_opt['backupCount']
45
+
46
+        handler = logging.handlers.RotatingFileHandler(
47
+                                        logging_opt['filename'],
48
+                                        maxBytes = maxBytes,
49
+                                        backupCount = backupCount,
50
+                                        encoding = 'utf-8')
51
+    else:
52
+        handler = logging.StreamHandler()
53
+    
54
+    if 'level' in logging_opt:
55
+        handler.setLevel(getattr(logging, logging_opt['level'].upper()))
56
+
57
+    if 'format' in logging_opt:
58
+        formatter = logging.Formatter(logging_opt['format'])
59
+    else:
60
+        if 'context' in logging_opt and not logging_opt['context']:
61
+            formatter = logging.Formatter(simple_format)
62
+        else:
63
+            formatter = logging.Formatter(default_format)
64
+
65
+    handler.setFormatter(formatter)
66
+    handlers[name] = handler
67
+    logger.addHandler(handler)
68
+    
69
+
70
+## @brief Remove an handler generated from configuration (runtime logger configuration)
71
+# @param name str : handler name
72
+def remove_handler(name):
73
+    if name in handlers:
74
+        logger.removeHandler(handlers[name])
75
+    # else: can we do anything ?
76
+
77
+## @brief Utility function that disable unconditionnaly handlers that implies console output
78
+# @note In fact, this function disables handlers generated from settings wich are instances of logging.StreamHandler
79
+def remove_console_handlers():
80
+    for name, handler in handlers.items():
81
+        if isinstance(handler, logging.StreamHandler):
82
+            remove_handler(name)
83
+    
84
+
85
+# Utility functions
86
+
87
+## @brief Generic logging function
88
+# @param lvl int : Log severity
89
+# @param msg str : log message
90
+# @param *args : additional positionnal arguments
91
+# @param **kwargs : additional named arguments
92
+def log(lvl, msg, *args, **kwargs):
93
+    caller = logger.findCaller() # Opti warning : small overhead
94
+    extra = {
95
+        '_pathname': os.path.relpath(
96
+                                        caller[0],
97
+                                        start=Settings.lodel2_lib_path
98
+        ), # os.path.relpath add another small overhead
99
+        '_lineno': caller[1],
100
+        '_funcName': caller[2],
101
+    }
102
+    logger.log(lvl, msg, extra = extra, *args, **kwargs)
103
+
104
+def debug(msg, *args, **kwargs): log(logging.DEBUG, msg, *args, **kwargs)
105
+def info(msg, *args, **kwargs): log(logging.INFO, msg, *args, **kwargs)
106
+def warning(msg, *args, **kwargs): log(logging.WARNING, msg, *args, **kwargs)
107
+def security(msg, *args, **kwargs): log(SECURITY_LOGLEVEL, msg, *args, **kwargs)
108
+def error(msg, *args, **kwargs): log(logging.ERROR, msg, *args, **kwargs)
109
+def critical(msg, *args, **kwargs): log(logging.CRITICAL, msg, *args, **kwargs)
110
+
111
+# Initialisation triggering
112
+if len(handlers) == 0:
113
+    __init_from_settings()

+ 145
- 0
lodel/settings.py Datei anzeigen

@@ -0,0 +1,145 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+import types
4
+import warnings
5
+from . import settings_format
6
+
7
+## @package Lodel.settings
8
+#
9
+# @brief Defines stuff to handles Lodel2 configuration (see @ref lodel_settings )
10
+#
11
+# To access the confs use the Lodel.settings.Settings SettingsHandler instance
12
+
13
+## @brief A class designed to handles Lodel2 settings
14
+#
15
+# When instanciating a SettingsHandler, the new instance is filled with the content of settings.py (in the root directory of lodel2
16
+#
17
+# @warning You don't have to instanciate this class, you can access to the global instance with the Settings variable in this module
18
+# @todo broken stuff... Rewrite it
19
+# @todo Forbid module assignement in settings ! and disable tests about this
20
+# @todo Implements a type checking of config value
21
+# @todo Implements default values for config keys
22
+class SettingsHandler(object):
23
+    
24
+    ## @brief Shortcut
25
+    _allowed = settings_format.ALLOWED + settings_format.MANDATORY
26
+    ## @brief Shortcut
27
+    _mandatory = settings_format.MANDATORY
28
+
29
+    def __init__(self):
30
+        try:
31
+            import settings as default_settings
32
+            self._load_module(default_settings)
33
+        except ImportError:
34
+            warnings.warn("Unable to find global default settings")
35
+
36
+        ## @brief A flag set to True when the instance is fully loaded
37
+        self._set_loaded(False if len(self._missings()) > 0 else True)
38
+    
39
+    ## @brief Compat wrapper for getattr
40
+    def get(self, name):
41
+        return getattr(self, name)
42
+    
43
+    ## @brief Compat wrapper for setattr
44
+    def set(self, name, value):
45
+        return setattr(self, name, value)
46
+
47
+    ## @brief Load every module properties in the settings instance
48
+    #
49
+    # Load a module content into a SettingsHandler instance and checks that no mandatory settings are missing
50
+    # @note Example : <pre> import my_cool_settings;
51
+    # Settings._load_module(my_cool_settings);</pre>
52
+    # @param module module|None: a loaded module (if None just check for missing settings)
53
+    # @throw LookupError if invalid settings found or if mandatory settings are missing
54
+    def load_module(self, module = None):
55
+        if not(module is None):
56
+            self._load_module(module)
57
+        missings = self._missings()
58
+        if len(missings) > 0:
59
+            self._loaded = False
60
+            raise LookupError("Mandatory settings are missing : %s"%missings)
61
+        self._set_loaded(True)
62
+    
63
+    ## @brief supersede of default __setattr__ method
64
+    def __setattr__(self, name, value):
65
+        if not hasattr(self, name):
66
+            if name not in self._allowed:
67
+                raise LookupError("Invalid setting : %s"%name)
68
+        super().__setattr__(name, value)
69
+
70
+    ## @brief This method do the job for SettingsHandler.load_module()
71
+    #
72
+    # @note The difference with SettingsHandler.load_module() is that it didn't check if some settings are missing
73
+    # @throw LokkupError if an invalid settings is given
74
+    # @param module : a loaded module
75
+    def _load_module(self, module):
76
+        errors = []
77
+        fatal_errors = []
78
+        conf_dict = {
79
+            name: getattr(module, name)
80
+            for name in dir(module) 
81
+            if not name.startswith('__') and not isinstance(getattr(module, name), types.ModuleType)
82
+        }
83
+        for name, value in conf_dict.items():
84
+            try:
85
+                setattr(self, name, value)
86
+            except LookupError:
87
+                errors.append(name)
88
+        if len(errors) > 0:
89
+            err_msg = "Found invalid settings in %s : %s"%(module.__name__, errors)
90
+            raise LookupError(err_msg)
91
+
92
+    ## @brief Refresh the allowed and mandatory settings list
93
+    @classmethod
94
+    def _refresh_format(cls):
95
+        ## @brief Shortcut
96
+        cls._allowed = settings_format.ALLOWED + settings_format.MANDATORY
97
+        ## @brief Shortcut
98
+        cls._mandatory = settings_format.MANDATORY
99
+
100
+    ## @brief If some settings are missings return their names
101
+    # @return an array of string
102
+    def _missings(self):
103
+        return [ confname for confname in self._mandatory if not hasattr(self, confname) ]
104
+
105
+    def _set_loaded(self, value):
106
+        super().__setattr__('_loaded', bool(value))
107
+
108
+Settings = SettingsHandler()
109
+
110
+## @page lodel_settings Lodel SettingsHandler
111
+#
112
+# This page describe the way settings are handled in Lodel2.
113
+#
114
+# @section lodel_settings_files Lodel settings files
115
+#
116
+# - Lodel/settings.py defines the Lodel.settings package, the SettingsHandler class and the Lodel.settings.Settings instance
117
+# - Lodel/settings_format.py defines the mandatory and allowed configurations keys lists
118
+# - install/instance_settings.py is a model of the file that will be deployed in Lodel2 instances directories
119
+#
120
+# @section Using Lodel.settings.Settings SettingsHandler instance
121
+#
122
+# @subsection lodel_settings_without_loader Without loader
123
+#
124
+# Without any loader you can import Lodel.settings.Settings and acces its property with getattr (or . ) or with SettingsHandler.get() method.
125
+# In the same way you can set a settings by standart affectation of a propery or with SettingsHandler.set() method.
126
+#
127
+# @subsection lodel_settings_loader With a loader in a lodel2 instance
128
+#
129
+# The loader will import Lodel.settings.Settings and then calls the SettingsHandler.load_module() method to load the content of the instance_settings.py file into the SettingsHandler instance
130
+#
131
+# @subsection lodel_settings_example Examples
132
+#
133
+# <pre>
134
+# #!/usr/bin/python
135
+# from Lodel.settings import Settings
136
+# if Settings.debug:
137
+#   print("DEBUG")
138
+# # or
139
+# if Settings.get('debug'):
140
+#   print("DEBUG")
141
+# Settings.debug = False
142
+# # or
143
+# Settings.set('debug', False)
144
+# </pre>
145
+# 

+ 26
- 0
lodel/settings_format.py Datei anzeigen

@@ -0,0 +1,26 @@
1
+#-*- coding: utf-8 -*-
2
+## @package Lodel.settings_format Rules for settings
3
+
4
+## @brief List mandatory configurations keys
5
+MANDATORY = [
6
+    'debug',
7
+    'debug_sql',
8
+    'sitename',
9
+    'lodel2_lib_path',
10
+    'em_file',
11
+    'dynamic_code_file',
12
+    'ds_package',
13
+    'datasource',
14
+    'mh_classname',
15
+    'migration_options',
16
+    'base_path',
17
+    'plugins',
18
+    'logging',
19
+]
20
+
21
+## @brief List allowed (but not mandatory) configurations keys
22
+ALLOWED = [
23
+    'em_graph_output',
24
+    'em_graph_format',
25
+    'templates_base_dir'
26
+]

+ 46
- 0
settings.py Datei anzeigen

@@ -0,0 +1,46 @@
1
+#-*- coding:utf8 -*-
2
+## @package settings Configuration file
3
+
4
+import pymysql
5
+import os
6
+import os.path
7
+
8
+lodel2_lib_path = os.path.dirname(os.path.abspath(__file__))
9
+base_path = os.path.dirname(os.path.abspath(__file__))
10
+debug = False
11
+debug_sql = False
12
+
13
+plugins = ['dummy', 'dummy_auth']
14
+
15
+datasource = {
16
+    'default': {
17
+        'module':pymysql,
18
+        'host': None,
19
+        'user': None,
20
+        'passwd': None,
21
+        'db': None,
22
+    }
23
+}
24
+
25
+migration_options = {
26
+    'dryrun': False,
27
+    'foreign_keys': True,
28
+    'drop_if_exists': False,
29
+}
30
+
31
+em_graph_format = 'png'
32
+em_graph_output = '/tmp/em_%s_graph.png'
33
+
34
+logging = {
35
+    'stderr': {
36
+        'level': 'DEBUG',
37
+        'context': False,
38
+    },
39
+    'logfile': {
40
+        'level': 'DEBUG',
41
+        'filename': '/tmp/lodel2.log',
42
+        'maxBytes': 1024 * 50, # rotate at 50MB
43
+        'backupCount': 10, # keep at most 10 backup
44
+        'context': True, # if false use a simpler format string
45
+    }
46
+}

Laden…
Abbrechen
Speichern