Coverage Report

Created: 2026-04-12 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/logging-log4cxx/src/main/cpp/asyncappender.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/asyncappender.h>
19
20
#include <log4cxx/helpers/loglog.h>
21
#include <log4cxx/spi/loggingevent.h>
22
#include <log4cxx/helpers/stringhelper.h>
23
#include <log4cxx/helpers/optionconverter.h>
24
#include <log4cxx/helpers/threadutility.h>
25
#include <log4cxx/private/appenderskeleton_priv.h>
26
#include <thread>
27
#include <atomic>
28
#include <condition_variable>
29
30
#if LOG4CXX_EVENTS_AT_EXIT
31
#include <log4cxx/private/atexitregistry.h>
32
#endif
33
34
using namespace LOG4CXX_NS;
35
using namespace LOG4CXX_NS::helpers;
36
using namespace LOG4CXX_NS::spi;
37
38
#if 15 < LOG4CXX_ABI_VERSION
39
namespace
40
{
41
#endif
42
43
/**
44
 * The default buffer size is set to 128 events.
45
*/
46
enum { DEFAULT_BUFFER_SIZE = 128 };
47
48
class DiscardSummary
49
{
50
  private:
51
    /**
52
     * First event of the highest severity.
53
    */
54
    LoggingEventPtr maxEvent;
55
56
    /**
57
    * Total count of messages discarded.
58
    */
59
    int count;
60
61
    /**
62
    * Why created
63
    */
64
    LogString reason;
65
66
  public:
67
    /**
68
     * Create new instance.
69
     *
70
     * @param event must not be null.
71
    */
72
    DiscardSummary(const LoggingEventPtr& event, const LogString& reason);
73
74
    /** Move values from \c src into a new instance.
75
    */
76
    DiscardSummary(DiscardSummary&& src);
77
#if 15 < LOG4CXX_ABI_VERSION
78
    /** Copy constructor.  */
79
    DiscardSummary(const DiscardSummary&) = delete;
80
    /** Assignment operator. */
81
    DiscardSummary& operator=(const DiscardSummary&) = delete;
82
#else
83
    /**
84
     * Create new instance.
85
     *
86
     * @param event event, may not be null.
87
    */
88
    DiscardSummary(const LoggingEventPtr& event);
89
    /** Copy constructor.  */
90
    DiscardSummary(const DiscardSummary& src);
91
    /** Assignment operator. */
92
    DiscardSummary& operator=(const DiscardSummary& src);
93
#endif
94
95
    /**
96
     * Add discarded event to summary.
97
     *
98
     * @param event event, may not be null.
99
    */
100
    void add(const LoggingEventPtr& event);
101
102
    /**
103
     * Create an event with a discard count and the message from \c maxEvent.
104
     *
105
     * @return the new event.
106
     */
107
    LoggingEventPtr createEvent(Pool& p);
108
109
#if LOG4CXX_ABI_VERSION <= 15
110
    static
111
    ::LOG4CXX_NS::spi::LoggingEventPtr createEvent(::LOG4CXX_NS::helpers::Pool& p,
112
      size_t discardedCount);
113
#endif
114
115
    /**
116
    * The number of messages discarded.
117
    */
118
0
    int getCount() const { return count; }
119
};
120
121
typedef std::map<LogString, DiscardSummary> DiscardMap;
122
123
#if 15 < LOG4CXX_ABI_VERSION
124
}
125
#endif
126
127
#ifdef __cpp_lib_hardware_interference_size
128
  using std::hardware_constructive_interference_size;
129
  using std::hardware_destructive_interference_size;
130
#else
131
  // 64 bytes on x86-64 │ L1_CACHE_BYTES │ L1_CACHE_SHIFT │ __cacheline_aligned │ ...
132
  constexpr std::size_t hardware_constructive_interference_size = 64;
133
  constexpr std::size_t hardware_destructive_interference_size = 64;
