Skip to content

Commit 08c83f5

Browse files
authored
Allow binary-to-text conversion to be moved to a background thread (#554)
* Update example benchmark data * Allow all wide or all narrow strings in XXXXX_FMT_ASYNC macros * Add a unit test * Older fmt version fails so remove it from CI * Include XXXXX_ASYNC macro support in the Log4cxx configuration summary
1 parent 923d9c1 commit 08c83f5

File tree

16 files changed

+591
-40
lines changed

16 files changed

+591
-40
lines changed

.github/workflows/log4cxx-ubuntu.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
- name: ubuntu22-clang
4343
os: ubuntu-22.04
4444
cxx: clang++
45-
fmt: ON
45+
fmt: OFF
4646
qt: OFF
4747
qt6: OFF
4848
odbc: ON
@@ -55,7 +55,7 @@ jobs:
5555
- name: ubuntu24-gcc
5656
os: ubuntu-24.04
5757
cxx: g++
58-
fmt: OFF
58+
fmt: ON
5959
qt: ON
6060
qt6: ON
6161
odbc: OFF

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,10 @@ endif(LOG4CXX_ENABLE_ESMTP)
105105
find_package(fmt 7.1 QUIET)
106106
if(${fmt_FOUND})
107107
option(ENABLE_FMT_LAYOUT "Enable the FMT layout(if libfmt found)" ON)
108+
option(ENABLE_FMT_ASYNC "Add support for the LOG4CXX_XXXX_FMT_ASYNC macros (if libfmt found)" ON)
108109
else()
109110
set(ENABLE_FMT_LAYOUT "OFF")
111+
set(ENABLE_FMT_ASYNC "OFF")
110112
endif()
111113

112114
option(LOG4CXX_CONFIG_VAR_EXPANSION "Expand \${varname} instances in LOG4CXX_CONFIGURATION" ON)
@@ -299,6 +301,8 @@ message(STATUS " character encoding .............. : ${LOG4CXX_CHARSET}")
299301
message(STATUS " Networking support .............. : ${LOG4CXX_NETWORKING_SUPPORT}")
300302
message(STATUS " DOMConfigurator support ......... : ${LOG4CXX_DOMCONFIGURATOR_SUPPORT}")
301303
message(STATUS " Qt support ...................... : ${LOG4CXX_QT_SUPPORT}")
304+
message(STATUS " LOG4CXX_XXXX_ASYNC macros ....... : ON")
305+
message(STATUS " LOG4CXX_XXXX_FMT_ASYNC macros ... : ${ENABLE_FMT_ASYNC}")
302306
message(STATUS "C++ version and Boost settings:")
303307
message(STATUS " Prefer boost: ................... : ${PREFER_BOOST}")
304308
message(STATUS " make_unique implementation :..... : ${STD_MAKE_UNIQUE_IMPL}")

src/examples/cpp/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,17 @@ if(${fmt_FOUND})
7474
)
7575
endif()
7676
endif(${fmt_FOUND})
77+
78+
if(ENABLE_FMT_ASYNC)
79+
add_executable( format-async format-async.cpp )
80+
target_compile_definitions(format-async PRIVATE ${LOG4CXX_COMPILE_DEFINITIONS} ${APR_COMPILE_DEFINITIONS} ${APR_UTIL_COMPILE_DEFINITIONS} )
81+
target_include_directories(format-async PRIVATE ${CMAKE_CURRENT_LIST_DIR} $<TARGET_PROPERTY:log4cxx,INCLUDE_DIRECTORIES>)
82+
target_link_libraries(format-async PRIVATE log4cxx ${APR_UTIL_LIBRARIES} ${EXPAT_LIBRARIES} ${APR_LIBRARIES} ${APR_SYSTEM_LIBS} fmt::fmt)
83+
if( WIN32 )
84+
set_target_properties( format-async PROPERTIES
85+
VS_DEBUGGER_ENVIRONMENT "PATH=${ESCAPED_PATH}"
86+
VS_DEBUGGER_WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
87+
FOLDER Examples
88+
)
89+
endif()
90+
endif(ENABLE_FMT_ASYNC)

