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 }