Coverage Report

Created: 2025-07-01 06:08

/src/logging-log4cxx/src/main/cpp/timebasedrollingpolicy.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
#define NOMINMAX /* tell windows to not define min/max macros */
18
#include <log4cxx/log4cxx.h>
19
#include <log4cxx/logstring.h>
20
#include <log4cxx/rolling/timebasedrollingpolicy.h>
21
#include <log4cxx/pattern/filedatepatternconverter.h>
22
#include <log4cxx/helpers/date.h>
23
#include <log4cxx/rolling/filerenameaction.h>
24
#include <log4cxx/helpers/loglog.h>
25
#include <log4cxx/helpers/exception.h>
26
#include <log4cxx/rolling/gzcompressaction.h>
27
#include <log4cxx/rolling/zipcompressaction.h>
28
#include <log4cxx/helpers/stringhelper.h>
29
#include <log4cxx/helpers/optionconverter.h>
30
#include <log4cxx/helpers/transcoder.h>
31
#include <log4cxx/fileappender.h>
32
#include <iostream>
33
#include <apr_mmap.h>
34
35
using namespace LOG4CXX_NS;
36
using namespace LOG4CXX_NS::rolling;
37
using namespace LOG4CXX_NS::helpers;
38
using namespace LOG4CXX_NS::pattern;
39
40
IMPLEMENT_LOG4CXX_OBJECT(TimeBasedRollingPolicy)
41
42
struct TimeBasedRollingPolicy::TimeBasedRollingPolicyPrivate{
43
#if LOG4CXX_HAS_MULTIPROCESS_ROLLING_FILE_APPENDER
44
  TimeBasedRollingPolicyPrivate() :
45
    _mmap(nullptr),
46
    _file_map(nullptr),
47
    _lock_file(nullptr),
48
    bAlreadyInitialized(false),
49
    bRefreshCurFile(false){}
50
#else
51
0
  TimeBasedRollingPolicyPrivate(){}
52
#endif
53
54
    /**
55
     * Time for next determination if time for rollover.
56
     */
57
    log4cxx_time_t nextCheck;
58
59
    /**
60
     * File name at last rollover.
61
     */
62
    LogString lastFileName;
63
64
    /**
65
     * Length of any file type suffix (.gz, .zip).
66
     */
67
    int suffixLength;
68
69
    /**
70
     * mmap pointer
71
     */
72
    apr_mmap_t* _mmap;
73
74
    /*
75
     * pool for mmap handler
76
     * */
77
    LOG4CXX_NS::helpers::Pool _mmapPool;
78
79
    /**
80
     * mmap file descriptor
81
     */
82
    apr_file_t* _file_map;
83
84
    /**
85
     * mmap file name
86
     */
87
    std::string _mapFileName;
88
89
    /*
90
     * lock file handle
91
     * */
92
    apr_file_t* _lock_file;
93
94
    /**
95
     * Check nextCheck if it has already been set
96
     * Timebased rolling policy has an issue when working at low rps.
97
     * Under low rps, multiple processes will not be scheduled in time for the second chance(do rolling),
98
     * so the rolling mechanism will not be triggered even if the time period is out of date.
99
     * This results in log entries will be accumulated for serveral minutes to be rolling.
100
     * Adding this flag to provide rolling opportunity for a process even if it is writing the first log entry
101
     */
102
    bool bAlreadyInitialized;
103
104
    /*
105
     * If the current file name contains date information, retrieve the current writting file from mmap
106
     * */
107
    bool bRefreshCurFile;
108
109
    /*
110
     * mmap file name
111
     * */
112
    LogString _fileNamePattern;
113
114
    bool multiprocess = false;
115
    bool throwIOExceptionOnForkFailure = true;
116
};
117
118
119
#define MMAP_FILE_SUFFIX ".map"
120
#define LOCK_FILE_SUFFIX ".maplck"
121
#define MAX_FILE_LEN 2048
122
123
#if LOG4CXX_HAS_MULTIPROCESS_ROLLING_FILE_APPENDER
124
bool TimeBasedRollingPolicy::isMapFileEmpty(LOG4CXX_NS::helpers::Pool& pool)
125
{
126
  apr_finfo_t finfo;
127
  apr_status_t st = apr_stat(&finfo, m_priv->_mapFileName.c_str(), APR_FINFO_SIZE, pool.getAPRPool());
128
129
  if (st != APR_SUCCESS)
130
  {
131
    LogLog::warn(helpers::Exception::makeMessage(LOG4CXX_STR("apr_stat"), st));
132
  }
133
134
  if (st == APR_SUCCESS && (0 == finfo.size ||
135
    (m_priv->_mmap && 0 == *static_cast<logchar*>(m_priv->_mmap->mm))))
136
  {
137
    return true;
138
  }
139
140
  return false;
141
}
142
143
void TimeBasedRollingPolicy::initMMapFile(const LogString& lastFileName, LOG4CXX_NS::helpers::Pool& pool)
144
{
145
  int iRet = 0;
146
147
  if (!m_priv->_mmap)
148
  {
149
    LOG4CXX_ENCODE_CHAR(mapFile, m_priv->_fileNamePattern);
150
    iRet = createMMapFile(mapFile, pool);
151
  }
152
153
  if (!iRet && isMapFileEmpty(pool))
154
  {
155
    lockMMapFile(APR_FLOCK_EXCLUSIVE);
156
    memset(m_priv->_mmap->mm, 0, MAX_FILE_LEN);
157
    size_t byteCount = sizeof (logchar) * lastFileName.size();
158
    if (byteCount <= MAX_FILE_LEN - sizeof (logchar))
159
      memcpy(m_priv->_mmap->mm, lastFileName.c_str(), byteCount);
160
    unLockMMapFile();
161
  }
162
}
163
164
const std::string TimeBasedRollingPolicy::createFile(const std::string& fileName, const std::string& suffix, LOG4CXX_NS::helpers::Pool& pool)
165
{
166
  char szUid[MAX_FILE_LEN] = "0000";
167
#ifndef _WIN32 // The uid provided by the Windows version of apr_uid_current is not a constant value
168
  apr_uid_t uid;
169
  apr_gid_t groupid;
170
  if (APR_SUCCESS == apr_uid_current(&uid, &groupid, pool.getAPRPool()))
171
    snprintf(szUid, MAX_FILE_LEN, "%u", uid);
172
#endif
173
  return fileName + szUid + suffix;
174
}
175
176
int TimeBasedRollingPolicy::createMMapFile(const std::string& fileName, LOG4CXX_NS::helpers::Pool& pool)
177
{
178
  m_priv->_mapFileName = createFile(fileName, MMAP_FILE_SUFFIX, pool);
179
180
  apr_status_t stat = apr_file_open(&m_priv->_file_map, m_priv->_mapFileName.c_str(), APR_CREATE | APR_READ | APR_WRITE, APR_OS_DEFAULT, m_priv->_mmapPool.getAPRPool());
181
182
  if (stat != APR_SUCCESS)
183
  {
184
    LogString msg = helpers::Exception::makeMessage(LOG4CXX_STR("apr_file_open"), stat);
185
    msg += LOG4CXX_STR(". Check the privilege or try to remove [");
186
    helpers::Transcoder::decode(m_priv->_mapFileName, msg);
187
    msg += LOG4CXX_STR("] if it exists.");
188
    LogLog::warn(msg);
189
    return -1;
190
  }
191
192
  if (isMapFileEmpty(pool))
193
  {
194
    stat = apr_file_trunc(m_priv->_file_map, MAX_FILE_LEN + 1);
195
196
    if (stat != APR_SUCCESS)
197
    {
198
      LogLog::warn(helpers::Exception::makeMessage(LOG4CXX_STR("apr_file_trunc"), stat));
199
      apr_file_close(m_priv->_file_map);
200
      return -1;
201
    }
202
  }
203
204
  stat = apr_mmap_create(&m_priv->_mmap, m_priv->_file_map, 0, MAX_FILE_LEN, APR_MMAP_WRITE | APR_MMAP_READ, m_priv->_mmapPool.getAPRPool());
205
206
  if (stat != APR_SUCCESS)
207
  {
208
    LogLog::warn(helpers::Exception::makeMessage(LOG4CXX_STR("apr_mmap_create"), stat));
209
    apr_file_close(m_priv->_file_map);
210
    return -1;
211
  }
212
213
  return 0;
214
}
215
216
int TimeBasedRollingPolicy::lockMMapFile(int type)
217
{
218
  apr_status_t stat = apr_file_lock(m_priv->_lock_file, type);
219
220
  if (stat != APR_SUCCESS)
221
  {
222
    LogLog::warn(helpers::Exception::makeMessage(LOG4CXX_STR("apr_file_lock for mmap"), stat));
223
  }
224
225
  return stat;
226
}
227
228
int TimeBasedRollingPolicy::unLockMMapFile()
229
{
230
  apr_status_t stat = apr_file_unlock(m_priv->_lock_file);
231
232
  if (stat != APR_SUCCESS)
233
  {
234
    LogLog::warn(helpers::Exception::makeMessage(LOG4CXX_STR("apr_file_unlock for mmap"), stat));
235
  }
236
237
  return stat;
238
}
239
#else
240
0
int TimeBasedRollingPolicy::createMMapFile(const std::string&, LOG4CXX_NS::helpers::Pool&) {
241
0
  return 0;
242
0
}
243
244
0
bool TimeBasedRollingPolicy::isMapFileEmpty(LOG4CXX_NS::helpers::Pool&){
245
0
  return true;
246
0
}
247
248
0
void TimeBasedRollingPolicy::initMMapFile(const LogString&, LOG4CXX_NS::helpers::Pool&){}
249
250
0
int TimeBasedRollingPolicy::lockMMapFile(int){
251
0
  return 0;
252
0
}
253
254
0
int TimeBasedRollingPolicy::unLockMMapFile(){
255
0
  return 0;
256
0
}
257
258
0
const std::string TimeBasedRollingPolicy::createFile(const std::string&, const std::string&, LOG4CXX_NS::helpers::Pool&){
259
0
  return "";
260
0
}
261
#endif
262
263
TimeBasedRollingPolicy::TimeBasedRollingPolicy() :
264
0
  m_priv(std::make_unique<TimeBasedRollingPolicyPrivate>())
