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