src/examples/cpp/format-async.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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 <stdlib.h>
19+
#include <log4cxx/logger.h>
20+
#include <log4cxx/logmanager.h>
21+
#include <log4cxx/basicconfigurator.h>
22+
#include <locale.h>
23+
#include <fmt/core.h>
24+
#include <fmt/args.h>
25+
#include <fmt/color.h>
26+
#include <fmt/ostream.h>
27+
#include <iomanip>
28+
29+
using namespace log4cxx;
30+
using OutputStreamType = std::basic_ostream<logchar>;
31+
32+
struct MyStruct {
33+
int x;
34+
};
35+
OutputStreamType&
36+
operator<<(OutputStreamType& stream, const MyStruct& mystruct )
37+
{
38+
stream << LOG4CXX_STR("[MyStruct x: ") << mystruct.x << LOG4CXX_STR("]");
39+
return stream;
40+
}
41+
#if FMT_VERSION >= (9 * 10000)
42+
template <> struct fmt::formatter<MyStruct, logchar> : fmt::basic_ostream_formatter<logchar> {};
43+
#endif
44+
45+
int main()
46+
{
47+
setlocale(LC_ALL, "");
48+
49+
BasicConfigurator::configureAsync();
50+
auto rootLogger = LogManager::getRootLogger();
51+
52+
LOG4CXX_INFO_FMT_ASYNC( rootLogger, "This is a {} message", "char" );
53+
LOG4CXX_INFO_FMT_ASYNC( rootLogger, LOG4CXX_STR("This is a {} message"), LOG4CXX_STR("LogString") );
54+
LOG4CXX_INFO_FMT_ASYNC( rootLogger, LOG4CXX_STR("We can also align text to the {:<10} or {:>10}"), LOG4CXX_STR("left"), LOG4CXX_STR("right") );
55+
56+
MyStruct mine{ 42 };
57+
LOG4CXX_INFO_FMT_ASYNC( rootLogger, LOG4CXX_STR("This custom type {} can also be logged, since it implements operator<<"), mine );
58+
59+
LOG4CXX_INFO_ASYNC( rootLogger, LOG4CXX_STR("Numbers can be formatted with excessive operator<<: ")
60+
<< std::setprecision(3) << 22.456
61+
<< LOG4CXX_STR(" And as hex: ")
62+
<< std::setbase( 16 ) << 123 );
63+
LOG4CXX_INFO_FMT_ASYNC( rootLogger, LOG4CXX_STR("Numbers can be formatted with a format string {:.1f} and as hex: {:x}"), 22.456, 123 );
64+
// Uncomment the next line to verify that compile time type checking works
65+
//LOG4CXX_INFO_FMT_ASYNC( rootLogger, LOG4CXX_STR("Numbers can be formatted with a format string {:.1f} and as hex: {:x}"), "wrong type", 123 );
66+
67+
LogManager::shutdown();
68+
return 0;
69+
}

src/main/cpp/asyncbuffer.cpp

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
#include <log4cxx/helpers/asyncbuffer.h>
19+
#include <log4cxx/helpers/transcoder.h>
1920

