File: Logging.hpp

package info (click to toggle)
yade 2023.02a-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 31,732 kB
  • sloc: cpp: 87,798; python: 48,852; sh: 512; makefile: 162
file content (285 lines) | stat: -rw-r--r-- 20,493 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
/*************************************************************************
*  2006-2008 Václav Šmilauer                                             *
*  2019      Janek Kozicki                                               *
*                                                                        *
*  This program is free software; it is licensed under the terms of the  *
*  GNU General Public License v2 or later. See file LICENSE for details. *
*************************************************************************/

#pragma once
/*
 * This file defines various useful logging-related macros. For full description see https://yade-dem.org/doc/prog.html#debugging
 * Quick summary:
 * - LOG_* for actual logging,
 * - DECLARE_LOGGER; that should be used in class header to create separate logger for that class,
 * - CREATE_LOGGER(ClassName); that must be used in class implementation file to create the static variable.
 * - CREATE_CPP_LOCAL_LOGGER("filename.cpp"); use this inside a *.cpp file which has code that does not belong to any class, and needs logging. The name will be used for filtering logging.
 * - TRVARn(…) for printing variables
 * - TRACE; for quick tracing
 *
 * Yade has the logging config file by default in ~/.yade-$VERSION/logging.conf.
 *
 * Note: The Logging.[hc]pp files and Singleton.hpp are competely independent. They can be used in unrelated projects under license GNU GPL v2 or later,
 * you only need to add  -DBOOST_LOG_DYN_LINK -lboost_log -lboost_log_setup to your compilation options.
 * Don't use -DBOOST_LOG_DYN_LINK if you use -static linking, which normally you shouldn't unless there is a really good reason.
 *
 */

// boost::log inspired by git show 014b11496
#ifdef YADE_BOOST_LOG
// workaruond bug https://github.com/boostorg/multiprecision/issues/207, later add here ' and (BOOST_VERSION < 107?00)' to not include this after it is fixed.
#include <boost/version.hpp>
#if defined(YADE_MPFR) and (BOOST_VERSION >= 106800)
#include <lib/base/Math.hpp>
#endif

#include <lib/base/Singleton.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup.hpp>
#include <boost/preprocessor.hpp>
#include <map>
#include <string>
#include <vector>

// TODO: move this when fixing https://gitlab.com/yade-dev/trunk/issues/97
constexpr unsigned long hash(const char* str, int ha = 0) { return !str[ha] ? 5381 : (hash(str, ha + 1) * 33) ^ str[ha]; }

#define _LOG_HEAD                                                                                                                                              \
	":" << Logging::instance().colorLineNumber() << __LINE__ << Logging::instance().colorFunction() << " " << __PRETTY_FUNCTION__                          \
	    << Logging::instance().colorEnd() << ": "
// If you get "error: ‘logger’ was not declared in this scope" then you have to declare logger.
// Use DECLARE_LOGGER; inside class and CREATE_LOGGER(ClassName); inside .cpp file
// or use CREATE_CPP_LOCAL_LOGGER("filename.cpp") if you need logging outside some class.
#define LOG_TRACE(msg)                                                                                                                                         \
	{                                                                                                                                                      \
		BOOST_LOG_SEV(logger, Logging::SeverityLevel::eTRACE) << _LOG_HEAD << msg;                                                                     \
	}
#define LOG_DEBUG(msg)                                                                                                                                         \
	{                                                                                                                                                      \
		BOOST_LOG_SEV(logger, Logging::SeverityLevel::eDEBUG) << _LOG_HEAD << msg;                                                                     \
	}
#define LOG_INFO(msg)                                                                                                                                          \
	{                                                                                                                                                      \
		BOOST_LOG_SEV(logger, Logging::SeverityLevel::eINFO) << _LOG_HEAD << msg;                                                                      \
	}
#define LOG_WARN(msg)                                                                                                                                          \
	{                                                                                                                                                      \
		BOOST_LOG_SEV(logger, Logging::SeverityLevel::eWARN) << _LOG_HEAD << msg;                                                                      \
	}