134
#endif
135
136
struct AsyncAppender::AsyncAppenderPriv : public AppenderSkeleton::AppenderSkeletonPrivate
137
{
138
  using BaseType = AppenderSkeleton::AppenderSkeletonPrivate;
139
  AsyncAppenderPriv()
140
0
    : AppenderSkeletonPrivate()
141
0
    , buffer(DEFAULT_BUFFER_SIZE)
142
0
    , bufferSize(DEFAULT_BUFFER_SIZE)
143
0
    , dispatcher()
144
0
    , locationInfo(false)
145
0
    , blocking(true)
146
#if LOG4CXX_EVENTS_AT_EXIT
147
    , atExitRegistryRaii([this]{if (setClosed()) stopDispatcher();})
148
#endif
149
0
    , eventCount(0)
150
0
    , dispatchedCount(0)
151
0
    , commitCount(0)
152
0
    { }
153
154
  ~AsyncAppenderPriv()
155
0
  {
156
0
    if (setClosed())
157
0
      close();
158
0
  }
159
160
  /**
161
   * Event buffer.
162
  */
163
  struct EventData
164
  {
165
    LoggingEventPtr event;
166
    size_t pendingCount;
167
  };
168
  std::vector<EventData> buffer;
169
170
  /**
171
   *  Mutex used to guard access to buffer and discardMap.
172
   */
173
  std::mutex bufferMutex;
174
175
  std::condition_variable bufferNotFull;
176
  std::condition_variable bufferNotEmpty;
177
178
  /**
179
    * Map of DiscardSummary objects keyed by logger name.
180
  */
181
  DiscardMap discardMap;
182
183
  /**
184
   * The maximum number of undispatched events.
185
  */
186
  int bufferSize;
187
188
  /**
189
   * Nested appenders.
190
  */
191
  helpers::AppenderAttachableImpl appenders;
192
193
  /**
194
   *  Dispatcher.
195
   */
196
  std::thread dispatcher;
197
198
  /**
199
   * Used to determine when to restart dispatch thread.
200
  */
201
  bool dispatcherActive{ false };
202
203
  /**
204
   * Used to determine whether to restart dispatch thread.
205
  */
206
  int dispatcherStartCount{ 0 };
207
208
  /**
209
   *  The function the dispatcher executes.
210
   */
211
  void dispatch(const LogString& appenderName);
212
213
  /**
214
   * Start dispatcher if not already running.
215
   */
216
  void checkDispatcher(const LogString& appenderName)
217
0
  {
218
    // Restart dispatcher if it has stopped by an exception in an attached appender.
219
0
    if (!this->dispatcherActive && this->dispatcher.joinable())
220
0
      this->dispatcher.join();
221
222
0
    if (!this->dispatcher.joinable() && this->dispatcherStartCount <= 1)
223
0
    {
224
0
      std::lock_guard<std::recursive_mutex> lock(this->mutex);
225
0
      if (!this->dispatcher.joinable())
226
0
      {
227
0
        ++this->dispatcherStartCount;
228
0
        this->dispatcherActive = true;
229
0
        this->dispatcher = ThreadUtility::instance()->createThread
230
0
          ( LOG4CXX_STR("AsyncAppender")
231
0
          , &AsyncAppender::AsyncAppenderPriv::dispatch
232
0
          , this
233
0
          , appenderName
234
0
          );
235
0
      }
236
0
    }
237
0
  }
238
239
  void stopDispatcher()
240
0
  {
241
0
    bufferNotEmpty.notify_all();
242
0
    bufferNotFull.notify_all();
243
244
0
    if (dispatcher.joinable())
245
0
    {
246
0
      dispatcher.join();
247
0
    }
248
0
  }
249
250
  void close();
251
252
  /**
253
   * Should location info be included in dispatched messages.
254
  */
255
  bool locationInfo;
256
257
  /**
258
   * Does appender block when buffer is full.
259
  */
260
  bool blocking;
261
262
#if LOG4CXX_EVENTS_AT_EXIT
263
  helpers::AtExitRegistry::Raii atExitRegistryRaii;
264
#endif
265
266
  /**
267
   * Used to calculate the buffer position at which to store the next event.
268
  */
269
  alignas(hardware_constructive_interference_size) std::atomic<size_t> eventCount;
270
271
  /**
272
   * Used to calculate the buffer position from which to extract the next event.
273
  */
274
  alignas(hardware_constructive_interference_size) std::atomic<size_t> dispatchedCount;
275
276
  /**
277
   * Used to communicate to the dispatch thread when an event is committed in buffer.
278
  */
279
  alignas(hardware_constructive_interference_size) std::atomic<size_t> commitCount;
280
281
  bool isClosed()
282
0
  {
283
0
    std::lock_guard<std::mutex> lock(this->bufferMutex);
284
0
    return this->closed;
285
0
  }
286
287
  bool setClosed()
288
0
  {
289
0
    std::lock_guard<std::mutex> lock(this->bufferMutex);
290
0
    return BaseType::setClosed();
291
0
  }
292
293
  /**
294
   * Used to ensure the dispatch thread does not wait when a logging thread is waiting.
295
  */
296
  alignas(hardware_constructive_interference_size) int blockedCount{0};
297
};
298
299
300
IMPLEMENT_LOG4CXX_OBJECT(AsyncAppender)
301
302
0
#define priv static_cast<AsyncAppenderPriv*>(m_priv.get())
303
304
AsyncAppender::AsyncAppender()
305
0
  : AppenderSkeleton(std::make_unique<AsyncAppenderPriv>())
