Coverage Report

Created: 2026-08-31 06:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/logging-log4cxx/src/main/cpp/fileappender.cpp
Line
Count
Source
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
18
#include <log4cxx/logstring.h>
19
#include <log4cxx/fileappender.h>
20
#include <log4cxx/helpers/stringhelper.h>
21
#include <log4cxx/helpers/loglog.h>
22
#include <log4cxx/helpers/optionconverter.h>
23
#include <log4cxx/helpers/pool.h>
24
#include <log4cxx/helpers/fileoutputstream.h>
25
#include <log4cxx/helpers/outputstreamwriter.h>
26
#include <log4cxx/helpers/bufferedwriter.h>
27
#include <log4cxx/helpers/bytebuffer.h>
28
#include "log4cxx/helpers/threadutility.h"
29
#include <log4cxx/private/writerappender_priv.h>
30
#include <log4cxx/private/fileappender_priv.h>
31
#include <limits>
32
#include <mutex>
33
34
using namespace LOG4CXX_NS;
35
using namespace LOG4CXX_NS::helpers;
36
using namespace LOG4CXX_NS::spi;
37
38
IMPLEMENT_LOG4CXX_OBJECT(FileAppender)
39
40
0
#define _priv static_cast<FileAppenderPriv*>(m_priv.get())
41
42
FileAppender::FileAppender() :
43
0
  WriterAppender (std::make_unique<FileAppenderPriv>())
44
0
{
45
0
}
Unexecuted instantiation: log4cxx::FileAppender::FileAppender()
Unexecuted instantiation: log4cxx::FileAppender::FileAppender()
46
47
FileAppender::FileAppender
48
  ( const LayoutPtr& layout1
49
  , const LogString& fileName1
50
  , bool append1
51
  , bool bufferedIO1
52
  , int bufferSize1
53
  )
54
0
  : WriterAppender(std::make_unique<FileAppenderPriv>(layout1, fileName1, append1, bufferedIO1, bufferSize1))
55
0
{
56
0
  activateOptionsInternal();
57
0
}
Unexecuted instantiation: log4cxx::FileAppender::FileAppender(std::__1::shared_ptr<log4cxx::Layout> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, bool, int)
Unexecuted instantiation: log4cxx::FileAppender::FileAppender(std::__1::shared_ptr<log4cxx::Layout> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool, bool, int)
58
59
FileAppender::FileAppender
60
  ( const LayoutPtr& layout1
61
  , const LogString& fileName1
62
  , bool append1
63
  )
64
0
  : WriterAppender(std::make_unique<FileAppenderPriv>(layout1, fileName1, append1, false))
65
0
{
66
0
  activateOptionsInternal();
67
0
}
Unexecuted instantiation: log4cxx::FileAppender::FileAppender(std::__1::shared_ptr<log4cxx::Layout> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool)
Unexecuted instantiation: log4cxx::FileAppender::FileAppender(std::__1::shared_ptr<log4cxx::Layout> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool)
68
69
FileAppender::FileAppender(const LayoutPtr& layout1, const LogString& fileName1)
70
0
  : WriterAppender(std::make_unique<FileAppenderPriv>(layout1, fileName1))