#define LOG_ERROR(msg)                                                                                                                                         \
	{                                                                                                                                                      \
		BOOST_LOG_SEV(logger, Logging::SeverityLevel::eERROR) << _LOG_HEAD << msg;                                                                     \
	}
#define LOG_FATAL(msg)                                                                                                                                         \
	{                                                                                                                                                      \
		BOOST_LOG_SEV(logger, Logging::SeverityLevel::eFATAL) << _LOG_HEAD << msg;                                                                     \
	}
#define LOG_NOFILTER(msg)                                                                                                                                      \
	{                                                                                                                                                      \
		boost::log::sources::severity_logger<Logging::SeverityLevel> slg;                                                                              \
		BOOST_LOG_SEV(slg, Logging::eNOFILTER) << _LOG_HEAD << msg;                                                                                    \
	}

class Logging : public Singleton<Logging> {
public:
	enum SeverityLevel { eNOFILTER = 0, eFATAL = 1, eERROR = 2, eWARN = 3, eINFO = 4, eDEBUG = 5, eTRACE = 6 };
	Logging();
	void                                                         readConfigFile(const std::string&);
	void                                                         saveConfigFile(const std::string&);
	std::string                                                  defaultConfigFileName();
	void                                                         setOutputStream(const std::string&, bool reset);
	short int                                                    getDefaultLogLevel() const { return defaultLogLevel; };
	short int                                                    getNamedLogLevel(const std::string&) const;
	void                                                         setNamedLogLevel(const std::string&, short int);
	void                                                         unsetNamedLogLevel(const std::string&);
	boost::log::sources::severity_logger<Logging::SeverityLevel> createNamedLogger(std::string name);
	const std::map<std::string, short int>&                      getClassLogLevels() const { return classLogLevels; };
	static constexpr short int                                   maxLogLevel { MAX_LOG_LEVEL };

	void        setUseColors(bool);
	std::string colorSeverity(Logging::SeverityLevel);
	std::string colorNameTag();
	std::string colorLineNumber();
	std::string colorFunction();
	std::string colorEnd();

private:
	void                                                                                 setDefaultLogLevel(short int);
	typedef boost::log::sinks::synchronous_sink<boost::log::sinks::text_ostream_backend> TextSink;
	const short int&                                                                     findFilterName(const std::string&) const;
	short int&                                                                           findFilterName(const std::string&);
	void                                                                                 updateFormatter();
	short int                                                                            defaultLogLevel {
                (short int)(SeverityLevel::eWARN)
	}; // see constructor in Logging.cpp, at later stages of initialization it is overwritten by core/main/main.py.in line 163
	std::map<std::string, short int>             classLogLevels { { "Default", defaultLogLevel } };
	boost::shared_ptr<TextSink>                  sink { boost::make_shared<TextSink>() };
	boost::shared_ptr<std::ostream>              streamClog {}, streamCerr {}, streamCout {}, streamFile {};
	std::vector<boost::shared_ptr<std::ostream>> streamOld {};
	bool colors { false }; // can't be set here to avoid race condition, in later initialization it is overwritten by core/main/main.py.in line 163
	const std::string                                            esc { char { 27 } };
	std::string                                                  lastOutputStream {};
	boost::log::sources::severity_logger<Logging::SeverityLevel> logger {};
	FRIEND_SINGLETON(Logging);
};
BOOST_LOG_ATTRIBUTE_KEYWORD(severity, "Severity", Logging::SeverityLevel)
BOOST_LOG_ATTRIBUTE_KEYWORD(class_name_tag, "NameTag", std::string)
inline std::ostream& operator<<(std::ostream& strm, Logging::SeverityLevel level) // necessary for formatting output.
{
	static std::vector<std::string> names = { "NOFILTER", "FATAL ERROR", "ERROR", "WARNING", "INFO", "DEBUG", "TRACE" };
	if ((int(level) < int(names.size())) and (int(level) >= 0))
		strm << Logging::instance().colorSeverity(level) << names[level] << Logging::instance().colorEnd();
	else
		strm << int(level);
	return strm;
}
// logger is local for every class, but if it is missing, we will use the parent's class logger automagically.
#define DECLARE_LOGGER                                                                                                                                         \
public:                                                                                                                                                        \
	static boost::log::sources::severity_logger<Logging::SeverityLevel> logger