265
0
{
266
0
}
Unexecuted instantiation: log4cxx::rolling::TimeBasedRollingPolicy::TimeBasedRollingPolicy()
Unexecuted instantiation: log4cxx::rolling::TimeBasedRollingPolicy::TimeBasedRollingPolicy()
267
268
0
TimeBasedRollingPolicy::~TimeBasedRollingPolicy(){}
269
270
void TimeBasedRollingPolicy::activateOptions(LOG4CXX_NS::helpers::Pool& pool)
271
0
{
272
  // find out period from the filename pattern
273
0
  if (getFileNamePattern().length() > 0)
274
0
  {
275
0
    parseFileNamePattern();
276
0
  }
277
0
  else
278
0
  {
279
0
    LogLog::warn(
280
0
      LOG4CXX_STR("The FileNamePattern option must be set before using TimeBasedRollingPolicy. "));
281
0
    throw IllegalStateException();
282
0
  }
283
284
0
  PatternConverterPtr dtc(getDatePatternConverter());
285
286
0
  if (dtc == NULL)
287
0
  {
288
0
    throw NullPointerException(LOG4CXX_STR("DatePatternConverter"));
289
0
  }
290
291
0
  LogString buf;
292
0
  ObjectPtr obj = std::make_shared<Date>();
293
0
  formatFileName(obj, buf, pool);
294
0
  m_priv->lastFileName = buf;
295
296
0
  m_priv->suffixLength = 0;
297
298
0
  if (m_priv->lastFileName.length() >= 3)
299
0
  {
300
0
    if (m_priv->lastFileName.compare(m_priv->lastFileName.length() - 3, 3, LOG4CXX_STR(".gz")) == 0)
301
0
    {
302
0
      m_priv->suffixLength = 3;
303
0
    }
304
0
    else if (m_priv->lastFileName.length() >= 4 && m_priv->lastFileName.compare(m_priv->lastFileName.length() - 4, 4, LOG4CXX_STR(".zip")) == 0)
305
0
    {
306
0
      m_priv->suffixLength = 4;
307
0
    }
308
0
  }
309
0
}
310
311
312
#define RULES_PUT(spec, cls) \
313
0
  specs.insert(PatternMap::value_type(LogString(LOG4CXX_STR(spec)), (PatternConstructor) cls ::newInstance))
