1 /** 2 * Module containing the default logger factory and associated components. 3 */ 4 module slf4d.default_provider.factory; 5 6 import slf4d; 7 import std.regex; 8 import core.sync.rwmutex; 9 10 /** 11 * A basic LoggerFactory implementation that just creates a `Logger` with a 12 * handler and pre-set logging level. It also includes methods for defining 13 * custom logging levels for individual module patterns, so that you can, for 14 * example, only show debug messages from a single module. 15 */ 16 shared class DefaultLoggerFactory : LoggerFactory { 17 private LogHandler handler; 18 private Level rootLoggingLevel; 19 private ModuleLoggingLevelMapping[] moduleMappings; 20 private ReadWriteMutex mutex; 21 22 /** 23 * Constructs the factory with the given handler, and optionally a root 24 * logging level. 25 * Params: 26 * handler = The handler that will handle all log messages. 27 * rootLoggingLevel = The root logging level, which is the default level 28 * assigned to all Loggers produced by this factory, unless a module 29 * specific level is set. 30 */ 31 public this(shared LogHandler handler, Level rootLoggingLevel = Levels.INFO) { 32 this.handler = handler; 33 this.rootLoggingLevel = rootLoggingLevel; 34 this.mutex = new shared ReadWriteMutex(); 35 } 36 37 /** 38 * Sets the root logging level for this factory. 39 * Params: 40 * level = The root logging level. 41 */ 42 public void setRootLevel(Level level) { 43 synchronized(this.mutex.writer) { 44 this.rootLoggingLevel = level; 45 } 46 } 47 48 /** 49 * Sets the logging level for a given module pattern regular expression. 50 * When this factory prepares a logger for a given module (or logger name 51 * if the developer has defined a custom name), it will give that logger a 52 * logging level according to the configured `rootLoggingLevel` of this 53 * factory. Then, if it finds a module pattern set using this function, it 54 * will use the declared level instead. Module patterns are searched in the 55 * order that they're defined. 56 * Params: 57 * modulePattern = The module pattern to match against. This is compiled 58 * as a regular expression. 59 * level = The logging level to apply to Loggers whose name matches the 60 * given module pattern. 61 */ 62 public void setModuleLevel(string modulePattern, Level level) { 63 synchronized(this.mutex.writer) { 64 auto safeUnsharedMappings = cast(ModuleLoggingLevelMapping[]*) &this.moduleMappings; 65 *safeUnsharedMappings ~= ModuleLoggingLevelMapping( 66 regex(modulePattern), 67 level 68 ); 69 } 70 } 71 72 /** 73 * Sets the logging level for a given module prefix. This is a convenience 74 * wrapper around `setModuleLevel`, which simply prepares a regular 75 * expression that matches the beginning of a logger's module (or name) 76 * with the given `modulePrefix`. 77 * Params: 78 * modulePrefix = The module's prefix. 79 * level = The logging level to apply to Loggers whose name matches the 80 * given module prefix. 81 */ 82 public void setModuleLevelPrefix(string modulePrefix, Level level) { 83 string pattern = "^" ~ replaceAll(modulePrefix, regex("\\."), "\\.") ~ ".*"; 84 this.setModuleLevel(pattern, level); 85 } 86 87 /** 88 * Gets a Logger. The Logger's level is set according to the root logging 89 * level, unless there exists a module-specific level that was set via 90 * `setModuleLevel`. 91 * Params: 92 * name = The logger's name, which defaults to the current module name. 93 * Returns: The Logger. 94 */ 95 public Logger getLogger(string name = __MODULE__) shared { 96 import std.algorithm : startsWith; 97 synchronized(this.mutex.reader) { 98 Level level = this.rootLoggingLevel; 99 auto safeUnsharedMappings = cast(ModuleLoggingLevelMapping[]*) &this.moduleMappings; 100 foreach (mapping; *safeUnsharedMappings) { 101 if (matchFirst(name, mapping.modulePattern)) { 102 level = mapping.level; 103 } 104 } 105 return Logger(this.handler, level, name); 106 } 107 } 108 } 109 110 /** 111 * A simple mapping struct that maps a module pattern string to a particular 112 * logging level, used by the DefaultLoggerFactory to configure module-specific 113 * logging levels. 114 */ 115 package struct ModuleLoggingLevelMapping { 116 private Regex!char modulePattern; 117 private immutable Level level; 118 } 119 120 unittest { 121 import slf4d.handler; 122 auto handler = new CachingLogHandler(); 123 124 auto f1 = new DefaultLoggerFactory(handler, Levels.INFO); 125 Logger log1 = f1.getLogger(); 126 log1.debug_("Testing"); 127 assert(handler.messageCount() == 0); 128 log1.warn("Testing"); 129 assert(handler.messageCount() == 1); 130 handler.reset(); 131 132 f1.setRootLevel(Levels.TRACE); 133 Logger log2 = f1.getLogger(); 134 log2.debug_("Testing"); 135 assert(handler.messageCount() == 1); 136 handler.reset(); 137 138 // Test setModuleLevel regular expression matching. 139 auto f2 = new DefaultLoggerFactory(handler, Levels.WARN); 140 f2.setModuleLevel("^my_module\\.a$", Levels.DEBUG); 141 f2.setModuleLevel("^my_module\\.b$", Levels.TRACE); 142 Logger log3 = f2.getLogger("my_module.a"); // First a logger that matches the first module level. 143 log3.info("Testing"); 144 assert(handler.messageCount() == 1); 145 log3.debug_("Testing debug"); 146 assert(handler.messageCount() == 2); 147 log3.trace("Testing trace. This should not be added to the messages."); 148 assert(handler.messageCount() == 2); 149 handler.reset(); 150 151 Logger log4 = f2.getLogger("my_module.b"); // Then a logger that matches the second module level. 152 log4.info("Testing"); 153 assert(handler.messageCount() == 1); 154 log4.info("Testing trace"); 155 assert(handler.messageCount() == 2); 156 handler.reset(); 157 158 Logger log5 = f2.getLogger("other_module"); // Finally, a logger that shouldn't match either, and uses root level. 159 log5.debug_("This should not appear."); 160 assert(handler.messageCount() == 0); 161 log5.trace("This should also not appear."); 162 assert(handler.messageCount() == 0); 163 log5.warn("This should appear!"); 164 assert(handler.messageCount() == 1); 165 handler.reset(); 166 167 // Test setModuleLevelPrefix matching. 168 auto f3 = new DefaultLoggerFactory(handler, Levels.WARN); 169 f3.setModuleLevelPrefix("first_mod.second_mod", Levels.TRACE); 170 f3.setModuleLevelPrefix("first_mod.third_mod.", Levels.DEBUG); 171 Logger log6 = f3.getLogger("first_mod.second_mod"); // A logger that matches the first module level. 172 log6.trace("This should appear!"); 173 assert(handler.messageCount() == 1); 174 handler.reset(); 175 176 Logger log7 = f3.getLogger("first_mod.second_mod.another"); // A logger that still matches the first module. 177 log7.trace("This should appear!"); 178 assert(handler.messageCount() == 1); 179 handler.reset(); 180 181 Logger log8 = f3.getLogger("first_mod.third_mod.a"); // A logger that matches the second module. 182 log8.trace("This should not appear."); 183 assert(handler.messageCount() == 0); 184 log8.debug_("This should appear!"); 185 assert(handler.messageCount() == 1); 186 handler.reset(); 187 188 Logger log9 = f3.getLogger("other_module"); // A logger that doesn't match any module levels. 189 log9.trace("This should not appear."); 190 log9.info("This should also not appear."); 191 assert(handler.messageCount() == 0); 192 log9.error("This should appear!"); 193 assert(handler.messageCount() == 1); 194 handler.reset(); 195 }