#define CREATE_LOGGER(classname)                                                                                                                               \
	boost::log::sources::severity_logger<Logging::SeverityLevel> classname::logger = Logging::instance().createNamedLogger(#classname)
#define TEMPLATE_CREATE_LOGGER(ClassName) template <> CREATE_LOGGER(ClassName) /* Use this when creating a logger for a templated ClassName<SomeOtherClass> */
#define CREATE_CPP_LOCAL_LOGGER(filtername)                                                                                                                    \
	namespace {                                                                                                                                            \
		boost::log::sources::severity_logger<Logging::SeverityLevel> logger = Logging::instance().createNamedLogger(filtername);                       \
	}
#else // #ifdef YADE_BOOST_LOG, without boost::log use the simplified logging method:
#include <iostream>
#define _POOR_MANS_LOG(level, msg)                                                                                                                             \
	{                                                                                                                                                      \
		std::cerr << level " " << _LOG_HEAD << msg << std::endl;                                                                                       \
	}
#define _LOG_HEAD __FILE__ ":" << __LINE__ << " " << __PRETTY_FUNCTION__ << ": "

#ifdef YADE_DEBUG
// when compiling with debug symbols and without boost::log it will print everything.
#define MAX_HARDCODED_LOG_LEVEL 6
#define LOG_TRACE(msg) _POOR_MANS_LOG("TRACE_6", msg)
#define LOG_DEBUG(msg) _POOR_MANS_LOG("DEBUG_5", msg)
#define LOG_INFO(msg) _POOR_MANS_LOG("INFO__4", msg)
#else
#define MAX_HARDCODED_LOG_LEVEL 3
#define LOG_TRACE(msg)                                                                                                                                         \
	{                                                                                                                                                      \
		_IGNORE_LOG(msg);                                                                                                                              \
	}
#define LOG_DEBUG(msg)                                                                                                                                         \
	{                                                                                                                                                      \
		_IGNORE_LOG(msg);                                                                                                                              \
	}
#define LOG_INFO(msg)                                                                                                                                          \
	{                                                                                                                                                      \
		_IGNORE_LOG(msg);                                                                                                                              \
	}
#endif

#define LOG_WARN(msg) _POOR_MANS_LOG("WARN__3", msg)
#define LOG_ERROR(msg) _POOR_MANS_LOG("ERROR_2", msg)
#define LOG_FATAL(msg) _POOR_MANS_LOG("FATAL_1", msg)
#define LOG_NOFILTER(msg) _POOR_MANS_LOG("NOFILTER", msg)

#define DECLARE_LOGGER
#define CREATE_LOGGER(classname)
#define TEMPLATE_CREATE_LOGGER(classname)
#define CREATE_CPP_LOCAL_LOGGER(name)
#endif // END #ifdef YADE_BOOST_LOG

// macros for quick debugging
#define TRACE LOG_TRACE("Been here")
#define _TRV(x) #x "=" << x << "; "
#define TRVAR1(a) LOG_TRACE(_TRV(a))
#define TRVAR2(a, b) LOG_TRACE(_TRV(a) << _TRV(b))
#define TRVAR3(a, b, c) LOG_TRACE(_TRV(a) << _TRV(b) << _TRV(c))
#define TRVAR4(a, b, c, d) LOG_TRACE(_TRV(a) << _TRV(b) << _TRV(c) << _TRV(d))
#define TRVAR5(a, b, c, d, e) LOG_TRACE(_TRV(a) << _TRV(b) << _TRV(c) << _TRV(d) << _TRV(e))
#define TRVAR6(a, b, c, d, e, f) LOG_TRACE(_TRV(a) << _TRV(b) << _TRV(c) << _TRV(d) << _TRV(e) << _TRV(f))

#define TRVARn_PRINT_ONE(r, SKIP, VARn) TRVAR1(VARn)
// this one prints arbitrary number of variables, but they must be a boost preprocessor sequence like (var1)(var2)(var3), see py/_log.cpp for example usage.
#define TRVARn(ALL_VARS) BOOST_PP_SEQ_FOR_EACH(TRVARn_PRINT_ONE, ~, ALL_VARS)

// Logger aliases:
#define LOG_6_TRACE(msg) LOG_TRACE(msg)
#define LOG_5_DEBUG(msg) LOG_DEBUG(msg)
#define LOG_4_INFO(msg) LOG_INFO(msg)
#define LOG_3_WARN(msg) LOG_WARN(msg)
#define LOG_2_ERROR(msg) LOG_ERROR(msg)
#define LOG_1_FATAL(msg) LOG_FATAL(msg)
#define LOG_0_NOFILTER(msg) LOG_NOFILTER(msg)

#define LOG_6(msg) LOG_TRACE(msg)
#define LOG_5(msg) LOG_DEBUG(msg)
#define LOG_4(msg) LOG_INFO(msg)
#define LOG_3(msg) LOG_WARN(msg)
#define LOG_2(msg) LOG_ERROR(msg)
#define LOG_1(msg) LOG_FATAL(msg)
#define LOG_0(msg) LOG_NOFILTER(msg)

// honor MAX_LOG_LEVEL cmake option to disable selected macros
#if MAX_LOG_LEVEL < 6
#undef LOG_TRACE
#define LOG_TRACE(msg)                                                                                                                                         \
	{                                                                                                                                                      \
		_IGNORE_LOG(msg);                                                                                                                              \
	}
#endif

#if MAX_LOG_LEVEL < 5
#undef LOG_DEBUG
#define LOG_DEBUG(msg)                                                                                                                                         \
	{                                                                                                                                                      \
		_IGNORE_LOG(msg);                                                                                                                              \
	}
#endif

#if MAX_LOG_LEVEL < 4
#undef LOG_INFO
#define LOG_INFO(msg)                                                                                                                                          \
	{                                                                                                                                                      \
		_IGNORE_LOG(msg);                                                                                                                              \
	}
#endif

#if MAX_LOG_LEVEL < 3
#undef LOG_WARN
#define LOG_WARN(msg)                                                                                                                                          \
	{                                                                                                                                                      \
		_IGNORE_LOG(msg);                                                                                                                              \
	}
#endif

#if MAX_LOG_LEVEL < 2
#warning "MAX_LOG_LEVEL<2 means that all LOG_ERROR messages are ignored, be careful with this option."
#undef LOG_ERROR
#define LOG_ERROR(msg)                                                                                                                                         \
	{                                                                                                                                                      \
		_IGNORE_LOG(msg);                                                                                                                              \
	}
#endif

#if MAX_LOG_LEVEL < 1
#warning "MAX_LOG_LEVEL<1 means that all LOG_ERROR, LOG_FATAL messages are ignored, be careful with this option."
#undef LOG_FATAL
#define LOG_FATAL(msg)                                                                                                                                         \
	{                                                                                                                                                      \
		_IGNORE_LOG(msg);                                                                                                                              \
	}
#endif

#if MAX_LOG_LEVEL < 0
#warning "MAX_LOG_LEVEL<0 means that all LOG_ERROR, LOG_FATAL and LOG_NOFILTER messages are are ignored, be careful with this option."
#undef LOG_NOFILTER
#define LOG_NOFILTER(msg)                                                                                                                                      \
	{                                                                                                                                                      \
		_IGNORE_LOG(msg);                                                                                                                              \
	}
#endif

// The msg must be used to avoid -Werror about unused variable. But the code is eliminated anyway.
// https://stackoverflow.com/questions/10809429/what-kind-of-dead-code-can-gcc-eliminate-from-the-final-output , added -fdce flag, to be sure.
#define _IGNORE_LOG(msg)                                                                                                                                       \
	{                                                                                                                                                      \
		if (false) { std::cerr << msg; }                                                                                                               \
	}