71
0
{
72
0
  activateOptionsInternal();
73
0
}
Unexecuted instantiation: log4cxx::FileAppender::FileAppender(std::__1::shared_ptr<log4cxx::Layout> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
Unexecuted instantiation: log4cxx::FileAppender::FileAppender(std::__1::shared_ptr<log4cxx::Layout> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
74
75
FileAppender::FileAppender(std::unique_ptr<FileAppenderPriv> priv)
76
0
  : WriterAppender (std::move(priv))
77
0
{
78
0
}
Unexecuted instantiation: log4cxx::FileAppender::FileAppender(std::__1::unique_ptr<log4cxx::FileAppender::FileAppenderPriv, std::__1::default_delete<log4cxx::FileAppender::FileAppenderPriv> >)
Unexecuted instantiation: log4cxx::FileAppender::FileAppender(std::__1::unique_ptr<log4cxx::FileAppender::FileAppenderPriv, std::__1::default_delete<log4cxx::FileAppender::FileAppenderPriv> >)
79
80
FileAppender::~FileAppender()
81
0
{
82
0
  if (auto p = _priv->taskManager.lock())
83
0
    p->value().removePeriodicTask(_priv->flushTaskName);
84
0
  if (_priv->flushTask)
85
0
    _priv->flushTask->detach(); // Blocks until any in-flight flush completes
86
0
}
87
88
void FileAppender::setAppend(bool fileAppend1)
89
0
{
90
0
  std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
91
0
  _priv->fileAppend = fileAppend1;
92
0
}
93
94
void FileAppender::setFile(const LogString& file)
95
0
{
96
0
  std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
97
0
  setFileInternal(file);
98
0
}
99
100
void FileAppender::setFileInternal(const LogString& file)
101
0
{
102
0
  _priv->fileName = file;
103
0
}
104
105
void FileAppender::setBufferedIO(bool bufferedIO1)
106
0
{
107
0
  std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
108
0
  _priv->bufferedIO = bufferedIO1;
109
110
0
  if (bufferedIO1)
111
0
  {
112
0
    setImmediateFlush(false);
113
0
  }
114
0
}
115
116
void FileAppender::setOption(const LogString& option,
117
  const LogString& value)
118
0
{
119
0
  if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("FILE"), LOG4CXX_STR("file"))
120
0
    || StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("FILENAME"), LOG4CXX_STR("filename")))
121
0
  {
122
0
    std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
123
0
    _priv->fileName = stripDuplicateBackslashes(value);
124
0
  }
125
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("APPEND"), LOG4CXX_STR("append")))
126
0
  {
127
0
    std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
128
0
    _priv->fileAppend = OptionConverter::toBoolean(value, true);
129
0
  }
130
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFEREDIO"), LOG4CXX_STR("bufferedio")))
131
0
  {
132
0
    std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
133
0
    _priv->bufferedIO = OptionConverter::toBoolean(value, false);
134
0
  }
135
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("IMMEDIATEFLUSH"), LOG4CXX_STR("immediateflush")))
136
0
  {
137
0
    std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
138
0
    _priv->bufferedIO = !OptionConverter::toBoolean(value, false);
139
0
  }
140
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFERSIZE"), LOG4CXX_STR("buffersize")))
141
0
  {
142
0
    std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
143
0
    long parsed = OptionConverter::toFileSize(value, 8 * 1024);
144
0
    if (parsed < 0 || parsed > static_cast<long>((std::numeric_limits<int>::max)()))
145
0
    {
146
0
      LogLog::warn(LOG4CXX_STR("FileAppender BufferSize is out of range. Using the default value."));
147
0
      parsed = 8 * 1024;
148
0
    }
149
0
    _priv->bufferSize = static_cast<int>(parsed);
150
0
  }
151
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFEREDSECONDS"), LOG4CXX_STR("bufferedseconds")))
152
0
  {
153
0
    std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
154
0
    _priv->bufferedSeconds = OptionConverter::toInt(value, 0);
155
0
  }
156
0
  else
157
0
  {
158
0
    WriterAppender::setOption(option, value);
159
0
  }