306
0
{
307
0
}
Unexecuted instantiation: log4cxx::AsyncAppender::AsyncAppender()
Unexecuted instantiation: log4cxx::AsyncAppender::AsyncAppender()
308
309
AsyncAppender::~AsyncAppender()
310
0
{
311
0
}
312
313
void AsyncAppender::addAppender(const AppenderPtr newAppender)
314
0
{
315
0
  priv->appenders.addAppender(newAppender);
316
0
}
317
318
319
void AsyncAppender::setOption(const LogString& option,
320
  const LogString& value)
321
0
{
322
0
  if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("LOCATIONINFO"), LOG4CXX_STR("locationinfo")))
323
0
  {
324
0
    setLocationInfo(OptionConverter::toBoolean(value, false));
325
0
  }
326
327
0
  if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BUFFERSIZE"), LOG4CXX_STR("buffersize")))
328
0
  {
329
0
    setBufferSize(OptionConverter::toInt(value, DEFAULT_BUFFER_SIZE));
330
0
  }
331
332
0
  if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("BLOCKING"), LOG4CXX_STR("blocking")))
333
0
  {
334
0
    setBlocking(OptionConverter::toBoolean(value, true));
335
0
  }
336
0
  else
337
0
  {
338
0
    AppenderSkeleton::setOption(option, value);
339
0
  }