314
315
LOG4CXX_NS::pattern::PatternMap TimeBasedRollingPolicy::getFormatSpecifiers() const
316
0
{
317
0
  PatternMap specs;
318
0
  RULES_PUT("d", FileDatePatternConverter);
319
0
  RULES_PUT("date", FileDatePatternConverter);
320
0
  return specs;
321
0
}
322
323
/**
324
 * {@inheritDoc}
325
 */
326
RolloverDescriptionPtr TimeBasedRollingPolicy::initialize(
327
  const   LogString&  currentActiveFile,
328
  const   bool        append,
329
  Pool&       pool)
330
0
{
331
0
  Date now;
332
0
  log4cxx_time_t n = now.getTime();
333
0
  m_priv->nextCheck = now.getNextSecond();
334
335
0
  File currentFile(currentActiveFile);
336
337
0
  LogString buf;
338
0
  ObjectPtr obj = std::make_shared<Date>(currentFile.exists(pool) ? currentFile.lastModified(pool) : n);
339
0
  formatFileName(obj, buf, pool);
340
0
  m_priv->lastFileName = buf;
341
342
0
  ActionPtr noAction;
343
344
0
  if (currentActiveFile.length() > 0)
345
0
  {
346
0
    return std::make_shared<RolloverDescription>(
347
0
          currentActiveFile, append, noAction, noAction);
348
0
  }
349
0
  else
350
0
  {
351
0
    m_priv->bRefreshCurFile = true;
352
0
    return std::make_shared<RolloverDescription>(
353
0
          m_priv->lastFileName.substr(0, m_priv->lastFileName.length() - m_priv->suffixLength), append,
354
0
          noAction, noAction);
355
0
  }
356
0
}
357
358
RolloverDescriptionPtr TimeBasedRollingPolicy::rollover(
359
  const   LogString&  currentActiveFile,
360
  const   bool        append,
361
  Pool&       pool)
