1 /** 2 * onyx-log: the generic, fast, multithreading logging library. 3 * 4 * Output Controllers implementation. 5 * 6 * Copyright: © 2015- Oleg Nykytenko 7 * License: MIT license. License terms written in "LICENSE.txt" file 8 * Authors: Oleg Nykytenko, oleg.nykytenko@gmail.com 9 */ 10 11 module onyx.core.controller; 12 13 14 @system: 15 package: 16 17 import onyx.log; 18 import onyx.core.logger; 19 import onyx.bundle; 20 21 22 struct Controller 23 { 24 import std.stdio; 25 import std.file; 26 27 /* Currently Logging file */ 28 File activeFile; 29 /* Do file rolling */ 30 Rollover rollover; 31 /* Name */ 32 mixin(addVal!(immutable string, "name", "public")); 33 34 /** 35 * Primary constructor 36 * 37 * Save config path and name 38 */ 39 this(immutable Bundle bundle) 40 { 41 rollover = createRollover(bundle); 42 43 _name = bundle.glKeys[0]; 44 45 createPath(rollover.activeFilePath()); 46 activeFile = File(rollover.activeFilePath(), "a"); 47 } 48 49 /** 50 * Extract logger type from bundle 51 * 52 * Throws: BundleException, LogCreateException 53 */ 54 Rollover createRollover(immutable Bundle bundle) 55 { 56 try 57 { 58 if (!bundle.isValuePresent(bundle.glKeys[0], "rolling")) 59 { 60 return new Rollover(bundle); 61 } 62 string rollingType = bundle.value(bundle.glKeys[0], "rolling"); 63 RolloverFactory f = cast(RolloverFactory)Object.factory("onyx.core.controller." ~ rollingType ~ "Factory"); 64 65 if (f is null) 66 { 67 throw new LogCreateException("Error create log rolling: " ~ rollingType ~ " is Illegal rolling type from config bundle."); 68 } 69 Rollover r = f.factory(bundle); 70 return r; 71 } 72 catch (BundleException e) 73 { 74 throw new BundleException("Error in Config bundle. [" ~ name ~ "]:" ~ e.msg); 75 } 76 catch (Exception e) 77 { 78 throw new LogCreateException("Error in creating rolling for logger: " ~ name ~ ": " ~ e.msg); 79 } 80 } 81 82 /** 83 * Extract logger type from bundle 84 * 85 * Throws: $(D ErrnoException) 86 */ 87 void saveMsg(string msg) 88 { 89 if (!activeFile.name.exists) 90 { 91 activeFile = File(rollover.activeFilePath(), "w"); 92 } 93 else if (rollover.roll(msg)) 94 { 95 activeFile.detach(); 96 rollover.carry(); 97 activeFile = File(rollover.activeFilePath(), "w"); 98 } 99 else if (!activeFile.isOpen()) 100 { 101 activeFile.open("a"); 102 } 103 activeFile.writeln(msg); 104 } 105 106 /** 107 * Flush log file 108 */ 109 void flush() 110 { 111 activeFile.flush; 112 } 113 } 114 115 116 /** 117 * Create file 118 */ 119 void createPath(string fileFullName) 120 { 121 import std.path:dirName; 122 import std.file:mkdirRecurse; 123 import std.file:exists; 124 125 string dir = dirName(fileFullName); 126 127 if ((dir.length != 0) && (!exists(dir))) 128 { 129 mkdirRecurse(dir); 130 } 131 } 132 133 134 135 /** 136 * Rollover Creating interface 137 * 138 * Use by Controller for create new Rollover 139 * 140 * ==================================================================================== 141 */ 142 interface RolloverFactory 143 { 144 Rollover factory(immutable Bundle bundle); 145 } 146 147 148 /** 149 * Base rollover class 150 */ 151 class Rollover 152 { 153 import std.path; 154 import std.string; 155 import std.typecons; 156 157 /* Control of size and number of log files */ 158 immutable Bundle bundle; 159 /* Path and file name template */ 160 mixin(addVal!(immutable string, "path", "protected")); 161 /* Work diroctory */ 162 mixin(addVal!(immutable string, "dir", "protected")); 163 /* Log file base name template */ 164 mixin(addVal!(immutable string, "baseName", "protected")); 165 /* Log file extension */ 166 mixin(addVal!(immutable string, "ext", "protected")); 167 /* Path to main log file */ 168 mixin(addVar!(string, "activeFilePath", "protected", "protected")); 169 170 /** 171 * Primary constructor 172 */ 173 this(immutable Bundle bundle) 174 { 175 this.bundle = bundle; 176 _path = bundle.value(bundle.glKeys[0], "fileName"); 177 auto fileInfo = parseConfigFilePath(path); 178 _dir = fileInfo[0]; 179 _baseName = fileInfo[1]; 180 _ext = fileInfo[2]; 181 init(); 182 } 183 184 /** 185 * Rollover start init 186 */ 187 void init() 188 { 189 activeFilePath = path; 190 } 191 192 /** 193 * Parse configuration file path and base name and save to members 194 */ 195 auto parseConfigFilePath(string rawConfigFile) 196 { 197 string configFile = buildNormalizedPath(rawConfigFile); 198 199 immutable dir = configFile.dirName; 200 string fullBaseName = std.path.baseName(configFile); 201 auto ldotPos = fullBaseName.lastIndexOf("."); 202 immutable ext = (ldotPos > 0)?fullBaseName[ldotPos+1..$]:"log"; 203 immutable baseName = (ldotPos > 0)?fullBaseName[0..ldotPos]:fullBaseName; 204 205 return tuple(dir, baseName, ext); 206 } 207 208 /** 209 * Do files rolling by default 210 */ 211 bool roll(string msg) 212 { 213 return false; 214 } 215 216 void carry(){} 217 } 218 219 220 /** 221 * Factory for SizeBasedRollover 222 * 223 * ==================================================================================== 224 */ 225 class SizeBasedRolloverFactory:RolloverFactory 226 { 227 override Rollover factory(immutable Bundle bundle) 228 { 229 return new SizeBasedRollover(bundle); 230 } 231 } 232 233 234 /** 235 * Control of size and number of log files 236 */ 237 class SizeBasedRollover:Rollover 238 { 239 import std.file; 240 import std.regex; 241 import std.algorithm; 242 import std.array; 243 244 245 /* Max size of one file */ 246 uint maxSize; 247 /* Max number of working files */ 248 uint maxHistory; 249 /* Primary constructor */ 250 this(immutable Bundle bundle) 251 { 252 super(bundle); 253 maxSize = extractSize(bundle.value(bundle.glKeys[0], "maxSize")); 254 maxHistory = bundle.value!int(bundle.glKeys[0], "maxHistory"); 255 } 256 257 258 /** 259 * Extract number fron configuration data 260 * 261 * Throws: LogException 262 */ 263 uint extractSize(string size) 264 { 265 import std.uni : toLower; 266 import std.uni : toUpper; 267 import std.conv; 268 269 uint nsize = 0; 270 auto n = matchAll(size, regex(`\d*`)); 271 if (!n.empty && (n.hit.length != 0)) 272 { 273 nsize = to!int(n.hit); 274 auto m = matchAll(size, regex(`\D{1}`)); 275 if (!m.empty && (m.hit.length != 0)) 276 { 277 switch(m.hit.toUpper) 278 { 279 case "K": 280 nsize *= KB; 281 break; 282 case "M": 283 nsize *= MB; 284 break; 285 case "G": 286 nsize *= GB; 287 break; 288 case "T": 289 nsize *= TB; 290 break; 291 case "P": 292 nsize *= PB; 293 break; 294 default: 295 throw new LogException("In Logger configuration uncorrect number: " ~ size); 296 } 297 } 298 } 299 return nsize; 300 } 301 302 303 enum KB = 1024; 304 enum MB = KB*1024; 305 enum GB = MB*1024; 306 enum TB = GB*1024; 307 enum PB = TB*1024; 308 309 /** 310 * Scan work directory 311 * save needed files to pool 312 */ 313 string[] scanDir() 314 { 315 import std.algorithm.sorting:sort; 316 bool tc(string s) 317 { 318 static import std.path; 319 auto base = std.path.baseName(s); 320 auto m = matchAll(base, regex(baseName ~ `\d*\.` ~ ext)); 321 if (m.empty || (m.hit != base)) 322 { 323 return false; 324 } 325 return true; 326 } 327 328 return std.file.dirEntries(dir, SpanMode.shallow) 329 .filter!(a => a.isFile) 330 .map!(a => a.name) 331 .filter!(a => tc(a)) 332 .array 333 .sort!("a < b") 334 .array; 335 } 336 337 /** 338 * Do files rolling by size 339 */ 340 override 341 bool roll(string msg) 342 { 343 auto filePool = scanDir(); 344 if (filePool.length == 0) 345 { 346 return false; 347 } 348 if ((getSize(filePool[0]) + msg.length) >= maxSize) 349 { 350 //if ((filePool.front.getSize == 0) throw 351 if (filePool.length >= maxHistory) 352 { 353 std.file.remove(filePool[$-1]); 354 filePool = filePool[0..$-1]; 355 } 356 //carry(filePool); 357 return true; 358 } 359 return false; 360 } 361 362 /** 363 * Rename log files 364 */ 365 override 366 void carry() 367 { 368 import std.conv; 369 import std.path; 370 371 auto filePool = scanDir(); 372 foreach_reverse(ref file; filePool) 373 { 374 auto newFile = dir ~ dirSeparator ~ baseName ~ to!string(extractNum(file)+1) ~ "." ~ ext; 375 std.file.rename(file, newFile); 376 file = newFile; 377 } 378 } 379 380 /** 381 * Extract number from file name 382 */ 383 uint extractNum(string file) 384 { 385 import std.conv; 386 387 uint num = 0; 388 try 389 { 390 static import std.path; 391 import std.string; 392 auto fch = std.path.baseName(file).chompPrefix(baseName); 393 auto m = matchAll(fch, regex(`\d*`)); 394 395 if (!m.empty && m.hit.length > 0) 396 { 397 num = to!uint(m.hit); 398 } 399 } 400 catch (Exception e) 401 { 402 throw new Exception("Uncorrect log file name: " ~ file ~ " -> " ~ e.msg); 403 } 404 return num; 405 } 406 407 }