160
0
}
161
162
void FileAppender::activateOptions( LOG4CXX_ACTIVATE_OPTIONS_FORMAL_PARAMETERS )
163
0
{
164
0
  std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
165
0
  activateOptionsInternal();
166
0
}
167
168
void FileAppender::activateOptionsInternal()
169
0
{
170
0
  int errors = 0;
171
172
0
  if (!_priv->fileName.empty())
173
0
  {
174
0
    try
175
0
    {
176
0
      setFileInternal(_priv->fileName, _priv->fileAppend, _priv->bufferedIO, _priv->bufferSize);
177
0
    }
178
0
    catch (IOException& e)
179
0
    {
180
0
      errors++;
181
0
      LogString msg(LOG4CXX_STR("setFile("));
182
0
      msg.append(_priv->fileName);
183
0
      msg.append(1, (logchar) 0x2C /* ',' */);
184
0
      StringHelper::toString(_priv->fileAppend, msg);
185
0
      msg.append(LOG4CXX_STR(") call failed."));
186
0
      _priv->errorHandler->error(msg, e, ErrorCode::FILE_OPEN_FAILURE);
187
0
    }
188
0
  }
189
0
  else
190
0
  {
191
0
    errors++;
192
0
    LogLog::error(LogString(LOG4CXX_STR("File option not set for appender ["))
193
0
      +  _priv->name + LOG4CXX_STR("]."));
194
0
    LogLog::warn(LOG4CXX_STR("Are you using FileAppender instead of ConsoleAppender?"));
195
0
  }
196
197
0
  if (errors == 0)
198
0
  {
199
0
    _priv->activateOptions();
200
0
    if (auto p = _priv->taskManager.lock())
201
0
      p->value().removePeriodicTask(_priv->flushTaskName);
202
203
0
    if (!_priv->bufferedIO)
204
0
      ;
205
0
    else if (0 < _priv->bufferedSeconds)
206
0
    {
207
0
      auto taskManager = ThreadUtility::instancePtr();
208
0
      if (!_priv->flushTask)
209
0
      {
210
0
        _priv->flushTask = std::make_shared<FileAppenderPriv::FlushTask>();
211
0
        _priv->flushTask->priv = _priv;
212
0
      }
213
      // Key the task by a unique name: the appender may be renamed after
214
      // activation or share its name with another appender, and the
215
      // destructor must deregister this task, not another appender's.
216
0
      _priv->flushTaskName = getName();
217
0
      _priv->flushTaskName.append(1, (logchar) 0x23 /* '#' */);
218
0
      StringHelper::toString(reinterpret_cast<size_t>(m_priv.get()), _priv->flushTaskName);
219
0
      auto flushTask = _priv->flushTask;
220
0
      taskManager->value().addPeriodicTask(_priv->flushTaskName
221
0
        , [flushTask]() { flushTask->run(); }
222
0
        , std::chrono::seconds(_priv->bufferedSeconds)
223
0
        );
224
0
      _priv->taskManager = taskManager;
225
0
    }
226
0
  }
227
0
}
228
229
230
/**
231
 * Replaces double backslashes (except the leading doubles of UNC's)
232
 * with single backslashes for compatibility with existing path
233
 * specifications that were working around use of
234
 * OptionConverter::convertSpecialChars in XML configuration files.
235
 *
236
 * @param src source string
237
 * @return modified string
238
 *
239
 *
240
 */
241
LogString FileAppender::stripDuplicateBackslashes(const LogString& src)
242
0
{
243
0
  logchar backslash = 0x5C; // '\\'
244
0
  LogString::size_type i = src.find_last_of(backslash);
245
246
0
  if (i != LogString::npos)
247
0
  {
248
0
    LogString tmp(src);
249
250
0
    for (;
251
0
      i != LogString::npos && i > 0;
252
0
      i = tmp.find_last_of(backslash, i - 1))
253
0
    {
254
      //
255
      //   if the preceding character is a slash then
256
      //      remove the preceding character
257
      //      and continue processing
258
0
      if (tmp[i - 1] == backslash)
259
0
      {
260
0
        tmp.erase(i, 1);
261
0
        i--;
262
263
0
        if (i == 0)
264
0
        {
265
0
          break;
266
0
        }
267
0
      }
268
0
      else
269
0
      {
270
        //
271
        //  if there an odd number of slashes
272
        //     the string wasn't trying to work around
273
        //     OptionConverter::convertSpecialChars
274
0
        return src;
275
0
      }
276
0
    }
277
278
0
    return tmp;
279
0
  }
280
281
0
  return src;
282
0
}
283
284
/**
285
  <p>Sets and <i>opens</i> the file where the log output will
286
  go. The specified file must be writable.
287
288
  <p>If there was already an opened file, then the previous file
289
  is closed first.
290
291
  <p><b>Do not use this method directly. To configure a FileAppender
292
  or one of its subclasses, set its properties one by one and then
293
  call activateOptions.</b>
294
295
  @param filename The path to the log file.
296
  @param append   If true will append to fileName. Otherwise will
297
      truncate fileName.
298
  @param bufferedIO
299
  @param bufferSize
300
301
  @throws IOException
302
303
 */
304
void FileAppender::setFileInternal(
305
  const LogString& filename,
306
  bool append1,
307
  bool bufferedIO1,
308
  size_t bufferSize1)
