/src/logging-log4cxx/src/main/cpp/threadutility.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/helpers/threadutility.h" |
19 | | #if !defined(LOG4CXX) |
20 | | #define LOG4CXX 1 |
21 | | #endif |
22 | | #include "log4cxx/private/log4cxx_private.h" |
23 | | #include "log4cxx/helpers/loglog.h" |
24 | | #include "log4cxx/helpers/transcoder.h" |
25 | | |
26 | | #include <signal.h> |
27 | | #include <mutex> |
28 | | #include <list> |
29 | | #include <condition_variable> |
30 | | #include <algorithm> |
31 | | |
32 | | #ifdef _WIN32 |
33 | | #include <windows.h> |
34 | | #include <processthreadsapi.h> |
35 | | #endif |
36 | | |
37 | | #if LOG4CXX_EVENTS_AT_EXIT |
38 | | #include <log4cxx/private/atexitregistry.h> |
39 | | #endif |
40 | | #if !defined(LOG4CXX) |
41 | | #define LOG4CXX 1 |
42 | | #endif |
43 | | #include <log4cxx/helpers/aprinitializer.h> |
44 | | |
45 | | namespace LOG4CXX_NS |
46 | | { |
47 | | namespace helpers |
48 | | { |
49 | | |
50 | | struct ThreadUtility::priv_data |
51 | | { |
52 | | priv_data() |
53 | | #if LOG4CXX_EVENTS_AT_EXIT |
54 | | : atExitRegistryRaii{ [this]{ stopThread(); } } |
55 | | #endif |
56 | 2 | { |
57 | 2 | } |
58 | | |
59 | | ~priv_data() |
60 | 2 | { stopThread(); } |
61 | | |
62 | | ThreadStartPre start_pre{nullptr}; |
63 | | ThreadStarted started{nullptr}; |
64 | | ThreadStartPost start_post{nullptr}; |
65 | | |
66 | | using TimePoint = std::chrono::time_point<std::chrono::system_clock>; |
67 | | struct NamedPeriodicFunction |
68 | | { |
69 | | LogString name; |
70 | | Period delay; |
71 | | TimePoint nextRun; |
72 | | std::function<void()> f; |
73 | | int errorCount; |
74 | | bool removed; |
75 | | }; |
76 | | using JobStore = std::list<NamedPeriodicFunction>; |
77 | | JobStore jobs; |
78 | | std::recursive_mutex job_mutex; |
79 | | std::thread thread; |
80 | | std::condition_variable interrupt; |
81 | | std::mutex interrupt_mutex; |
82 | | bool terminated{ false }; |
83 | | int retryCount{ 2 }; |
84 | | Period maxDelay{ 0 }; |
85 | | bool threadIsActive{ false }; |
86 | | |
87 | | void doPeriodicTasks(); |
88 | | |
89 | | void setTerminated() |
90 | 451 | { |
91 | 451 | std::lock_guard<std::mutex> lock(interrupt_mutex); |
92 | 451 | terminated = true; |
93 | 451 | } |
94 | | |
95 | | void stopThread() |
96 | 451 | { |
97 | 451 | setTerminated(); |
98 | 451 | interrupt.notify_all(); |
99 | 451 | if (thread.joinable()) |
100 | 0 | thread.join(); |
101 | 451 | } |
102 | | |
103 | | #if LOG4CXX_EVENTS_AT_EXIT |
104 | | helpers::AtExitRegistry::Raii atExitRegistryRaii; |
105 | | #endif |
106 | | }; |
107 | | |
108 | | #if LOG4CXX_HAS_PTHREAD_SIGMASK |
109 | | static thread_local sigset_t old_mask; |
110 | | static thread_local bool sigmask_valid; |
111 | | #endif |
112 | | |
113 | | ThreadUtility::ThreadUtility() |
114 | 2 | : m_priv( std::make_unique<priv_data>() ) |
115 | 2 | { |
116 | | // Block signals by default. |
117 | 2 | configureFuncs( std::bind( &ThreadUtility::preThreadBlockSignals, this ), |
118 | 2 | nullptr, |
119 | 2 | std::bind( &ThreadUtility::postThreadUnblockSignals, this ) ); |
120 | 2 | } |
121 | | |
122 | 2 | ThreadUtility::~ThreadUtility() {} |
123 | | |
124 | | auto ThreadUtility::instancePtr() -> ManagerPtr |
125 | 451 | { |
126 | 451 | auto result = APRInitializer::getOrAddUnique<Manager> |
127 | 451 | ( []() -> ObjectPtr |
128 | 451 | { return std::make_shared<Manager>(); } |
129 | 451 | ); |
130 | 451 | return result; |
131 | 451 | } |
132 | | |
133 | | ThreadUtility* ThreadUtility::instance() |
134 | 451 | { |
135 | 451 | return &instancePtr()->value(); |
136 | 451 | } |
137 | | |
138 | | void ThreadUtility::configure( ThreadConfigurationType type ) |
139 | 2 | { |
140 | 2 | auto utility = instance(); |
141 | | |
142 | 2 | if ( type == ThreadConfigurationType::NoConfiguration ) |
143 | 1 | { |
144 | 1 | utility->configureFuncs( nullptr, nullptr, nullptr ); |
145 | 1 | } |
146 | 1 | else if ( type == ThreadConfigurationType::NameThreadOnly ) |
147 | 0 | { |
148 | 0 | utility->configureFuncs( nullptr, |
149 | 0 | std::bind( &ThreadUtility::threadStartedNameThread, utility, |
150 | 0 | std::placeholders::_1, |
151 | 0 | std::placeholders::_2, |
152 | 0 | std::placeholders::_3 ), |
153 | 0 | nullptr ); |
154 | 0 | } |
155 | 1 | else if ( type == ThreadConfigurationType::BlockSignalsOnly ) |
156 | 1 | { |
157 | 1 | utility->configureFuncs( std::bind( &ThreadUtility::preThreadBlockSignals, utility ), |
158 | 1 | nullptr, |
159 | 1 | std::bind( &ThreadUtility::postThreadUnblockSignals, utility ) ); |
160 | 1 | } |
161 | 0 | else if ( type == ThreadConfigurationType::BlockSignalsAndNameThread ) |
162 | 0 | { |
163 | 0 | utility->configureFuncs( std::bind( &ThreadUtility::preThreadBlockSignals, utility ), |
164 | 0 | std::bind( &ThreadUtility::threadStartedNameThread, utility, |
165 | 0 | std::placeholders::_1, |
166 | 0 | std::placeholders::_2, |
167 | 0 | std::placeholders::_3 ), |
168 | 0 | std::bind( &ThreadUtility::postThreadUnblockSignals, utility ) ); |
169 | 0 | } |
170 | 2 | } |
171 | | |
172 | | void ThreadUtility::configureFuncs( ThreadStartPre pre_start, |
173 | | ThreadStarted started, |
174 | | ThreadStartPost post_start ) |
175 | 4 | { |
176 | 4 | m_priv->start_pre = pre_start; |
177 | 4 | m_priv->started = started; |
178 | 4 | m_priv->start_post = post_start; |
179 | 4 | } log4cxx::helpers::ThreadUtility::configureFuncs(std::__1::function<void ()>, std::__1::function<void (std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::__thread_id, unsigned long)>, std::__1::function<void ()>) Line | Count | Source | 175 | 4 | { | 176 | 4 | m_priv->start_pre = pre_start; | 177 | 4 | m_priv->started = started; | 178 | 4 | m_priv->start_post = post_start; | 179 | 4 | } |
Unexecuted instantiation: log4cxx::helpers::ThreadUtility::configureFuncs(std::__1::function<void ()>, std::__1::function<void (std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> >, std::__1::__thread_id, unsigned long)>, std::__1::function<void ()>) |
180 | | |
181 | | void ThreadUtility::preThreadBlockSignals() |
182 | 0 | { |
183 | 0 | #if LOG4CXX_HAS_PTHREAD_SIGMASK |
184 | 0 | sigset_t set; |
185 | 0 | sigfillset(&set); |
186 | |
|
187 | 0 | if ( pthread_sigmask(SIG_SETMASK, &set, &old_mask) < 0 ) |
188 | 0 | { |
189 | 0 | LOGLOG_ERROR( LOG4CXX_STR("Unable to set thread sigmask") ); |
190 | 0 | sigmask_valid = false; |
191 | 0 | } |
192 | 0 | else |
193 | 0 | { |
194 | 0 | sigmask_valid = true; |
195 | 0 | } |
196 | |
|
197 | 0 | #endif /* LOG4CXX_HAS_PTHREAD_SIGMASK */ |
198 | 0 | } Unexecuted instantiation: log4cxx::helpers::ThreadUtility::preThreadBlockSignals() Unexecuted instantiation: log4cxx::helpers::ThreadUtility::preThreadBlockSignals() |
199 | | |
200 | | void ThreadUtility::threadStartedNameThread(LogString threadName, |
201 | | std::thread::id /*threadId*/, |
202 | | std::thread::native_handle_type nativeHandle) |
203 | 0 | { |
204 | 0 | #if LOG4CXX_HAS_PTHREAD_SETNAME && !(defined(_WIN32) && defined(_LIBCPP_VERSION)) |
205 | 0 | LOG4CXX_ENCODE_CHAR(sthreadName, threadName); |
206 | 0 | if (pthread_setname_np(static_cast<pthread_t>(nativeHandle), sthreadName.c_str()) < 0) { |
207 | 0 | LOGLOG_ERROR(LOG4CXX_STR("unable to set thread name")); |
208 | 0 | } |
209 | | #elif defined(_WIN32) |
210 | | typedef HRESULT (WINAPI *TSetThreadDescription)(HANDLE, PCWSTR); |
211 | | static struct initialiser |
212 | | { |
213 | | HMODULE hKernelBase; |
214 | | TSetThreadDescription SetThreadDescription; |
215 | | initialiser() |
216 | | : hKernelBase(GetModuleHandleA("KernelBase.dll")) |
217 | | , SetThreadDescription(nullptr) |
218 | | { |
219 | | if (hKernelBase) |
220 | | SetThreadDescription = reinterpret_cast<TSetThreadDescription>(GetProcAddress(hKernelBase, "SetThreadDescription")); |
221 | | } |
222 | | } win32Func; |
223 | | if (win32Func.SetThreadDescription) |
224 | | { |
225 | | LOG4CXX_ENCODE_WCHAR(wthreadName, threadName); |
226 | | if(FAILED(win32Func.SetThreadDescription(static_cast<HANDLE>(nativeHandle), wthreadName.c_str()))) |
227 | | LOGLOG_ERROR( LOG4CXX_STR("unable to set thread name") ); |
228 | | } |
229 | | #endif |
230 | 0 | } Unexecuted instantiation: log4cxx::helpers::ThreadUtility::threadStartedNameThread(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::__thread_id, unsigned long) Unexecuted instantiation: log4cxx::helpers::ThreadUtility::threadStartedNameThread(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> >, std::__1::__thread_id, unsigned long) |
231 | | |
232 | | void ThreadUtility::postThreadUnblockSignals() |
233 | 0 | { |
234 | 0 | #if LOG4CXX_HAS_PTHREAD_SIGMASK |
235 | | |
236 | | // Only restore the signal mask if we were able to set it in the first place. |
237 | 0 | if ( sigmask_valid ) |
238 | 0 | { |
239 | 0 | if ( pthread_sigmask(SIG_SETMASK, &old_mask, nullptr) < 0 ) |
240 | 0 | { |
241 | 0 | LOGLOG_ERROR( LOG4CXX_STR("Unable to set thread sigmask") ); |
242 | 0 | } |
243 | 0 | } |
244 | |
|
245 | 0 | #endif /* LOG4CXX_HAS_PTHREAD_SIGMASK */ |
246 | 0 | } Unexecuted instantiation: log4cxx::helpers::ThreadUtility::postThreadUnblockSignals() Unexecuted instantiation: log4cxx::helpers::ThreadUtility::postThreadUnblockSignals() |
247 | | |
248 | | |
249 | | ThreadStartPre ThreadUtility::preStartFunction() |
250 | 0 | { |
251 | 0 | return m_priv->start_pre; |
252 | 0 | } |
253 | | |
254 | | ThreadStarted ThreadUtility::threadStartedFunction() |
255 | 0 | { |
256 | 0 | return m_priv->started; |
257 | 0 | } |
258 | | |
259 | | ThreadStartPost ThreadUtility::postStartFunction() |
260 | 0 | { |
261 | 0 | return m_priv->start_post; |
262 | 0 | } |
263 | | |
264 | | /** |
265 | | * Add a periodic task |
266 | | */ |
267 | | void ThreadUtility::addPeriodicTask(const LogString& name, std::function<void()> f, const Period& delay) |
268 | 0 | { |
269 | 0 | std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex); |
270 | 0 | if (m_priv->maxDelay < delay) |
271 | 0 | m_priv->maxDelay = delay; |
272 | 0 | auto currentTime = std::chrono::system_clock::now(); |
273 | 0 | m_priv->jobs.push_back( priv_data::NamedPeriodicFunction{name, delay, currentTime + delay, f, 0, false} ); |
274 | | |
275 | | // Restart thread if it has stopped. |
276 | 0 | if (!m_priv->threadIsActive && m_priv->thread.joinable()) |
277 | 0 | m_priv->thread.join(); |
278 | |
|
279 | 0 | if (!m_priv->thread.joinable()) |
280 | 0 | { |
281 | 0 | m_priv->terminated = false; |
282 | 0 | m_priv->threadIsActive = true; |
283 | 0 | m_priv->thread = createThread(LOG4CXX_STR("log4cxx"), std::bind(&priv_data::doPeriodicTasks, m_priv.get())); |
284 | 0 | } |
285 | 0 | else |
286 | 0 | m_priv->interrupt.notify_one(); |
287 | 0 | } Unexecuted instantiation: log4cxx::helpers::ThreadUtility::addPeriodicTask(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::function<void ()>, std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000l> > const&) Unexecuted instantiation: log4cxx::helpers::ThreadUtility::addPeriodicTask(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&, std::__1::function<void ()>, std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000l> > const&) |
288 | | |
289 | | /** |
290 | | * Is this already running a \c taskName periodic task? |
291 | | */ |
292 | | bool ThreadUtility::hasPeriodicTask(const LogString& name) |
293 | 0 | { |
294 | 0 | std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex); |
295 | 0 | auto pItem = std::find_if(m_priv->jobs.begin(), m_priv->jobs.end() |
296 | 0 | , [&name](const priv_data::NamedPeriodicFunction& item) |
297 | 0 | { return !item.removed && name == item.name; }Unexecuted instantiation: threadutility.cpp:log4cxx::helpers::ThreadUtility::hasPeriodicTask(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0::operator()(log4cxx::helpers::ThreadUtility::priv_data::NamedPeriodicFunction const&) const Unexecuted instantiation: threadutility.cpp:log4cxx::helpers::ThreadUtility::hasPeriodicTask(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&)::$_0::operator()(log4cxx::helpers::ThreadUtility::priv_data::NamedPeriodicFunction const&) const |
298 | 0 | ); |
299 | 0 | return m_priv->jobs.end() != pItem; |
300 | 0 | } Unexecuted instantiation: log4cxx::helpers::ThreadUtility::hasPeriodicTask(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) Unexecuted instantiation: log4cxx::helpers::ThreadUtility::hasPeriodicTask(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&) |
301 | | |
302 | | /** |
303 | | * Remove all periodic tasks and stop the processing thread |
304 | | */ |
305 | | void ThreadUtility::removeAllPeriodicTasks() |
306 | 449 | { |
307 | 449 | { |
308 | 449 | std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex); |
309 | 449 | while (!m_priv->jobs.empty()) |
310 | 0 | m_priv->jobs.pop_back(); |
311 | 449 | } |
312 | 449 | m_priv->stopThread(); |
313 | 449 | } |
314 | | |
315 | | /** |
316 | | * Remove the \c taskName periodic task |
317 | | */ |
318 | | void ThreadUtility::removePeriodicTask(const LogString& name) |
319 | 0 | { |
320 | 0 | std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex); |
321 | 0 | auto pItem = std::find_if(m_priv->jobs.begin(), m_priv->jobs.end() |
322 | 0 | , [&name](const priv_data::NamedPeriodicFunction& item) |
323 | 0 | { return !item.removed && name == item.name; }Unexecuted instantiation: threadutility.cpp:log4cxx::helpers::ThreadUtility::removePeriodicTask(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0::operator()(log4cxx::helpers::ThreadUtility::priv_data::NamedPeriodicFunction const&) const Unexecuted instantiation: threadutility.cpp:log4cxx::helpers::ThreadUtility::removePeriodicTask(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&)::$_0::operator()(log4cxx::helpers::ThreadUtility::priv_data::NamedPeriodicFunction const&) const |
324 | 0 | ); |
325 | 0 | if (m_priv->jobs.end() != pItem) |
326 | 0 | { |
327 | 0 | pItem->removed = true; |
328 | 0 | m_priv->interrupt.notify_one(); |
329 | 0 | } |
330 | 0 | } Unexecuted instantiation: log4cxx::helpers::ThreadUtility::removePeriodicTask(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) Unexecuted instantiation: log4cxx::helpers::ThreadUtility::removePeriodicTask(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&) |
331 | | |
332 | | /** |
333 | | * Remove any periodic task matching \c namePrefix |
334 | | */ |
335 | | void ThreadUtility::removePeriodicTasksMatching(const LogString& namePrefix) |
336 | 0 | { |
337 | 0 | while (1) |
338 | 0 | { |
339 | 0 | std::lock_guard<std::recursive_mutex> lock(m_priv->job_mutex); |
340 | 0 | auto pItem = std::find_if(m_priv->jobs.begin(), m_priv->jobs.end() |
341 | 0 | , [&namePrefix](const priv_data::NamedPeriodicFunction& item) |
342 | 0 | { return !item.removed && namePrefix.size() <= item.name.size() && item.name.substr(0, namePrefix.size()) == namePrefix; }Unexecuted instantiation: threadutility.cpp:log4cxx::helpers::ThreadUtility::removePeriodicTasksMatching(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)::$_0::operator()(log4cxx::helpers::ThreadUtility::priv_data::NamedPeriodicFunction const&) const Unexecuted instantiation: threadutility.cpp:log4cxx::helpers::ThreadUtility::removePeriodicTasksMatching(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&)::$_0::operator()(log4cxx::helpers::ThreadUtility::priv_data::NamedPeriodicFunction const&) const |
343 | 0 | ); |
344 | 0 | if (m_priv->jobs.end() == pItem) |
345 | 0 | break; |
346 | 0 | pItem->removed = true; |
347 | 0 | } |
348 | 0 | m_priv->interrupt.notify_one(); |
349 | 0 | } Unexecuted instantiation: log4cxx::helpers::ThreadUtility::removePeriodicTasksMatching(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) Unexecuted instantiation: log4cxx::helpers::ThreadUtility::removePeriodicTasksMatching(std::__1::basic_string<wchar_t, std::__1::char_traits<wchar_t>, std::__1::allocator<wchar_t> > const&) |
350 | | |
351 | | // Run ready tasks |
352 | | void ThreadUtility::priv_data::doPeriodicTasks() |
353 | 0 | { |
354 | 0 | while (!this->terminated) |
355 | 0 | { |
356 | 0 | auto currentTime = std::chrono::system_clock::now(); |
357 | 0 | TimePoint nextOperationTime = currentTime + this->maxDelay; |
358 | 0 | { |
359 | 0 | std::lock_guard<std::recursive_mutex> lock(this->job_mutex); |
360 | 0 | for (auto& item : this->jobs) |
361 | 0 | { |
362 | 0 | if (this->terminated) |
363 | 0 | return; |
364 | 0 | if (item.removed) |
365 | 0 | ; |
366 | 0 | else if (item.nextRun <= currentTime) |
367 | 0 | { |
368 | 0 | try |
369 | 0 | { |
370 | 0 | item.f(); |
371 | 0 | item.nextRun = std::chrono::system_clock::now() + item.delay; |
372 | 0 | if (item.nextRun < nextOperationTime) |
373 | 0 | nextOperationTime = item.nextRun; |
374 | 0 | item.errorCount = 0; |
375 | 0 | } |
376 | 0 | catch (std::exception& ex) |
377 | 0 | { |
378 | 0 | LogLog::warn(item.name, ex); |
379 | 0 | ++item.errorCount; |
380 | 0 | } |
381 | 0 | catch (...) |
382 | 0 | { |
383 | 0 | LogLog::warn(item.name + LOG4CXX_STR(" threw an exception")); |
384 | 0 | ++item.errorCount; |
385 | 0 | } |
386 | 0 | } |
387 | 0 | else if (item.nextRun < nextOperationTime) |
388 | 0 | nextOperationTime = item.nextRun; |
389 | 0 | } |
390 | 0 | } |
391 | | // Delete removed and faulty tasks |
392 | 0 | while (1) |
393 | 0 | { |
394 | 0 | std::lock_guard<std::recursive_mutex> lock(this->job_mutex); |
395 | 0 | auto pItem = std::find_if(this->jobs.begin(), this->jobs.end() |
396 | 0 | , [this](const NamedPeriodicFunction& item) |
397 | 0 | { return item.removed || this->retryCount < item.errorCount; } |
398 | 0 | ); |
399 | 0 | if (this->jobs.end() == pItem) |
400 | 0 | break; |
401 | 0 | this->jobs.erase(pItem); |
402 | 0 | if (this->jobs.empty()) |
403 | 0 | return; |
404 | 0 | } |
405 | | |
406 | 0 | std::unique_lock<std::mutex> lock(this->interrupt_mutex); |
407 | 0 | this->interrupt.wait_until(lock, nextOperationTime); |
408 | 0 | } |
409 | 0 | this->threadIsActive = false; |
410 | 0 | } |
411 | | |
412 | | } //namespace helpers |
413 | | } //namespace log4cxx |