362
0
{
363
0
  Date now;
364
0
  log4cxx_time_t n = now.getTime();
365
0
  m_priv->nextCheck = now.getNextSecond();
366
367
0
  LogString buf;
368
0
  ObjectPtr obj = std::make_shared<Date>(n);
369
0
  formatFileName(obj, buf, pool);
370
371
0
  LogString newFileName(buf);
372
373
0
  if( m_priv->multiprocess ){
374
#if LOG4CXX_HAS_MULTIPROCESS_ROLLING_FILE_APPENDER
375
376
    if (!m_priv->bAlreadyInitialized)
377
    {
378
      if (getPatternConverterList().size())
379
      {
380
        (*(getPatternConverterList().begin()))->format(obj, m_priv->_fileNamePattern, pool);
381
      }
382
      else
383
      {
384
        m_priv->_fileNamePattern = m_priv->lastFileName;
385
      }
386
387
      if (!m_priv->_lock_file)
388
      {
389
        LOG4CXX_ENCODE_CHAR(mapFile, m_priv->_fileNamePattern);
390
        const std::string lockname = createFile(mapFile, LOCK_FILE_SUFFIX, m_priv->_mmapPool);
391
        apr_status_t stat = apr_file_open(&m_priv->_lock_file, lockname.c_str(), APR_CREATE | APR_READ | APR_WRITE, APR_OS_DEFAULT, m_priv->_mmapPool.getAPRPool());
392
393
        if (stat != APR_SUCCESS)
394
        {
395
          LOG4CXX_DECODE_CHAR(msg, lockname);
396
          msg += LOG4CXX_STR(": apr_file_open");
397
          LogLog::warn(helpers::Exception::makeMessage(msg, stat));
398
        }
399
      }
400
401
      initMMapFile(m_priv->lastFileName, m_priv->_mmapPool);
402
    }
403
    m_priv->bAlreadyInitialized = true;
404
405
    if (m_priv->_mmap && !isMapFileEmpty(m_priv->_mmapPool))
406
    {
407
      lockMMapFile(APR_FLOCK_SHARED);
408
      LogString mapLastFile(static_cast<logchar*>(m_priv->_mmap->mm));
409
      m_priv->lastFileName = mapLastFile;
410
      unLockMMapFile();
411
    }
412
    else
413
    {
414
      m_priv->_mmap = NULL;
415
      initMMapFile(m_priv->lastFileName, m_priv->_mmapPool);
416
    }
417
#endif
418
0
  }
419
420
  //
421
  //  if file names haven't changed, no rollover
422
  //
423
0
  if (newFileName == m_priv->lastFileName)
424
0
  {
425
0
    RolloverDescriptionPtr desc;
426
0
    return desc;
427
0
  }
428
429
0
  ActionPtr renameAction;
430
0
  ActionPtr compressAction;
431
0
  LogString lastBaseName(
432
0
    m_priv->lastFileName.substr(0, m_priv->lastFileName.length() - m_priv->suffixLength));
433
0
  LogString nextActiveFile(
434
0
    newFileName.substr(0, newFileName.length() - m_priv->suffixLength));
435
436
0
  if(getCreateIntermediateDirectories()){
437
0
    File compressedFile(m_priv->lastFileName);
438
0
    File compressedParent (compressedFile.getParent(pool));
439
0
    compressedParent.mkdirs(pool);
440
0
  }
441
442
  //
443
  //   if currentActiveFile is not lastBaseName then
444
  //        active file name is not following file pattern
445
  //        and requires a rename plus maintaining the same name
446
0
  if (currentActiveFile != lastBaseName)
447
0
  {
448
0
    renameAction = std::make_shared<FileRenameAction>(
449
0
          File().setPath(currentActiveFile), File().setPath(lastBaseName), true);
450
0
    nextActiveFile = currentActiveFile;
451
0
  }
452
453
0
  if (m_priv->suffixLength == 3)
454
0
  {
455
0
    GZCompressActionPtr comp = std::make_shared<GZCompressAction>(
456
0
          File().setPath(lastBaseName), File().setPath(m_priv->lastFileName), true);
457
0
    comp->setThrowIOExceptionOnForkFailure(m_priv->throwIOExceptionOnForkFailure);
458
0
    compressAction = comp;
459
0
  }
460
461
0
  if (m_priv->suffixLength == 4)
462
0
  {
463
0
    ZipCompressActionPtr comp = std::make_shared<ZipCompressAction>(
464
0
          File().setPath(lastBaseName), File().setPath(m_priv->lastFileName), true);
465
0
    comp->setThrowIOExceptionOnForkFailure(m_priv->throwIOExceptionOnForkFailure);
466
0
    compressAction = comp;
467
0
  }
468
469
0
  if( m_priv->multiprocess ){
470
#if LOG4CXX_HAS_MULTIPROCESS_ROLLING_FILE_APPENDER
471
    size_t byteCount = sizeof (logchar) * newFileName.size();
472
    if (MAX_FILE_LEN - sizeof (logchar) < byteCount)
473
    {
474
      LogString msg(newFileName + LOG4CXX_STR(": cannot exceed "));
475
      StringHelper::toString(MAX_FILE_LEN / sizeof (logchar), pool, msg);
476
      msg += LOG4CXX_STR(" characters");
477
      throw IllegalArgumentException(msg);
478
    }
479
    if (m_priv->_mmap && !isMapFileEmpty(m_priv->_mmapPool))
480
    {
481
      lockMMapFile(APR_FLOCK_EXCLUSIVE);
482
      memset(m_priv->_mmap->mm, 0, MAX_FILE_LEN);
483
      memcpy(m_priv->_mmap->mm, newFileName.c_str(), byteCount);
484
      unLockMMapFile();
485
    }
486
    else
487
    {
488
      m_priv->_mmap = NULL;
489
      initMMapFile(newFileName, m_priv->_mmapPool);
490
    }
491
#endif
492
0
  }else{
493
0
    m_priv->lastFileName = newFileName;
494
0
  }
495
496
0
  return std::make_shared<RolloverDescription>(nextActiveFile, append, renameAction, compressAction);
497
0
}
498
499
bool TimeBasedRollingPolicy::isTriggeringEvent(
500
  Appender* appender,
501
  const LOG4CXX_NS::spi::LoggingEventPtr& /* event */,
502
  const LogString&  filename,
503
  size_t /* fileLength */)
