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 }