340
0
}
341
342
343
void AsyncAppender::doAppend(const spi::LoggingEventPtr& event, Pool& pool1)
344
0
{
345
0
  doAppendImpl(event, pool1);
346
0
}
347
348
void AsyncAppender::append(const spi::LoggingEventPtr& event, Pool& p)
349
0
{
350
0
  if (priv->bufferSize <= 0)
351
0
  {
352
0
    priv->appenders.appendLoopOnAppenders(event, p);
353
0
    return;
354
0
  }
355
356
  // Get a copy of this thread's diagnostic context
357
0
  event->LoadDC();
358
359
0
  priv->checkDispatcher(getName());
360
361
0
  if (priv->dispatcher.get_id() == std::this_thread::get_id()) // From an appender attached to this?
362
0
  {
363
0
    std::unique_lock<std::mutex> lock(priv->bufferMutex);
364
0
    auto loggerName = event->getLoggerName();
365
0
    auto iter = priv->discardMap.find(loggerName);
366
0
    if (priv->discardMap.end() == iter)
367
0
      priv->discardMap.emplace(loggerName, DiscardSummary{ event, LOG4CXX_STR("from an attached appender") });
368
0
    else
369
0
      iter->second.add(event);
370
0
  }
371
0
  else while (true)
372
0
  {
373
0
    auto pendingCount = priv->eventCount - priv->dispatchedCount;
374
0
    if (0 <= pendingCount && pendingCount < priv->bufferSize)
375
0
    {
376
      // Claim a slot in the ring buffer
377
0
      auto oldEventCount = priv->eventCount++;
378
0
      auto index = oldEventCount % priv->buffer.size();
379
      // Wait for a free slot
380
0
      while (priv->bufferSize <= oldEventCount - priv->dispatchedCount)
381
0
        std::this_thread::yield(); // Allow the dispatch thread to free a slot
382
      // Write to the ring buffer
383
0
      priv->buffer[index] = AsyncAppenderPriv::EventData{event, pendingCount};
384
      // Notify the dispatch thread that an event has been added
385
0
      auto failureCount = 0;
386
0
      auto savedEventCount = oldEventCount;
387
0
      while (!priv->commitCount.compare_exchange_weak(oldEventCount, oldEventCount + 1, std::memory_order_release))
388
0
      {
389
0
        oldEventCount = savedEventCount;
390
0
        if (2 < ++failureCount) // Did the scheduler suspend a thread between claiming a slot and advancing commitCount?
391
0
          std::this_thread::yield(); // Wait a bit
392
0
      }
393
0
      priv->bufferNotEmpty.notify_all();
394
0
      break;
395
0
    }
396
    //
397
    //   Following code is only reachable if buffer is full or eventCount has overflowed
398
    //
399
0
    std::unique_lock<std::mutex> lock(priv->bufferMutex);
400
0
    priv->bufferNotEmpty.notify_all();
401
    //
402
    //   if blocking and thread is not already interrupted
403
    //      and not the dispatcher then
404
    //      wait for a buffer notification
405
0
    bool discard = true;
406
407
0
    if (priv->blocking
408
0
      && !priv->closed)
409
0
    {
410
0
      ++priv->blockedCount;
411
0
      priv->bufferNotFull.wait(lock, [this]()
412
0
      {
413
0
        priv->checkDispatcher(getName());
414
0
        return priv->eventCount - priv->dispatchedCount < priv->bufferSize;
415
0
      });
416
0
      --priv->blockedCount;
417
0
      discard = false;
418
0
    }
419
420
    //
421
    //   if blocking is false or thread has been interrupted
422
    //   add event to discard map.
423
    //
424
0
    if (discard)
425
0
    {
426
0
      LogString loggerName = event->getLoggerName();
427
0
      DiscardMap::iterator iter = priv->discardMap.find(loggerName);
428
429
0
      if (iter == priv->discardMap.end())
430
0
      {
431
0
        priv->discardMap.emplace(loggerName, DiscardSummary{ event, LOG4CXX_STR("due to a full event buffer") });
432
0
      }
433
0
      else
434
0
      {
435
0
        iter->second.add(event);
436
0
      }
437
438
0
      break;
439
0
    }
440
0
  }
441
0
}
442
443
void AsyncAppender::close()
444
0
{
445
0
  if (priv->setClosed())
446
0
    priv->close();
447
0
}
448
449
void AsyncAppender::AsyncAppenderPriv::close()
450
0
{
451
0
  this->stopDispatcher();
452
0
  for (auto item : this->appenders.getAllAppenders())
453
0
  {
454
0
    item->close();
455
0
  }
456
0
}
457
458
AppenderList AsyncAppender::getAllAppenders() const
459
0
{
460
0
  return priv->appenders.getAllAppenders();
461
0
}
462
463
AppenderPtr AsyncAppender::getAppender(const LogString& n) const
464
0
{
465
0
  return priv->appenders.getAppender(n);
466
0
}
467
468
bool AsyncAppender::isAttached(const AppenderPtr appender) const
469
0
{
470
0
  return priv->appenders.isAttached(appender);
471
0
}
472
473
bool AsyncAppender::requiresLayout() const
474
0
{
475
0
  return false;
476
0
}
477
478
void AsyncAppender::removeAllAppenders()
479
0
{
480
0
  priv->appenders.removeAllAppenders();
481
0
}
482
483
void AsyncAppender::removeAppender(const AppenderPtr appender)
484
0
{
485
0
  priv->appenders.removeAppender(appender);
486
0
}
487
488
void AsyncAppender::removeAppender(const LogString& n)
489
0
{
490
0
  priv->appenders.removeAppender(n);
491
0
}
492
493
bool AsyncAppender::replaceAppender(const AppenderPtr& oldAppender, const AppenderPtr& newAppender)
494
0
{
495
0
  return priv->appenders.replaceAppender(oldAppender, newAppender);
496
0
}
497
498
void AsyncAppender::replaceAppenders( const AppenderList& newList)
499
0
{
500
0
  priv->appenders.replaceAppenders(newList);
501
0
}
502
503
bool AsyncAppender::getLocationInfo() const
504
0
{
505
0
  return priv->locationInfo;
506
0
}
507
508
void AsyncAppender::setLocationInfo(bool flag)
509
0
{
510
0
  priv->locationInfo = flag;
511
0
}
512
513
514
void AsyncAppender::setBufferSize(int size)
515
0
{
516
0
  if (size < 0)
517
0
  {
518
0
    throw IllegalArgumentException(LOG4CXX_STR("size argument must be non-negative"));
519
0
  }
520
521
0
  std::lock_guard<std::mutex> lock(priv->bufferMutex);
522
0
  if (priv->dispatcher.joinable())
523
0
  {
524
0
    throw RuntimeException(LOG4CXX_STR("AsyncAppender buffer size cannot be changed now"));
525
0
  }
526
0
  priv->bufferSize = (size < 1) ? 1 : size;
527
0
  priv->buffer.resize(priv->bufferSize);
528
0
  priv->bufferNotFull.notify_all();
529
0
}
530
531
int AsyncAppender::getBufferSize() const
532
0
{
533
0
  return priv->bufferSize;
534
0
}
535
536
void AsyncAppender::setBlocking(bool value)
537
0
{
538
0
  std::lock_guard<std::mutex> lock(priv->bufferMutex);
539
0
  priv->blocking = value;
540
0
  priv->bufferNotFull.notify_all();
541
0
}
542
543
bool AsyncAppender::getBlocking() const
544
0
{
545
0
  return priv->blocking;
546
0
}
547
548
DiscardSummary::DiscardSummary(const LoggingEventPtr& event, const LogString& reasonArg)
549
0
  : maxEvent(event)