504
0
{
505
0
  if( m_priv->multiprocess ){
506
#if LOG4CXX_HAS_MULTIPROCESS_ROLLING_FILE_APPENDER
507
    if (m_priv->bRefreshCurFile && m_priv->_mmap && !isMapFileEmpty(m_priv->_mmapPool))
508
    {
509
      lockMMapFile(APR_FLOCK_SHARED);
510
      LogString mapCurrent(static_cast<logchar*>(m_priv->_mmap->mm));
511
      unLockMMapFile();
512
      LogString mapCurrentBase(mapCurrent.substr(0, mapCurrent.length() - m_priv->suffixLength));
513
514
      if (!mapCurrentBase.empty() && mapCurrentBase != filename)
515
      {
516
        if (auto fappend = dynamic_cast<FileAppender*>(appender))
517
          fappend->setFile(mapCurrentBase);
518
      }
519
    }
520
521
    return ( Date::currentTime() > m_priv->nextCheck) || (!m_priv->bAlreadyInitialized);
522
#endif
523
0
  }
524
525
0
  return Date::currentTime() > m_priv->nextCheck;
526
0
}
527
528
0
void TimeBasedRollingPolicy::setMultiprocess(bool multiprocess){
529
#if LOG4CXX_HAS_MULTIPROCESS_ROLLING_FILE_APPENDER
530
  // If we don't have the multiprocess stuff, disregard any attempt to set this value
531
  m_priv->multiprocess = multiprocess;
532
#endif
533
0
}
534
535
void TimeBasedRollingPolicy::setOption(const LogString& option,
536
  const LogString& value)
