Skip to content

Commit fe520a3

Browse files
authored
Merge pull request #975 from openzim/log_debug_utils
Log debug utils
2 parents ba2600f + 3cf4e08 commit fe520a3

File tree

7 files changed

+786
-0
lines changed

7 files changed

+786
-0
lines changed

src/log.cpp

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright (C) 2025 Veloman Yunkan
3+
*
4+
* This program is free software; you can redistribute it and/or
5+
* modify it under the terms of the GNU General Public License as
6+
* published by the Free Software Foundation; either version 2 of the
7+
* License, or (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but
10+
* is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
11+
* warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
12+
* NON-INFRINGEMENT. See the GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program; if not, write to the Free Software
16+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17+
*
18+
*/
19+
20+
// Compile this translation unit as if logging has been enabled
21+
// so that if it is enabled in any other translation unit the linking
22+
// step doesn't end with an undefined symbol error.
23+
#define LIBZIM_ENABLE_LOGGING
24+
25+
#include "log.h"
26+
27+
#include <condition_variable>
28+
#include <iomanip>
29+
#include <sstream>
30+
31+
#include "namedthread.h"
32+
#include "tools.h"
33+
34+
namespace zim
35+
{
36+
37+
namespace
38+
{
39+
40+
std::ostream* logStreamPtr_;
41+
std::ostringstream inMemLog_;
42+
std::mutex mutex_;
43+
44+
// When non-empty, the last element represents the name of the thread
45+
// that should be allowed to proceed for the next logging statement.
46+
std::vector<std::string> orchestrationStack_;
47+
48+
std::condition_variable cv_;
49+
50+
bool threadMayProceed(const std::string& threadName)
51+
{
52+
return orchestrationStack_.empty()
53+
|| threadName == orchestrationStack_.back();
54+
}
55+
56+
std::map<std::string, size_t> nestingLevelMap_;
57+
std::mutex nestingLevelMapMutex_;
58+
59+
size_t getNestingLevel(const std::string& threadName)
60+
{
61+
std::lock_guard<std::mutex> lock(nestingLevelMapMutex_);
62+
return nestingLevelMap_[threadName];
63+
}
64+
65+
} // unnamed namespace
66+
67+
namespace LoggingImpl
68+
{
69+
70+
DebugLog::DebugLog()
71+
: os_(logStreamPtr_)
72+
, lock_(mutex_)
73+
{
74+
}
75+
76+
bool DebugLog::isEnabled()
77+
{
78+
return logStreamPtr_ != nullptr;
79+
}
80+
81+
std::ostream& DebugLog::newLogRequest()
82+
{
83+
const auto threadName = NamedThread::getCurrentThreadName();
84+
if ( !threadMayProceed(threadName) ) {
85+
cv_.wait(lock_, [threadName]() { return threadMayProceed(threadName); });
86+
}
87+
88+
const auto nestingLevel = getNestingLevel(threadName);
89+
*os_ << threadName << ": " << std::setw(nestingLevel) << "";
90+
91+
if ( !orchestrationStack_.empty() ) {
92+
orchestrationStack_.pop_back();
93+
cv_.notify_all();
94+
}
95+
96+
return *os_;
97+
}
98+
99+
} // namespace LoggingImpl
100+
101+
void Logging::orchestrateConcurrentExecutionVia(const std::string& logOutput)
102+
{
103+
orchestrationStack_.clear();
104+
for ( auto line : zim::split(logOutput, "\n") ) {
105+
const std::string threadName = zim::split(line, ":")[0];
106+
orchestrationStack_.push_back(threadName);
107+
}
108+
std::reverse(orchestrationStack_.begin(), orchestrationStack_.end());
109+
}
110+
111+
namespace LoggingImpl
112+
{
113+
114+
void logValue(std::ostream& out, const char* x)
115+
{
116+
if ( x ) {
117+
out << '"' << x << '"';
118+
} else {
119+
out << "nullptr";
120+
}
121+
}
122+
123+
void logValue(std::ostream& out, const std::string& x)
124+
{
125+
out << '"' << x << '"';
126+
}
127+
128+
void logValue(std::ostream& out, bool x)
129+
{
130+
out << (x ? "true" : "false");
131+
}
132+
133+
RAIISyncLogger::~RAIISyncLogger()
134+
{
135+
if (LoggingImpl::DebugLog::isEnabled()) {
136+
DebugLog().newLogRequest() << "exiting synchronized section" << std::endl;
137+
}
138+
}
139+
140+
FunctionCallLogger::~FunctionCallLogger()
141+
{
142+
changeNestingLevel(-1);
143+
if (LoggingImpl::DebugLog::isEnabled()) {
144+
LoggingImpl::DebugLog debugLog;
145+
std::ostream& os = debugLog.newLogRequest();
146+
os << "}";
147+
if ( !returnValue_.empty() ) {
148+
os << " (return value: " << returnValue_ << ")";
149+
}
150+
os << std::endl;
151+
}
152+
}
153+
154+
void FunctionCallLogger::changeNestingLevel(int delta)
155+
{
156+
std::lock_guard<std::mutex> lock(nestingLevelMapMutex_);
157+
nestingLevelMap_[NamedThread::getCurrentThreadName()] += delta;
158+
}
159+
160+
} // namespace LoggingImpl
161+
162+
void Logging::logIntoMemory()
163+
{
164+
inMemLog_.str("");
165+
logStreamPtr_ = &inMemLog_;
166+
}
167+
168+
std::string Logging::getInMemLogContent()
169+
{
170+
return inMemLog_.str();
171+
}
172+
173+
} // namespace zim

src/log.h

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,178 @@
1919

2020
#include "config.h"
2121

22+
// Should we keep the dependence on cxxtools logging framework?
2223
#ifdef WITH_CXXTOOLS
2324

2425
#include <cxxtools/log.h>
2526

27+
#elif defined(LIBZIM_ENABLE_LOGGING)
28+
29+
#include <iostream>
30+
#include <mutex>
31+
#include <string>
32+
#include <sstream>
33+
#include <tuple>
34+
35+
namespace zim
36+
{
37+
38+
class LIBZIM_PRIVATE_API Logging
39+
{
40+
public:
41+
static void logIntoMemory();
42+
static std::string getInMemLogContent();
43+
static void orchestrateConcurrentExecutionVia(const std::string& logOutput);
44+
};
45+
46+
namespace LoggingImpl
47+
{
48+
class LIBZIM_PRIVATE_API DebugLog
49+
{
50+
std::ostream* const os_;
51+
std::unique_lock<std::mutex> lock_;
52+
53+
public:
54+
static bool isEnabled();
55+
56+
DebugLog();
57+
58+
DebugLog(const DebugLog& ) = delete;
59+
void operator=(const DebugLog& ) = delete;
60+
61+
std::ostream& newLogRequest();
62+
};
63+
64+
template<class T>
65+
void LIBZIM_PRIVATE_API logValue(std::ostream& out, const T& x)
66+
{
67+
out << x;
68+
}
69+
70+
void LIBZIM_PRIVATE_API logValue(std::ostream& out, const char* x);
71+
void LIBZIM_PRIVATE_API logValue(std::ostream& out, const std::string& x);
72+
void LIBZIM_PRIVATE_API logValue(std::ostream& out, bool x);
73+
74+
class LIBZIM_PRIVATE_API FunctionCallLogger
75+
{
76+
std::string returnValue_;
77+
78+
public:
79+
~FunctionCallLogger();
80+
81+
void changeNestingLevel(int delta);
82+
83+
template<class T>
84+
const T& setReturnValue(const T& v)
85+
{
86+
std::ostringstream ss;
87+
logValue(ss, v);
88+
returnValue_ = ss.str();
89+
return v;
90+
}
91+
};
92+
93+
class LIBZIM_PRIVATE_API RAIISyncLogger
94+
{
95+
public:
96+
~RAIISyncLogger();
97+
};
98+
99+
template<size_t COUNT>
100+
struct TupleDumper
101+
{
102+
template<class TupleType>
103+
static void dump(std::ostream& out, const TupleType& t)
104+
{
105+
TupleDumper<COUNT-1>::dump(out, t);
106+
out << ", ";
107+
logValue(out, std::get<COUNT-1>(t));
108+
}
109+
};
110+
111+
template<>
112+
struct TupleDumper<1>
113+
{
114+
template<class TupleType>
115+
static void dump(std::ostream& out, const TupleType& t)
116+
{
117+
logValue(out, std::get<0>(t));
118+
}
119+
};
120+
121+
template<>
122+
struct TupleDumper<0>
123+
{
124+
template<class TupleType>
125+
static void dump(std::ostream& out, const TupleType& t)
126+
{
127+
}
128+
};
129+
130+
template<class... Types>
131+
class FuncArgs
132+
{
133+
public:
134+
typedef std::tuple<Types...> ImplType;
135+
enum { ArgCount = std::tuple_size<ImplType>::value };
136+
137+
public:
138+
FuncArgs(const Types&... t) : impl_(t...) {}
139+
140+
void dump(std::ostream& out) const
141+
{
142+
out << "(";
143+
TupleDumper<ArgCount>::dump(out, impl_);
144+
out << ")";
145+
}
146+
147+
private:
148+
ImplType impl_;
149+
};
150+
151+
template<class... Types>
152+
static FuncArgs<Types...> funcArgs(Types... t)
153+
{
154+
return FuncArgs<Types...>(t...);
155+
};
156+
157+
template<class... Types>
158+
std::ostream& operator<<(std::ostream& out, const FuncArgs<Types...>& x)
159+
{
160+
x.dump(out);
161+
return out;
162+
}
163+
} // namespace LoggingImpl
164+
165+
} // namespace zim
166+
167+
#define log_debug(x) \
168+
if (zim::LoggingImpl::DebugLog::isEnabled()) { \
169+
zim::LoggingImpl::DebugLog().newLogRequest() << x << std::endl; \
170+
} else {}
171+
172+
#define log_debug_func_call(FUNCNAME, ...) \
173+
zim::LoggingImpl::FunctionCallLogger functionCallLogger; \
174+
log_debug(FUNCNAME << zim::LoggingImpl::funcArgs(__VA_ARGS__) << " {"); \
175+
functionCallLogger.changeNestingLevel(+1);
176+
177+
178+
#define log_debug_return_value(v) functionCallLogger.setReturnValue(v)
179+
180+
#define log_debug_raii_sync_statement(statement) \
181+
statement; \
182+
log_debug("entered synchronized section"); \
183+
zim::LoggingImpl::RAIISyncLogger raiiSyncLogger;
184+
185+
// Below logging macros are not yet implemented
186+
#define log_define(e)
187+
#define log_fatal(e)
188+
#define log_error(e)
189+
#define log_warn(e)
190+
#define log_info(e)
191+
#define log_trace(e)
192+
#define log_init()
193+
26194
#else
27195
28196
#define log_define(e)
@@ -31,6 +199,9 @@
31199
#define log_warn(e)
32200
#define log_info(e)
33201
#define log_debug(e)
202+
#define log_debug_func_call(FUNCNAME, ...)
203+
#define log_debug_return_value(v) v
204+
#define log_debug_raii_sync_statement(statement) statement
34205
#define log_trace(e)
35206
#define log_init()
36207

src/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ common_sources = [
2525
'tools.cpp',
2626
'compression.cpp',
2727
'istreamreader.cpp',
28+
'namedthread.cpp',
29+
'log.cpp',
2830
'writer/contentProvider.cpp',
2931
'writer/creator.cpp',
3032
'writer/item.cpp',

0 commit comments

Comments
 (0)