550
0
  , count(1)
551
0
  , reason(reasonArg)
552
0
{
553
0
}
554
555
DiscardSummary::DiscardSummary(DiscardSummary&& other)
556
0
  : maxEvent(std::move(other.maxEvent))
557
0
  , count(other.count)
558
0
  , reason(std::move(other.reason))
559
0
{
560
0
}
561
562
#if LOG4CXX_ABI_VERSION <= 15
563
DiscardSummary::DiscardSummary(const LoggingEventPtr& event) :
564
0
  maxEvent(event), count(1)
565
0
{
566
0
}
567
568
DiscardSummary::DiscardSummary(const DiscardSummary& src) :
569
0
  maxEvent(src.maxEvent), count(src.count)
570
0
{
571
0
}
572
573
DiscardSummary& DiscardSummary::operator=(const DiscardSummary& src)
574
0
{
575
0
  maxEvent = src.maxEvent;
576
0
  count = src.count;
577
0
  return *this;
578
0
}
579
#endif
580
581
void DiscardSummary::add(const LoggingEventPtr& event)
582
0
{
583
0
  if (this->maxEvent->getLevel()->toInt() < event->getLevel()->toInt())
584
0
    this->maxEvent = event;
585
0
  ++this->count;
586
0
}
587
588
LoggingEventPtr DiscardSummary::createEvent(Pool& p)
589
0
{
590
0
  LogString msg(LOG4CXX_STR("Discarded "));
591
0
  StringHelper::toString(this->count, p, msg);
592
0
  msg.append(LOG4CXX_STR(" messages ") + this->reason + LOG4CXX_STR(" including: "));
593
0
  msg.append(this->maxEvent->getRenderedMessage());
594
0
  return std::make_shared<LoggingEvent>
595
0
    ( this->maxEvent->getLoggerName()
596
0
    , this->maxEvent->getLevel()
597
0
    , msg
598
0
    , LocationInfo::getLocationUnavailable()
599
0
    );
600
0
}
601
602
#if LOG4CXX_ABI_VERSION <= 15
603
::LOG4CXX_NS::spi::LoggingEventPtr
604
DiscardSummary::createEvent(::LOG4CXX_NS::helpers::Pool& p,
605
  size_t discardedCount)
