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 & Marenz 10 11 *******************************************************************************/ 12 13 module tango.util.log.AppendSyslog; 14 15 private import tango.time.Time; 16 17 private import Path = tango.io.Path, tango.io.device.File, tango.io.FilePath; 18 19 private import tango.io.model.IFile; 20 21 private import tango.util.log.Log, tango.util.log.AppendFile; 22 23 private import Integer = tango.text.convert.Integer; 24 25 private import tango.text.convert.Format; 26 27 private import tango.util.MinMax; 28 29 private import tango.sys.Process; 30 31 /******************************************************************************* 32 33 Append log messages to a file set 34 35 *******************************************************************************/ 36 37 public class AppendSyslog: Filer 38 { 39 private Mask mask_; 40 private long max_size, file_size, max_files, compress_index; 41 42 private FilePath file_path; 43 private char[] path; 44 private char[] compress_suffix; 45 private Process compress_cmd; 46 47 /*********************************************************************** 48 49 Create an AppendSyslog upon a file-set with the specified 50 path and optional layout. The minimal file count is two 51 and the maximum is 1000 (explicitly 999). 52 The minimal compress_begin index is 2. 53 54 55 Params: 56 path = path to the first logfile 57 count = maximum number of logfiles 58 max_size = maximum size of a logfile in bytes 59 compress_cmd = command to use to compress logfiles 60 compress_suffix = suffix for compressed logfiles 61 compress_begin = index after which logfiles should be compressed 62 how = which layout to use 63 64 ***********************************************************************/ 65 66 this ( char[] path, uint count, long max_size, 67 char[] compress_cmd = null, char[] compress_suffix = null, 68 size_t compress_begin = 2, Appender.Layout how = null ) 69 { 70 assert (path); 71 assert (count < 1000); 72 assert (compress_begin >= 2); 73 74 // Get a unique fingerprint for this instance 75 mask_ = register(path); 76 77 auto style = File.WriteAppending; 78 style.share = File.Share.Read; 79 auto conduit = new File(path, style); 80 81 configure(conduit); 82 83 // remember the maximum size 84 this.max_size = max_size; 85 // and the current size 86 this.file_size = conduit.length; 87 this.max_files = count; 88 89 // set provided layout (ignored when null) 90 layout(how); 91 92 this.file_path = new FilePath(path); 93 this.file_path.pop(); 94 95 this.path = path.dup; 96 // "gzip {}" this.path.{} 97 98 char[512] buf, buf1; 99 100 auto compr_path = Format.sprint(buf, "{}.{}", this.path, compress_begin); 101 102 auto cmd = Format.sprint(buf1, compress_cmd, compr_path); 103 104 this.compress_cmd = new Process(cmd.dup); 105 this.compress_suffix = "." ~ compress_suffix; 106 this.compress_index = compress_begin; 107 } 108 109 /*********************************************************************** 110 111 Return the fingerprint for this class 112 113 ***********************************************************************/ 114 115 @property override final Mask mask ( ) const 116 { 117 return mask_; 118 } 119 120 /*********************************************************************** 121 122 Return the name of this class 123 124 ***********************************************************************/ 125 126 @property override final const(char)[] name ( ) const 127 { 128 return this.classinfo.name; 129 } 130 131 /*********************************************************************** 132 133 Append an event to the output 134 135 ***********************************************************************/ 136 137 override final void append ( LogEvent event ) 138 { 139 synchronized(this) 140 { 141 char[] msg; 142 143 // file already full? 144 if (file_size >= max_size) nextFile(); 145 146 size_t write ( const(void)[] content ) 147 { 148 file_size += content.length; 149 return buffer.write(content); 150 } 151 152 // write log message and flush it 153 layout.format(event, &write); 154 write(tango.io.model.IFile.FileConst.NewlineString); 155 buffer.flush(); 156 } 157 } 158 159 private void openConduit () 160 { 161 this.file_size = 0; 162 // make it shareable for read 163 auto style = File.WriteAppending; 164 style.share = File.Share.Read; 165 (cast(File) this.conduit).open(this.path, style); 166 //this.buffer.output(this.conduit); 167 } 168 169 /*********************************************************************** 170 171 Switch to the next file within the set 172 173 ***********************************************************************/ 174 175 private void nextFile ( ) 176 { 177 size_t free, used; 178 179 long oldest = 1; 180 char[512] buf; 181 182 buf[0 .. this.path.length] = this.path[]; 183 buf[this.path.length] = '.'; 184 185 // release currently opened file 186 this.conduit.detach(); 187 188 foreach ( ref file; this.file_path ) 189 { 190 auto pathlen = file.path.length; 191 192 if ( file.name.length > this.path.length + 1 - pathlen && 193 file.name[0 .. this.path.length - pathlen] == this.path[pathlen .. $] ) 194 { 195 size_t ate = 0; 196 auto num = Integer.parse(file.name[this.path.length - pathlen + 1 .. $], 0, &ate); 197 198 if ( ate != 0 ) 199 { 200 oldest = max!(long)(oldest, num); 201 } 202 } 203 } 204 205 for ( long i = oldest; i > 0; --i ) 206 { 207 const(char)[] compress = i >= this.compress_index ? 208 this.compress_suffix : ""; 209 210 auto path = Format.sprint(buf, "{}.{}{}", this.path, i, 211 compress); 212 213 this.file_path.set(path, true); 214 215 if ( this.file_path.exists() ) 216 { 217 if ( i + 1 < this.max_files) 218 { 219 path = Format.sprint(buf, "{}.{}{}\0", this.path, i+1, 220 compress); 221 222 this.file_path.rename(path); 223 224 if ( i + 1 == this.compress_index ) with (this.compress_cmd) 225 { 226 if ( isRunning() ) 227 { 228 wait(); 229 close(); 230 } 231 232 execute(); 233 } 234 } 235 else this.file_path.remove(); 236 } 237 } 238 239 this.file_path.set(this.path); 240 241 if ( this.file_path.exists() ) 242 { 243 auto path = Format.sprint(buf, "{}.{}\0", this.path, 1); 244 245 this.file_path.rename(path); 246 } 247 248 this.openConduit (); 249 250 this.file_path.set(this.path); 251 this.file_path.pop(); 252 } 253 } 254 255 /******************************************************************************* 256 257 *******************************************************************************/ 258 259 debug (AppendSyslog) 260 { 261 void main ( ) 262 { 263 Log.root.add(new AppendFiles("foo", 5, 6)); 264 auto log = Log.lookup("fu.bar"); 265 log.trace("hello {}", "world"); 266 log.trace("hello {}", "world"); 267 log.trace("hello {}", "world"); 268 log.trace("hello {}", "world"); 269 log.trace("hello {}", "world"); 270 log.trace("hello {}", "world"); 271 log.trace("hello {}", "world"); 272 log.trace("hello {}", "world"); 273 log.trace("hello {}", "world"); 274 log.trace("hello {}", "world"); 275 log.trace("hello {}", "world"); 276 log.trace("hello {}", "world"); 277 log.trace("hello {}", "world"); 278 log.trace("hello {}", "world"); 279 log.trace("hello {}", "world"); 280 log.trace("hello {}", "world"); 281 282 } 283 }