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