606
0
{
607
0
  LogString msg(LOG4CXX_STR("Discarded "));
608
0
  StringHelper::toString(discardedCount, p, msg);
609
0
  msg.append(LOG4CXX_STR(" messages due to a full event buffer"));
610
611
0
  return std::make_shared<LoggingEvent>(
612
0
        LOG4CXX_STR(""),
613
0
        LOG4CXX_NS::Level::getError(),
614
0
        msg,
615
0
        LocationInfo::getLocationUnavailable() );
616
0
}
617
#endif
618
619
void AsyncAppender::AsyncAppenderPriv::dispatch(const LogString& appenderName)
620
0
{
621
0
  size_t discardCount = 0;
622
0
  size_t iterationCount = 0;
623
0
  size_t waitCount = 0;
624
0
  size_t producerBlockedCount = 0;
625
0
  int failureCount = 0;
626
0
  std::vector<size_t> pendingCountHistogram(this->bufferSize, 0);
627
0
  bool isActive = true;
628
629
0
  while (isActive)
630
0
  {
631
0
    Pool p;
632
0
    LoggingEventList events;
633
0
    events.reserve(this->bufferSize);
634
0
    for (int count = 0; count < 2 && this->dispatchedCount == this->commitCount; ++count)
635
0
      std::this_thread::yield(); // Wait a bit
636
0
    if (this->dispatchedCount == this->commitCount)
637
0
    {
638
0
      ++waitCount;
639
0
      std::unique_lock<std::mutex> lock(this->bufferMutex);
640
0
      this->bufferNotEmpty.wait(lock, [this]() -> bool
641
0
        { return 0 < this->blockedCount || this->dispatchedCount != this->commitCount || this->closed; }
642
0
      );
643
0
    }
644
0
    isActive = !this->isClosed();
645
646
0
    while (events.size() < this->bufferSize && this->dispatchedCount != this->commitCount)
647
0
    {
648
0
      auto index = this->dispatchedCount % this->buffer.size();
649
0
      const auto& data = this->buffer[index];
650
0
      events.push_back(data.event);
651
0
      if (data.pendingCount < pendingCountHistogram.size())
652
0
        ++pendingCountHistogram[data.pendingCount];
653
0
      ++this->dispatchedCount;
654
0
    }
655
0
    this->bufferNotFull.notify_all();
656
0
    {
657
0
      std::lock_guard<std::mutex> lock(this->bufferMutex);
658
0
      producerBlockedCount += this->blockedCount;
659
0
      for (auto& discardItem : this->discardMap)
660
0
      {
661
0
        events.push_back(discardItem.second.createEvent(p));
662
0
        discardCount += discardItem.second.getCount();
663
0
      }
664
0
      this->discardMap.clear();
665
0
    }
666
667
0
    for (auto item : events)
668
0
    {
669
0
      try
670
0
      {
671
0
        this->appenders.appendLoopOnAppenders(item, p);
672
0
      }
673
0
      catch (std::exception& ex)
674
0
      {
675
0
        if (1 < ++failureCount)
676
0
          break;
677
0
        if (!this->isClosed())
678
0
          this->errorHandler->error(LOG4CXX_STR("[") + appenderName + LOG4CXX_STR("] AsyncAppender"), ex, spi::ErrorCode::WRITE_FAILURE, item);
679
0
      }
680
0
      catch (...)
681
0
      {
682
0
        if (1 < ++failureCount)
683
0
          break;
684
0
        if (!this->isClosed())
685
0
          this->errorHandler->error(LOG4CXX_STR("[") + appenderName + LOG4CXX_STR("] AsyncAppender unknown exception thrown"));
686
0
      }
687
0
    }
688
0
    ++iterationCount;
689
0
    if (1 < failureCount)
690
0
      break;
691
0
  }
692
0
  if (LogLog::isDebugEnabled())
693
0
  {
694
0
    Pool p;
695
0
    LogString msg(LOG4CXX_STR("[") + appenderName + LOG4CXX_STR("] AsyncAppender"));
696
#ifdef _DEBUG
697
    msg += LOG4CXX_STR(" iterationCount ");
698
    StringHelper::toString(iterationCount, p, msg);
699
    msg += LOG4CXX_STR(" waitCount ");
700
    StringHelper::toString(waitCount, p, msg);
701
    msg += LOG4CXX_STR(" producerBlockedCount ");
702
    StringHelper::toString(producerBlockedCount, p, msg);
703
    msg += LOG4CXX_STR(" commitCount ");
704
    StringHelper::toString(this->commitCount, p, msg);
705
#endif
706
0
    msg += LOG4CXX_STR(" dispatchedCount ");
707
0
    StringHelper::toString(this->dispatchedCount, p, msg);
708
0
    msg += LOG4CXX_STR(" discardCount ");
709
0
    StringHelper::toString(discardCount, p, msg);
710
0
    msg += LOG4CXX_STR(" pendingCountHistogram");
711
0
    for (auto item : pendingCountHistogram)
712
0
    {
713
0
      msg += logchar(' ');
714
0
      StringHelper::toString(item, p, msg);
715
0
    }
716
0
    LogLog::debug(msg);
717
0
  }
718
0
  this->dispatcherActive = false;
719
0
  if (0 < this->blockedCount) // Restart this dispatcher?
720
0
    this->bufferNotFull.notify_all();
721
0
}