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