537
0
{
538
0
  if (StringHelper::equalsIgnoreCase(option,
539
0
      LOG4CXX_STR("THROWIOEXCEPTIONONFORKFAILURE"),
540
0
      LOG4CXX_STR("throwioexceptiononforkfailure")))
541
0
  {
542
0
    m_priv->throwIOExceptionOnForkFailure = OptionConverter::toBoolean(value, true);
543
0
  }
544
0
  else
545
0
  {
546
0
    RollingPolicyBase::setOption(option, value);
547
0
  }
548
0
}
549
550
/**
551
 * Was the name in shared memory set by this process?
552
 */
553
bool TimeBasedRollingPolicy::isLastFileNameUnchanged()
554
0
{
555
0
  bool result = true;
556
0
  if( m_priv->multiprocess ){
557
#if LOG4CXX_HAS_MULTIPROCESS_ROLLING_FILE_APPENDER
558
    if (m_priv->_mmap)
559
    {
560
      lockMMapFile(APR_FLOCK_SHARED);
561
      LogString mapCurrent(static_cast<logchar*>(m_priv->_mmap->mm));
562
      unLockMMapFile();
563
      result = (mapCurrent == m_priv->lastFileName);
564
    }
565
#endif
566
0
  }
567
0
  return result;
568
0
}
569
570
/**
571
 * Load the name (set by some other process) from shared memory
572
 */
573
void TimeBasedRollingPolicy::loadLastFileName()
574
0
{
575
0
  if( m_priv->multiprocess ){
576
#if LOG4CXX_HAS_MULTIPROCESS_ROLLING_FILE_APPENDER
577
    if (m_priv->_mmap)
578
    {
579
      lockMMapFile(APR_FLOCK_SHARED);
580
      LogString mapLastFile(static_cast<logchar*>(m_priv->_mmap->mm));
581
      unLockMMapFile();
582
      if (!mapLastFile.empty())
583
        m_priv->lastFileName = mapLastFile;
584
    }
585
#endif
586
0
  }
587
0
}