1 /******************************************************************************* 2 3 copyright: Copyright (c) 2004 Kris Bell. All rights reserved 4 5 license: BSD style: $(LICENSE) 6 7 version: Initial release: May 2004 8 9 author: Kris 10 11 *******************************************************************************/ 12 13 module tango.util.log.AppendFiles; 14 15 private import tango.time.Time; 16 17 private import Path = tango.io.Path, 18 tango.io.device.File; 19 20 private import tango.io.model.IFile; 21 22 private import tango.util.log.Log, 23 tango.util.log.AppendFile; 24 25 /******************************************************************************* 26 27 Append log messages to a file set 28 29 *******************************************************************************/ 30 31 public class AppendFiles : Filer 32 { 33 private Mask mask_; 34 private const(char)[][] paths; 35 private int index; 36 private long maxSize, 37 fileSize; 38 39 /*********************************************************************** 40 41 Create an AppendFiles upon a file-set with the specified 42 path and optional layout. The minimal file count is two 43 and the maximum is 1000 (explicitly 999). Note that files 44 are numbered starting with zero rather than one. 45 46 A path of "my.log" will be expanded to "my.0.log". 47 48 maxSize is the advisory maximum size of a single log-file, 49 in bytes. 50 51 Where a file set already exists, we resume appending to 52 the one with the most recent activity timestamp 53 54 ***********************************************************************/ 55 56 this (const(char)[] path, int count, long maxSize, Appender.Layout how = null) 57 { 58 --count; 59 assert (path); 60 assert (count > 0 && count < 1000); 61 62 // Get a unique fingerprint for this instance 63 mask_ = register (path); 64 65 // split the path into components 66 auto c = Path.parse (path); 67 68 char[3] x; 69 Time mostRecent; 70 for (int i=0; i <= count; ++i) 71 { 72 x[0] = cast(char)('0' + i/100); 73 x[1] = cast(char)('0' + i/10%10); 74 x[2] = cast(char)('0' + i%10); 75 auto p = c.toString()[0..$-c.suffix.length] ~ x ~ c.suffix; 76 paths ~= p; 77 78 // use the most recent file in the set 79 if (Path.exists(p)) 80 { 81 auto modified = Path.modified(p); 82 if (modified > mostRecent) 83 { 84 mostRecent = modified; 85 index = i; 86 } 87 } 88 } 89 90 // remember the maximum size 91 this.maxSize = maxSize; 92 93 // adjust index and open the appropriate log file 94 --index; 95 nextFile (false); 96 97 // set provided layout (ignored when null) 98 layout (how); 99 } 100 101 /*********************************************************************** 102 103 Return the fingerprint for this class 104 105 ***********************************************************************/ 106 107 @property override final const Mask mask () 108 { 109 return mask_; 110 } 111 112 /*********************************************************************** 113 114 Return the name of this class 115 116 ***********************************************************************/ 117 118 @property override final const const(char)[] name () 119 { 120 return this.classinfo.name; 121 } 122 123 /*********************************************************************** 124 125 Append an event to the output 126 127 ***********************************************************************/ 128 129 override final void append (LogEvent event) 130 { 131 synchronized(this) 132 { 133 char[] msg; 134 135 // file already full? 136 if (fileSize >= maxSize) 137 nextFile (true); 138 139 size_t write (const(void)[] content) 140 { 141 fileSize += content.length; 142 return buffer.write (content); 143 } 144 145 // write log message and flush it 146 layout.format (event, &write); 147 write (FileConst.NewlineString); 148 buffer.flush(); 149 } 150 } 151 152 /*********************************************************************** 153 154 Switch to the next file within the set 155 156 ***********************************************************************/ 157 158 private void nextFile (bool reset) 159 { 160 // select next file in the set 161 if (++index >= paths.length) 162 index = 0; 163 164 // close any existing conduit 165 close(); 166 167 // make it shareable for read 168 auto style = File.WriteAppending; 169 style.share = File.Share.Read; 170 auto conduit = new File (paths[index], style); 171 172 configure (conduit); 173 174 // reset file size 175 if (reset) 176 conduit.truncate (fileSize = 0); 177 else 178 fileSize = conduit.length; 179 } 180 } 181 182 /******************************************************************************* 183 184 *******************************************************************************/ 185 186 debug (AppendFiles) 187 { 188 void main() 189 { 190 Log.root.add (new AppendFiles ("foo", 5, 6)); 191 auto log = Log.lookup ("fu.bar"); 192 log.trace ("hello {}", "world"); 193 log.trace ("hello {}", "world"); 194 log.trace ("hello {}", "world"); 195 log.trace ("hello {}", "world"); 196 log.trace ("hello {}", "world"); 197 log.trace ("hello {}", "world"); 198 log.trace ("hello {}", "world"); 199 log.trace ("hello {}", "world"); 200 log.trace ("hello {}", "world"); 201 log.trace ("hello {}", "world"); 202 log.trace ("hello {}", "world"); 203 log.trace ("hello {}", "world"); 204 log.trace ("hello {}", "world"); 205 log.trace ("hello {}", "world"); 206 log.trace ("hello {}", "world"); 207 log.trace ("hello {}", "world"); 208 209 } 210 }