309
0
{
310
  // It does not make sense to have immediate flush and bufferedIO.
311
0
  if (bufferedIO1)
312
0
  {
313
0
    setImmediateFlush(false);
314
0
  }
315
316
0
  _priv->close();
317
318
0
  bool writeBOM = false;
319
320
0
  if (StringHelper::equalsIgnoreCase(getEncoding(),
321
0
      LOG4CXX_STR("utf-16"), LOG4CXX_STR("UTF-16")))
322
0
  {
323
    //
324
    //    don't want to write a byte order mark if the file exists
325
    //
326
0
    if (append1)
327
0
    {
328
0
      File outFile;
329
0
      outFile.setPath(filename);
330
0
      writeBOM = !outFile.exists();
331
0
    }
332
0
    else
333
0
    {
334
0
      writeBOM = true;
335
0
    }
336
0
  }
337
338
0
  OutputStreamPtr outStream;
339
340
0
  try
341
0
  {
342
0
    outStream = std::make_shared<FileOutputStream>(filename, append1);
343
0
  }
344
0
  catch (IOException&)
345
0
  {
346
0
    LogString parentName = File().setPath(filename).getParent();
347
348
0
    if (!parentName.empty())
349
0
    {
350
0
      File parentDir;
351
0
      parentDir.setPath(parentName);
352
353
0
      if (!parentDir.exists() && parentDir.mkdirs())
354
0
      {
355
0
        outStream = std::make_shared<FileOutputStream>(filename, append1);
356
0
      }
357
0
      else
358
0
      {
359
0
        throw;
360
0
      }
361
0
    }
362
0
    else
363
0
    {
364
0
      throw;
365
0
    }
366
0
  }
367
368
369
  //
370
  //   if a new file and UTF-16, then write a BOM
371
  //
372
0
  if (writeBOM)
373
0
  {
374
0
    Pool p;
375
0
    char bom[] = { (char) 0xFE, (char) 0xFF };
376
0
    ByteBuffer buf(bom, 2);
377
0
    outStream->write(buf);
378
0
  }
379
380
0
  WriterPtr newWriter(createWriter(outStream));
381
382
0
  if (bufferedIO1)
383
0
  {
384
0
    newWriter = std::make_shared<BufferedWriter>(newWriter, bufferSize1);
385
0
  }
386
387
0
  _priv->setWriter(newWriter);
388
389
0
  _priv->fileAppend = append1;
390
0
  _priv->bufferedIO = bufferedIO1;
391
0
  _priv->fileName = filename;
392
0
  const size_t intMax = static_cast<size_t>((std::numeric_limits<int>::max)());
393
0
  _priv->bufferSize = static_cast<int>(bufferSize1 > intMax ? intMax : bufferSize1);
394
0
  _priv->writeHeader();
395
396
0
}
397
398
LogString FileAppender::getFile() const
399
0
{
400
0
  return _priv->fileName;
401
0
}
402
403
bool FileAppender::getBufferedIO() const
404
0
{
405
0
  return _priv->bufferedIO;
406
0
}
407
408
int FileAppender::getBufferSize() const
409
0
{
410
0
  return _priv->bufferSize;
411
0
}
412
413
int FileAppender::getBufferedSeconds() const
414
0
{
415
0
  return _priv->bufferedSeconds;
416
0
}
417
418
void FileAppender::setBufferSize(int newValue)
419
0
{
420
0
  if (newValue < 0)
421
0
  {
422
0
    LogLog::warn(LOG4CXX_STR("FileAppender BufferSize must be non-negative. Using zero."));
423
0
    newValue = 0;
424
0
  }
425
0
  _priv->bufferSize = newValue;
426
0
}
427
428
void FileAppender::setBufferedSeconds(int newValue)
429
0
{
430
0
  _priv->bufferedSeconds = newValue;
431
0
}
432
433
bool FileAppender::getAppend() const
434
0
{
435
0
  return _priv->fileAppend;
436
0
}
437
438
#if LOG4CXX_ABI_VERSION <= 15
439
void FileAppender::activateOptionsInternal(helpers::Pool& )
440
0
{ activateOptionsInternal(); }
441
void FileAppender::setFileInternal(const LogString& file, bool append,
442
  bool bufferedIO, size_t bufferSize,
443
  helpers::Pool&)
444
0
{ setFileInternal(file, append, bufferedIO, bufferSize); }
445
#endif