2021
namespace LOG4CXX_NS
2122
{
@@ -26,8 +27,59 @@ namespace helpers
2627
struct AsyncBuffer::Private
2728
{
2829
std::vector<MessageBufferAppender> data;
30+
31+
Private(const MessageBufferAppender& f)
32+
: data{ f }
33+
{}
34+
35+
#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
36+
StringViewType fmt_string;
37+
FmtArgStore fmt_args;
38+
39+
Private(StringViewType&& format_string, FmtArgStore&& args)
40+
: fmt_string{ std::move(format_string) }
41+
, fmt_args{ std::move(args) }
42+
{}
43+
44+
#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
45+
WideStringViewType fmt_wstring;
46+
WideFmtArgStore fmt_wargs;
47+
48+
Private(WideStringViewType&& format_string, WideFmtArgStore&& args)
49+
: fmt_wstring{ std::move(format_string) }
50+
, fmt_wargs{ std::move(args) }
51+
{}
52+
#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
53+
#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
54+
2955
};
3056

57+
#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
58+
void AsyncBuffer::initializeForFmt(StringViewType&& format_string, FmtArgStore&& args)
59+
{
60+
if (!m_priv)
61+
m_priv = std::make_unique<Private>(std::move(format_string), std::move(args));
62+
else
63+
{
64+
m_priv->fmt_string = std::move(format_string);
65+
m_priv->fmt_args = std::move(args);
66+
}
67+
}
68+
69+
#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
70+
void AsyncBuffer::initializeForFmt(WideStringViewType&& format_string, WideFmtArgStore&& args)
71+
{
72+
if (!m_priv)
73+
m_priv = std::make_unique<Private>(std::move(format_string), std::move(args));
74+
else
75+
{
76+
m_priv->fmt_wstring = std::move(format_string);
77+
m_priv->fmt_wargs = std::move(args);
78+
}
79+
}
80+
#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
81+
#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
82+
3183
/** An empty buffer.
3284
*/
3385
AsyncBuffer::AsyncBuffer()
@@ -49,16 +101,57 @@ AsyncBuffer::~AsyncBuffer()
49101
/**
50102
* Has no item been added to this?
51103
*/
52-
bool AsyncBuffer::empty() const { return !m_priv || m_priv->data.empty(); }
104+
bool AsyncBuffer::empty() const
105+
{
106+
bool result{ true };
107+
if (m_priv)
108+
{
109+
result = m_priv->data.empty();
110+
#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
111+
if (result)
112+
result = (0 == m_priv->fmt_string.size());
113+
#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
114+
if (result)
115+
result = (0 == m_priv->fmt_wstring.size());
116+
#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
117+
#endif
118+
}
119+
return result;
120+
}
53121

54122
/**
55123
* Add text version of buffered values to \c msg
56124
*/
57-
void AsyncBuffer::renderMessage(LogCharMessageBuffer& msg)
125+
void AsyncBuffer::renderMessage(LogCharMessageBuffer& msg) const
58126
{
59127
if (m_priv)
128+
{
60129
for (auto& renderer : m_priv->data)
61130
renderer(msg);
131+
#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
132+
#if LOG4CXX_LOGCHAR_IS_UTF8
133+
if (0 < m_priv->fmt_string.size())
134+
msg << fmt::vformat(m_priv->fmt_string, m_priv->fmt_args);
135+
#if LOG4CXX_WCHAR_T_API
136+
if (0 < m_priv->fmt_wstring.size())
137+
{
138+
LOG4CXX_DECODE_WCHAR(lsMsg, fmt::vformat(m_priv->fmt_wstring, m_priv->fmt_wargs));
139+
msg << lsMsg;
140+
}
141+
#endif // LOG4CXX_WCHAR_T_API
142+
#endif // LOG4CXX_LOGCHAR_IS_UTF8
143+
144+
#if LOG4CXX_LOGCHAR_IS_WCHAR
145+
if (0 < m_priv->fmt_wstring.size())
146+
msg << fmt::vformat(m_priv->fmt_wstring, m_priv->fmt_wargs);
147+
if (0 < m_priv->fmt_string.size())
148+
{
149+
LOG4CXX_DECODE_CHAR(lsMsg, fmt::vformat(m_priv->fmt_string, m_priv->fmt_args));
150+
msg << lsMsg;
151+
}
152+
#endif // LOG4CXX_LOGCHAR_IS_WCHAR
153+
#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
154+
}
62155
}
63156

64157
/**
@@ -67,7 +160,15 @@ void AsyncBuffer::renderMessage(LogCharMessageBuffer& msg)
67160
void AsyncBuffer::clear()
68161
{
69162
if (m_priv)
163+
{
70164
m_priv->data.clear();
165+
#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
166+
m_priv->fmt_string = {};
167+
#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
168+
m_priv->fmt_wstring = {};
169+
#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
170+
#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
171+
}
71172
}
72173

73174
/**
@@ -76,8 +177,9 @@ void AsyncBuffer::clear()
76177
void AsyncBuffer::append(const MessageBufferAppender& f)
77178
{
78179
if (!m_priv)
79-
m_priv = std::make_unique<Private>();
80-
m_priv->data.push_back(f);
180+
m_priv = std::make_unique<Private>(f);
181+
else
182+
m_priv->data.push_back(f);
81183
}
82184

83185
} // namespace helpers

src/main/cpp/basicconfigurator.cpp

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,34 +18,63 @@
1818
#include <log4cxx/basicconfigurator.h>
1919
#include <log4cxx/patternlayout.h>
2020
#include <log4cxx/consoleappender.h>
21+
#include <log4cxx/asyncappender.h>
2122
#include <log4cxx/logmanager.h>
2223
#include <log4cxx/logger.h>
2324
#include <log4cxx/helpers/loglog.h>
2425

2526
using namespace LOG4CXX_NS;
2627

28+
namespace
29+
{
30+
31+
LayoutPtr getDefaultLayout()
32+
{
33+
auto pattern = LogString
34+
{ helpers::LogLog::isColorEnabled()
35+
? LOG4CXX_STR("%r [%t] %p %c %x - %Y%m%y%n")
36+
: LOG4CXX_STR("%r [%t] %p %c %x - %m%n")
37+
};
38+
return std::make_shared<PatternLayout>(pattern);
39+
}
40+
41+
} // namespace
42+
43+
2744
void BasicConfigurator::configure(const LayoutPtr& layoutArg)
2845
{
29-
LogManager::getLoggerRepository()->setConfigured(true);
30-
auto layout = layoutArg;
31-
if (!layout)
32-
{
33-
auto pattern = LogString
34-
{ helpers::LogLog::isColorEnabled()
35-
? LOG4CXX_STR("%r [%t] %p %c %x - %Y%m%y%n")
36-
: LOG4CXX_STR("%r [%t] %p %c %x - %m%n")
37-
};
38-
layout = std::make_shared<PatternLayout>(pattern);
39-
}
40-
auto appender = std::make_shared<ConsoleAppender>(layout);
41-
Logger::getRootLogger()->addAppender(appender);
46+
auto appender = std::make_shared<ConsoleAppender>(layoutArg ? layoutArg : getDefaultLayout());
47+
appender->setName(LOG4CXX_STR("BasicConfigurator"));
48+
auto r = LogManager::getLoggerRepository();
49+
r->getRootLogger()->addAppender(appender);
50+
r->setConfigured(true);
4251
}
4352

4453
void BasicConfigurator::configure(const AppenderPtr& appender)
4554
{
46-
LogManager::getLoggerRepository()->setConfigured(true);
47-
LoggerPtr root = Logger::getRootLogger();
48-
root->addAppender(appender);
55+
auto r = LogManager::getLoggerRepository();
56+
r->getRootLogger()->addAppender(appender);
57+
r->setConfigured(true);
58+
}
59+
60+
void BasicConfigurator::configureAsync(const LayoutPtr& layoutArg)
61+
{
62+
auto ringBuffer = std::make_shared<AsyncAppender>();
63+
ringBuffer->setName(LOG4CXX_STR("Default"));
64+
ringBuffer->addAppender(std::make_shared<ConsoleAppender>(layoutArg ? layoutArg : getDefaultLayout()));
65+
auto r = LogManager::getLoggerRepository();
66+
r->getRootLogger()->addAppender(ringBuffer);
67+
r->setConfigured(true);
68+
}
69+
70+
void BasicConfigurator::configureAsync(const AppenderPtr& appender)
71+
{
72+
auto ringBuffer = std::make_shared<AsyncAppender>();
73+
ringBuffer->addAppender(appender);
74+
ringBuffer->setName(LOG4CXX_STR("Default"));
75+
auto r = LogManager::getLoggerRepository();
76+
r->getRootLogger()->addAppender(ringBuffer);
77+
r->setConfigured(true);
4978
}
5079

5180
void BasicConfigurator::resetConfiguration()

src/main/include/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ foreach(varName
164164
HAS_PTHREAD_SIGMASK
165165
HAS_PTHREAD_SETNAME
166166
HAS_PTHREAD_GETNAME
167+
ENABLE_FMT_ASYNC
167168
)
168169
if(${varName} EQUAL 0)
169170
continue()

0 commit comments

Comments
 (0)