1 // Module:  Log4CPLUS
2 // File:    fileappender.cxx
3 // Created: 6/2001
4 // Author:  Tad E. Smith
5 //
6 //
7 // Copyright 2001-2010 Tad E. Smith
8 //
9 // Licensed under the Apache License, Version 2.0 (the "License");
10 // you may not use this file except in compliance with the License.
11 // You may obtain a copy of the License at
12 //
13 //     http://www.apache.org/licenses/LICENSE-2.0
14 //
15 // Unless required by applicable law or agreed to in writing, software
16 // distributed under the License is distributed on an "AS IS" BASIS,
17 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 // See the License for the specific language governing permissions and
19 // limitations under the License.
20 
21 #include "dcmtk/oflog/fileap.h"
22 #include "dcmtk/oflog/layout.h"
23 #include "dcmtk/oflog/streams.h"
24 #include "dcmtk/oflog/helpers/loglog.h"
25 #include "dcmtk/oflog/helpers/strhelp.h"
26 #include "dcmtk/oflog/helpers/timehelp.h"
27 #include "dcmtk/oflog/helpers/property.h"
28 #include "dcmtk/oflog/helpers/fileinfo.h"
29 #include "dcmtk/oflog/spi/logevent.h"
30 #include "dcmtk/oflog/spi/factory.h"
31 #include "dcmtk/oflog/thread/syncpub.h"
32 #include "dcmtk/oflog/internal/internal.h"
33 #include <algorithm>
34 #include <sstream>
35 #include <cstdio>
36 #include <stdexcept>
37 
38 #if defined (__BORLANDC__)
39 // For _wrename() and _wremove() on Windows.
40 #  include <stdio.h>
41 #endif
42 #include <cerrno>
43 #ifdef DCMTK_LOG4CPLUS_HAVE_ERRNO_H
44 #include <errno.h>
45 #endif
46 
47 #ifdef MAX
48 #undef MAX
49 #endif
50 
51 #define MAX(a, b) ((a) < (b) ? (b) : (a))
52 
53 namespace dcmtk
54 {
55 namespace log4cplus
56 {
57 
58 using helpers::Properties;
59 using helpers::Time;
60 
61 
62 const long DEFAULT_ROLLING_LOG_SIZE = 10 * 1024 * 1024L;
63 const long MINIMUM_ROLLING_LOG_SIZE = 200*1024L;
64 
65 
66 ///////////////////////////////////////////////////////////////////////////////
67 // File LOCAL definitions
68 ///////////////////////////////////////////////////////////////////////////////
69 
70 namespace
71 {
72 
73 long const DCMTK_LOG4CPLUS_FILE_NOT_FOUND = ENOENT;
74 
75 
76 static
77 long
file_rename(tstring const & src,tstring const & target)78 file_rename (tstring const & src, tstring const & target)
79 {
80 #if defined (DCMTK_OFLOG_UNICODE) && defined (_WIN32)
81     if (_wrename (src.c_str (), target.c_str ()) == 0)
82         return 0;
83     else
84         return errno;
85 
86 #else
87     if (rename (DCMTK_LOG4CPLUS_TSTRING_TO_STRING (src).c_str (),
88             DCMTK_LOG4CPLUS_TSTRING_TO_STRING (target).c_str ()) == 0)
89         return 0;
90     else
91         return errno;
92 
93 #endif
94 }
95 
96 
97 static
98 long
file_remove(tstring const & src)99 file_remove (tstring const & src)
100 {
101 #if defined (DCMTK_OFLOG_UNICODE) && defined (_WIN32)
102     if (_wremove (src.c_str ()) == 0)
103         return 0;
104     else
105         return errno;
106 
107 #else
108     if (remove (DCMTK_LOG4CPLUS_TSTRING_TO_STRING (src).c_str ()) == 0)
109         return 0;
110     else
111         return errno;
112 
113 #endif
114 }
115 
116 
117 static
118 void
loglog_renaming_result(helpers::LogLog & loglog,tstring const & src,tstring const & target,long ret)119 loglog_renaming_result (helpers::LogLog & loglog, tstring const & src,
120     tstring const & target, long ret)
121 {
122     if (ret == 0)
123     {
124         loglog.debug (
125             DCMTK_LOG4CPLUS_TEXT("Renamed file ")
126             + src
127             + DCMTK_LOG4CPLUS_TEXT(" to ")
128             + target);
129     }
130     else if (ret != DCMTK_LOG4CPLUS_FILE_NOT_FOUND)
131     {
132         tostringstream oss;
133         oss << DCMTK_LOG4CPLUS_TEXT("Failed to rename file from ")
134             << src
135             << DCMTK_LOG4CPLUS_TEXT(" to ")
136             << target
137             << DCMTK_LOG4CPLUS_TEXT("; error ")
138             << ret;
139         loglog.error (OFString(oss.str().c_str(), oss.str().length()));
140     }
141 }
142 
143 
144 static
145 void
loglog_opening_result(helpers::LogLog & loglog,log4cplus::tostream const & os,tstring const & filename)146 loglog_opening_result (helpers::LogLog & loglog,
147     log4cplus::tostream const & os, tstring const & filename)
148 {
149     if (! os)
150     {
151         loglog.error (
152             DCMTK_LOG4CPLUS_TEXT("Failed to open file ")
153             + filename);
154     }
155 }
156 
157 
158 static
159 void
rolloverFiles(const tstring & filename,unsigned int maxBackupIndex)160 rolloverFiles(const tstring& filename, unsigned int maxBackupIndex)
161 {
162     helpers::LogLog * loglog = helpers::LogLog::getLogLog();
163 
164     // Delete the oldest file
165     tostringstream buffer;
166     buffer << filename << DCMTK_LOG4CPLUS_TEXT(".") << maxBackupIndex;
167     long ret = file_remove (OFString(buffer.str().c_str(), buffer.str().length()));
168 
169     tostringstream source_oss;
170     tostringstream target_oss;
171 
172     // Map {(maxBackupIndex - 1), ..., 2, 1} to {maxBackupIndex, ..., 3, 2}
173     for (int i = maxBackupIndex - 1; i >= 1; --i)
174     {
175         source_oss.str(DCMTK_LOG4CPLUS_TEXT(""));
176         target_oss.str(DCMTK_LOG4CPLUS_TEXT(""));
177 
178         source_oss << filename << DCMTK_LOG4CPLUS_TEXT(".") << i;
179         target_oss << filename << DCMTK_LOG4CPLUS_TEXT(".") << (i+1);
180 
181         tstring const source (OFString(source_oss.str().c_str(), source_oss.str().length()));
182         tstring const target (OFString(target_oss.str().c_str(), target_oss.str().length()));
183 
184 #if defined (_WIN32)
185         // Try to remove the target first. It seems it is not
186         // possible to rename over existing file.
187         ret = file_remove (target);
188 #endif
189 
190         ret = file_rename (source, target);
191         loglog_renaming_result (*loglog, source, target, ret);
192     }
193 } // end rolloverFiles()
194 
195 
196 static
197 STD_NAMESPACE locale
get_locale_by_name(tstring const & locale_name)198 get_locale_by_name (tstring const & locale_name)
199 {
200     try {
201         spi::LocaleFactoryRegistry & reg = spi::getLocaleFactoryRegistry ();
202         spi::LocaleFactory * fact = reg.get (locale_name);
203         if (fact)
204         {
205             helpers::Properties props;
206             props.setProperty (DCMTK_LOG4CPLUS_TEXT ("Locale"), locale_name);
207             return fact->createObject (props);
208         }
209         else
210             return STD_NAMESPACE locale (DCMTK_LOG4CPLUS_TSTRING_TO_STRING (locale_name).c_str ());
211     }
212     catch (STD_NAMESPACE runtime_error const &)
213     {
214         helpers::getLogLog ().error (
215             DCMTK_LOG4CPLUS_TEXT ("Failed to create locale " + locale_name));
216         return STD_NAMESPACE locale ();
217     }
218 }
219 
220 } // namespace
221 
222 
223 ///////////////////////////////////////////////////////////////////////////////
224 // FileAppender ctors and dtor
225 ///////////////////////////////////////////////////////////////////////////////
226 
FileAppender(const tstring & filename_,STD_NAMESPACE ios_base::openmode mode_,bool immediateFlush_)227 FileAppender::FileAppender(const tstring& filename_,
228     STD_NAMESPACE ios_base::openmode mode_, bool immediateFlush_)
229     : immediateFlush(immediateFlush_)
230     , reopenDelay(1)
231     , bufferSize (0)
232     , buffer (0)
233     , out ()
234     , filename ()
235     , localeName (DCMTK_LOG4CPLUS_TEXT ("DEFAULT"))
236     , reopen_time ()
237 {
238     init(filename_, mode_, internal::empty_str);
239 }
240 
241 
FileAppender(const Properties & props,STD_NAMESPACE ios_base::openmode mode_)242 FileAppender::FileAppender(const Properties& props,
243                            STD_NAMESPACE ios_base::openmode mode_)
244     : Appender(props)
245     , immediateFlush(true)
246     , reopenDelay(1)
247     , bufferSize (0)
248     , buffer (0)
249     , out ()
250     , filename ()
251     , localeName ()
252     , reopen_time ()
253 {
254     bool app = (mode_ == STD_NAMESPACE ios::app);
255     tstring const & fn = props.getProperty( DCMTK_LOG4CPLUS_TEXT("File") );
256     if (fn.empty())
257     {
258         getErrorHandler()->error( DCMTK_LOG4CPLUS_TEXT("Invalid filename") );
259         return;
260     }
261 
262     props.getBool (immediateFlush, DCMTK_LOG4CPLUS_TEXT("ImmediateFlush"));
263     props.getBool (app, DCMTK_LOG4CPLUS_TEXT("Append"));
264     props.getInt (reopenDelay, DCMTK_LOG4CPLUS_TEXT("ReopenDelay"));
265     props.getULong (bufferSize, DCMTK_LOG4CPLUS_TEXT("BufferSize"));
266 
267     tstring lockFileName = props.getProperty (DCMTK_LOG4CPLUS_TEXT ("LockFile"));
268     if (useLockFile && lockFileName.empty ())
269     {
270         lockFileName = fn;
271         lockFileName += DCMTK_LOG4CPLUS_TEXT(".lock");
272     }
273 
274     localeName = props.getProperty (DCMTK_LOG4CPLUS_TEXT ("Locale"),
275         DCMTK_LOG4CPLUS_TEXT ("DEFAULT"));
276 
277     init(fn, (app ? STD_NAMESPACE ios::app : STD_NAMESPACE ios::trunc), lockFileName);
278 }
279 
280 
281 
282 void
init(const tstring & filename_,STD_NAMESPACE ios_base::openmode mode_,const log4cplus::tstring & lockFileName_)283 FileAppender::init(const tstring& filename_,
284                    STD_NAMESPACE ios_base::openmode mode_,
285                    const log4cplus::tstring& lockFileName_)
286 {
287     filename = filename_;
288 
289     if (bufferSize != 0)
290     {
291         delete[] buffer;
292         buffer = new tchar[bufferSize];
293         out.rdbuf ()->pubsetbuf (buffer, bufferSize);
294     }
295 
296     helpers::LockFileGuard guard;
297     if (useLockFile && ! lockFile.get ())
298     {
299         try
300         {
301             lockFile.reset (new helpers::LockFile (lockFileName_));
302             guard.attach_and_lock (*lockFile);
303         }
304         catch (STD_NAMESPACE runtime_error const &)
305         {
306             // We do not need to do any logging here as the internals
307             // of LockFile already use LogLog to report the failure.
308             return;
309         }
310     }
311 
312     open(mode_);
313     imbue (get_locale_by_name (localeName));
314 
315     if(!out.good()) {
316         getErrorHandler()->error(  DCMTK_LOG4CPLUS_TEXT("Unable to open file: ")
317                                  + filename);
318         return;
319     }
320     helpers::getLogLog().debug(DCMTK_LOG4CPLUS_TEXT("Just opened file: ") + filename);
321 }
322 
323 
324 
~FileAppender()325 FileAppender::~FileAppender()
326 {
327     destructorImpl();
328 }
329 
330 
331 
332 ///////////////////////////////////////////////////////////////////////////////
333 // FileAppender public methods
334 ///////////////////////////////////////////////////////////////////////////////
335 
336 void
close()337 FileAppender::close()
338 {
339     thread::MutexGuard guard (access_mutex);
340 
341     out.close();
342     delete[] buffer;
343     buffer = 0;
344     closed = true;
345 }
346 
347 
348 STD_NAMESPACE locale
imbue(STD_NAMESPACE locale const & loc)349 FileAppender::imbue(STD_NAMESPACE locale const& loc)
350 {
351     return out.imbue (loc);
352 }
353 
354 
355 STD_NAMESPACE locale
getloc() const356 FileAppender::getloc () const
357 {
358     return out.getloc ();
359 }
360 
361 
362 ///////////////////////////////////////////////////////////////////////////////
363 // FileAppender protected methods
364 ///////////////////////////////////////////////////////////////////////////////
365 
366 // This method does not need to be locked since it is called by
367 // doAppend() which performs the locking
368 void
append(const spi::InternalLoggingEvent & event)369 FileAppender::append(const spi::InternalLoggingEvent& event)
370 {
371     if(!out.good()) {
372         if(!reopen()) {
373             getErrorHandler()->error(  DCMTK_LOG4CPLUS_TEXT("file is not open: ")
374                                      + filename);
375             return;
376         }
377         // Resets the error handler to make it
378         // ready to handle a future append error.
379         else
380             getErrorHandler()->reset();
381     }
382 
383     if (useLockFile)
384         out.seekp (0, STD_NAMESPACE ios_base::end);
385 
386     layout->formatAndAppend(out, event);
387 
388     if(immediateFlush || useLockFile)
389         out.flush();
390 }
391 
392 void
open(STD_NAMESPACE ios::openmode mode)393 FileAppender::open(STD_NAMESPACE ios::openmode mode)
394 {
395     out.open(DCMTK_LOG4CPLUS_FSTREAM_PREFERED_FILE_NAME(filename).c_str(), mode);
396 }
397 
398 bool
reopen()399 FileAppender::reopen()
400 {
401     // When append never failed and the file re-open attempt must
402     // be delayed, set the time when reopen should take place.
403     if (reopen_time == log4cplus::helpers::Time () && reopenDelay != 0)
404         reopen_time = log4cplus::helpers::Time::gettimeofday()
405             + log4cplus::helpers::Time(reopenDelay);
406     else
407     {
408         // Otherwise, check for end of the delay (or absence of delay)
409         // to re-open the file.
410         if (reopen_time <= log4cplus::helpers::Time::gettimeofday()
411             || reopenDelay == 0)
412         {
413             // Close the current file
414             out.close();
415             out.clear(); // reset flags since the C++ standard specified that all the
416                          // flags should remain unchanged on a close
417 
418             // Re-open the file.
419             open(STD_NAMESPACE ios_base::out | STD_NAMESPACE ios_base::ate);
420 
421             // Reset last fail time.
422             reopen_time = log4cplus::helpers::Time ();
423 
424             // Succeed if no errors are found.
425             if(out.good())
426                 return true;
427         }
428     }
429     return false;
430 }
431 
432 ///////////////////////////////////////////////////////////////////////////////
433 // RollingFileAppender ctors and dtor
434 ///////////////////////////////////////////////////////////////////////////////
435 
RollingFileAppender(const tstring & filename_,long maxFileSize_,int maxBackupIndex_,bool immediateFlush_)436 RollingFileAppender::RollingFileAppender(const tstring& filename_,
437     long maxFileSize_, int maxBackupIndex_, bool immediateFlush_)
438     : FileAppender(filename_, STD_NAMESPACE ios::app, immediateFlush_), maxFileSize (), maxBackupIndex ()
439 {
440     init(maxFileSize_, maxBackupIndex_);
441 }
442 
443 
RollingFileAppender(const Properties & properties)444 RollingFileAppender::RollingFileAppender(const Properties& properties)
445     : FileAppender(properties, STD_NAMESPACE ios::app), maxFileSize (), maxBackupIndex ()
446 {
447     long tmpMaxFileSize = DEFAULT_ROLLING_LOG_SIZE;
448     int tmpMaxBackupIndex = 1;
449     tstring tmp (
450         helpers::toUpper (
451             properties.getProperty (DCMTK_LOG4CPLUS_TEXT ("MaxFileSize"))));
452     if (! tmp.empty ())
453     {
454         tmpMaxFileSize = atoi(DCMTK_LOG4CPLUS_TSTRING_TO_STRING(tmp).c_str());
455         if (tmpMaxFileSize != 0)
456         {
457             tstring::size_type const len = tmp.length();
458             if (len > 2
459                 && tmp.compare (len - 2, 2, DCMTK_LOG4CPLUS_TEXT("MB")) == 0)
460                 tmpMaxFileSize *= (1024 * 1024); // convert to megabytes
461             else if (len > 2
462                 && tmp.compare (len - 2, 2, DCMTK_LOG4CPLUS_TEXT("KB")) == 0)
463                 tmpMaxFileSize *= 1024; // convert to kilobytes
464         }
465         tmpMaxFileSize = MAX(tmpMaxFileSize, MINIMUM_ROLLING_LOG_SIZE);
466     }
467 
468     properties.getInt (tmpMaxBackupIndex, DCMTK_LOG4CPLUS_TEXT("MaxBackupIndex"));
469 
470     init(tmpMaxFileSize, tmpMaxBackupIndex);
471 }
472 
473 
474 void
init(long maxFileSize_,int maxBackupIndex_)475 RollingFileAppender::init(long maxFileSize_, int maxBackupIndex_)
476 {
477     if (maxFileSize_ < MINIMUM_ROLLING_LOG_SIZE)
478     {
479         tostringstream oss;
480         oss << DCMTK_LOG4CPLUS_TEXT ("RollingFileAppender: MaxFileSize property")
481             DCMTK_LOG4CPLUS_TEXT (" value is too small. Resetting to ")
482             << MINIMUM_ROLLING_LOG_SIZE << ".";
483         helpers::getLogLog ().warn (OFString(oss.str().c_str(), oss.str().length()));
484         maxFileSize_ = MINIMUM_ROLLING_LOG_SIZE;
485     }
486 
487     maxFileSize = maxFileSize_;
488     maxBackupIndex = MAX(maxBackupIndex_, 1);
489 }
490 
491 
~RollingFileAppender()492 RollingFileAppender::~RollingFileAppender()
493 {
494     destructorImpl();
495 }
496 
497 
498 ///////////////////////////////////////////////////////////////////////////////
499 // RollingFileAppender protected methods
500 ///////////////////////////////////////////////////////////////////////////////
501 
502 // This method does not need to be locked since it is called by
503 // doAppend() which performs the locking
504 void
append(const spi::InternalLoggingEvent & event)505 RollingFileAppender::append(const spi::InternalLoggingEvent& event)
506 {
507     FileAppender::append(event);
508 
509     if(out.tellp() > maxFileSize) {
510         rollover(true);
511     }
512 }
513 
514 
515 void
rollover(bool alreadyLocked)516 RollingFileAppender::rollover(bool alreadyLocked)
517 {
518     helpers::LogLog & loglog = helpers::getLogLog();
519     helpers::LockFileGuard guard;
520 
521     // Close the current file
522     out.close();
523     // Reset flags since the C++ standard specified that all the flags
524     // should remain unchanged on a close.
525     out.clear();
526 
527     if (useLockFile)
528     {
529         if (! alreadyLocked)
530         {
531             try
532             {
533                 guard.attach_and_lock (*lockFile);
534             }
535             catch (STD_NAMESPACE runtime_error const &)
536             {
537                 return;
538             }
539         }
540 
541         // Recheck the condition as there is a window where another
542         // process can rollover the file before us.
543 
544         helpers::FileInfo fi;
545         if (helpers::getFileInfo (&fi, filename) == -1
546             || fi.size < maxFileSize)
547         {
548             // The file has already been rolled by another
549             // process. Just reopen with the new file.
550 
551             // Open it up again.
552             open (STD_NAMESPACE ios::out | STD_NAMESPACE ios::ate);
553             loglog_opening_result (loglog, out, filename);
554 
555             return;
556         }
557     }
558 
559     // If maxBackups <= 0, then there is no file renaming to be done.
560     if (maxBackupIndex > 0)
561     {
562         rolloverFiles(filename, maxBackupIndex);
563 
564         // Rename fileName to fileName.1
565         tstring target = filename + DCMTK_LOG4CPLUS_TEXT(".1");
566 
567         long ret;
568 
569 #if defined (_WIN32)
570         // Try to remove the target first. It seems it is not
571         // possible to rename over existing file.
572         ret = file_remove (target);
573 #endif
574 
575         loglog.debug (
576             DCMTK_LOG4CPLUS_TEXT("Renaming file ")
577             + filename
578             + DCMTK_LOG4CPLUS_TEXT(" to ")
579             + target);
580         ret = file_rename (filename, target);
581         loglog_renaming_result (loglog, filename, target, ret);
582     }
583     else
584     {
585         loglog.debug (filename + DCMTK_LOG4CPLUS_TEXT(" has no backups specified"));
586     }
587 
588     // Open it up again in truncation mode
589     open(STD_NAMESPACE ios::out | STD_NAMESPACE ios::trunc);
590     loglog_opening_result (loglog, out, filename);
591 }
592 
593 
594 ///////////////////////////////////////////////////////////////////////////////
595 // DailyRollingFileAppender ctors and dtor
596 ///////////////////////////////////////////////////////////////////////////////
597 
DailyRollingFileAppender(const tstring & filename_,DailyRollingFileSchedule schedule_,bool immediateFlush_,int maxBackupIndex_)598 DailyRollingFileAppender::DailyRollingFileAppender(
599     const tstring& filename_, DailyRollingFileSchedule schedule_,
600     bool immediateFlush_, int maxBackupIndex_)
601     : FileAppender(filename_, STD_NAMESPACE ios::app, immediateFlush_)
602     , schedule()
603     , scheduledFilename()
604     , nextRolloverTime()
605     , maxBackupIndex(maxBackupIndex_)
606 {
607     init(schedule_);
608 }
609 
610 
611 
DailyRollingFileAppender(const Properties & properties)612 DailyRollingFileAppender::DailyRollingFileAppender(
613     const Properties& properties)
614     : FileAppender(properties, STD_NAMESPACE ios::app)
615     , schedule()
616     , scheduledFilename()
617     , nextRolloverTime()
618     , maxBackupIndex(10)
619 {
620     DailyRollingFileSchedule theSchedule = DAILY;
621     tstring scheduleStr (helpers::toUpper (
622         properties.getProperty (DCMTK_LOG4CPLUS_TEXT ("Schedule"))));
623 
624     if(scheduleStr == DCMTK_LOG4CPLUS_TEXT("MONTHLY"))
625         theSchedule = MONTHLY;
626     else if(scheduleStr == DCMTK_LOG4CPLUS_TEXT("WEEKLY"))
627         theSchedule = WEEKLY;
628     else if(scheduleStr == DCMTK_LOG4CPLUS_TEXT("DAILY"))
629         theSchedule = DAILY;
630     else if(scheduleStr == DCMTK_LOG4CPLUS_TEXT("TWICE_DAILY"))
631         theSchedule = TWICE_DAILY;
632     else if(scheduleStr == DCMTK_LOG4CPLUS_TEXT("HOURLY"))
633         theSchedule = HOURLY;
634     else if(scheduleStr == DCMTK_LOG4CPLUS_TEXT("MINUTELY"))
635         theSchedule = MINUTELY;
636     else {
637         helpers::getLogLog().warn(
638             DCMTK_LOG4CPLUS_TEXT("DailyRollingFileAppender::ctor()")
639             DCMTK_LOG4CPLUS_TEXT("- \"Schedule\" not valid: ")
640             + properties.getProperty(DCMTK_LOG4CPLUS_TEXT("Schedule")));
641         theSchedule = DAILY;
642     }
643 
644     properties.getInt (maxBackupIndex, DCMTK_LOG4CPLUS_TEXT("MaxBackupIndex"));
645 
646     init(theSchedule);
647 }
648 
649 
650 
651 void
init(DailyRollingFileSchedule sch)652 DailyRollingFileAppender::init(DailyRollingFileSchedule sch)
653 {
654     this->schedule = sch;
655 
656     Time now = Time::gettimeofday();
657     now.usec(0);
658     struct tm time;
659     now.localtime(&time);
660 
661     time.tm_sec = 0;
662     switch (schedule)
663     {
664     case MONTHLY:
665         time.tm_mday = 1;
666         time.tm_hour = 0;
667         time.tm_min = 0;
668         break;
669 
670     case WEEKLY:
671         time.tm_mday -= (time.tm_wday % 7);
672         time.tm_hour = 0;
673         time.tm_min = 0;
674         break;
675 
676     case DAILY:
677         time.tm_hour = 0;
678         time.tm_min = 0;
679         break;
680 
681     case TWICE_DAILY:
682         if(time.tm_hour >= 12) {
683             time.tm_hour = 12;
684         }
685         else {
686             time.tm_hour = 0;
687         }
688         time.tm_min = 0;
689         break;
690 
691     case HOURLY:
692         time.tm_min = 0;
693         break;
694 
695     case MINUTELY:
696         break;
697     };
698     now.setTime(&time);
699 
700     scheduledFilename = getFilename(now);
701     nextRolloverTime = calculateNextRolloverTime(now);
702 }
703 
704 
705 
~DailyRollingFileAppender()706 DailyRollingFileAppender::~DailyRollingFileAppender()
707 {
708     destructorImpl();
709 }
710 
711 
712 
713 
714 ///////////////////////////////////////////////////////////////////////////////
715 // DailyRollingFileAppender public methods
716 ///////////////////////////////////////////////////////////////////////////////
717 
718 void
close()719 DailyRollingFileAppender::close()
720 {
721     rollover();
722     FileAppender::close();
723 }
724 
725 
726 
727 ///////////////////////////////////////////////////////////////////////////////
728 // DailyRollingFileAppender protected methods
729 ///////////////////////////////////////////////////////////////////////////////
730 
731 // This method does not need to be locked since it is called by
732 // doAppend() which performs the locking
733 void
append(const spi::InternalLoggingEvent & event)734 DailyRollingFileAppender::append(const spi::InternalLoggingEvent& event)
735 {
736     if(event.getTimestamp() >= nextRolloverTime) {
737         rollover(true);
738     }
739 
740     FileAppender::append(event);
741 }
742 
743 
744 
745 void
rollover(bool alreadyLocked)746 DailyRollingFileAppender::rollover(bool alreadyLocked)
747 {
748     helpers::LockFileGuard guard;
749 
750     if (useLockFile && ! alreadyLocked)
751     {
752         try
753         {
754             guard.attach_and_lock (*lockFile);
755         }
756         catch (STD_NAMESPACE runtime_error const &)
757         {
758             return;
759         }
760     }
761 
762     // Close the current file
763     out.close();
764     out.clear(); // reset flags since the C++ standard specified that all the
765                  // flags should remain unchanged on a close
766 
767     // If we've already rolled over this time period, we'll make sure that we
768     // don't overwrite any of those previous files.
769     // E.g. if "log.2009-11-07.1" already exists we rename it
770     // to "log.2009-11-07.2", etc.
771     rolloverFiles(scheduledFilename, maxBackupIndex);
772 
773     // Do not overwriet the newest file either, e.g. if "log.2009-11-07"
774     // already exists rename it to "log.2009-11-07.1"
775     tostringstream backup_target_oss;
776     backup_target_oss << scheduledFilename << DCMTK_LOG4CPLUS_TEXT(".") << 1;
777     tstring backupTarget(backup_target_oss.str().c_str(), backup_target_oss.str().length());
778 
779     helpers::LogLog & loglog = helpers::getLogLog();
780     long ret;
781 
782 #if defined (_WIN32)
783     // Try to remove the target first. It seems it is not
784     // possible to rename over existing file, e.g. "log.2009-11-07.1".
785     ret = file_remove (backupTarget);
786 #endif
787 
788     // Rename e.g. "log.2009-11-07" to "log.2009-11-07.1".
789     ret = file_rename (scheduledFilename, backupTarget);
790     loglog_renaming_result (loglog, scheduledFilename, backupTarget, ret);
791 
792 #if defined (_WIN32)
793     // Try to remove the target first. It seems it is not
794     // possible to rename over existing file, e.g. "log.2009-11-07".
795     ret = file_remove (scheduledFilename);
796 #endif
797 
798     // Rename filename to scheduledFilename,
799     // e.g. rename "log" to "log.2009-11-07".
800     loglog.debug(
801         DCMTK_LOG4CPLUS_TEXT("Renaming file ")
802         + filename
803         + DCMTK_LOG4CPLUS_TEXT(" to ")
804         + scheduledFilename);
805     ret = file_rename (filename, scheduledFilename);
806     loglog_renaming_result (loglog, filename, scheduledFilename, ret);
807 
808     // Open a new file, e.g. "log".
809     open(STD_NAMESPACE ios::out | STD_NAMESPACE ios::trunc);
810     loglog_opening_result (loglog, out, filename);
811 
812     // Calculate the next rollover time
813     log4cplus::helpers::Time now = Time::gettimeofday();
814     if (now >= nextRolloverTime)
815     {
816         scheduledFilename = getFilename(now);
817         nextRolloverTime = calculateNextRolloverTime(now);
818     }
819 }
820 
821 
822 
823 Time
calculateNextRolloverTime(const Time & t) const824 DailyRollingFileAppender::calculateNextRolloverTime(const Time& t) const
825 {
826     switch(schedule)
827     {
828     case MONTHLY:
829     {
830         struct tm nextMonthTime;
831         t.localtime(&nextMonthTime);
832         nextMonthTime.tm_mon += 1;
833         nextMonthTime.tm_isdst = 0;
834 
835         Time ret;
836         if(ret.setTime(&nextMonthTime) == -1) {
837             helpers::getLogLog().error(
838                 DCMTK_LOG4CPLUS_TEXT("DailyRollingFileAppender::calculateNextRolloverTime()-")
839                 DCMTK_LOG4CPLUS_TEXT(" setTime() returned error"));
840             // Set next rollover to 31 days in future.
841             ret = (t + Time(2678400));
842         }
843 
844         return ret;
845     }
846 
847     case WEEKLY:
848         return (t + Time(7 * 24 * 60 * 60));
849 
850     default:
851         helpers::getLogLog ().error (
852             DCMTK_LOG4CPLUS_TEXT ("DailyRollingFileAppender::calculateNextRolloverTime()-")
853             DCMTK_LOG4CPLUS_TEXT (" invalid schedule value"));
854         // Fall through.
855 
856     case DAILY:
857         return (t + Time(24 * 60 * 60));
858 
859     case TWICE_DAILY:
860         return (t + Time(12 * 60 * 60));
861 
862     case HOURLY:
863         return (t + Time(60 * 60));
864 
865     case MINUTELY:
866         return (t + Time(60));
867     };
868 }
869 
870 
871 
872 tstring
getFilename(const Time & t) const873 DailyRollingFileAppender::getFilename(const Time& t) const
874 {
875     tchar const * pattern = 0;
876     switch (schedule)
877     {
878     case MONTHLY:
879         pattern = DCMTK_LOG4CPLUS_TEXT("%Y-%m");
880         break;
881 
882     case WEEKLY:
883         pattern = DCMTK_LOG4CPLUS_TEXT("%Y-%W");
884         break;
885 
886     default:
887         helpers::getLogLog ().error (
888             DCMTK_LOG4CPLUS_TEXT ("DailyRollingFileAppender::getFilename()-")
889             DCMTK_LOG4CPLUS_TEXT (" invalid schedule value"));
890         // Fall through.
891 
892     case DAILY:
893         pattern = DCMTK_LOG4CPLUS_TEXT("%Y-%m-%d");
894         break;
895 
896     case TWICE_DAILY:
897         pattern = DCMTK_LOG4CPLUS_TEXT("%Y-%m-%d-%p");
898         break;
899 
900     case HOURLY:
901         pattern = DCMTK_LOG4CPLUS_TEXT("%Y-%m-%d-%H");
902         break;
903 
904     case MINUTELY:
905         pattern = DCMTK_LOG4CPLUS_TEXT("%Y-%m-%d-%H-%M");
906         break;
907     };
908 
909     tstring result (filename);
910     result += DCMTK_LOG4CPLUS_TEXT(".");
911     result += t.getFormattedTime(pattern, false);
912     return result;
913 }
914 
915 } // namespace log4cplus
916 } // end namespace dcmtk
917