File: _log.cpp

package info (click to toggle)
yade 2025.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 33,308 kB
  • sloc: cpp: 93,298; python: 50,409; sh: 577; makefile: 162
file content (373 lines) | stat: -rw-r--r-- 17,172 bytes parent folder | download | duplicates (3)
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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
/*************************************************************************
*  2006-2008 © Václav Šmilauer                                           *
*  2019      © Janek Kozicki, rewritten using boost::log the version     *
*              which was removed in 2008 (git revision 014b11496)        *
*                                                                        *
*  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. *
*************************************************************************/

// hint: follow changes in d067b0696a8 to add new modules.

#include <lib/base/AliasNamespaces.hpp>
#include <lib/base/LoggingUtils.hpp>
#include <lib/pyutil/doc_opts.hpp>
#include <core/Omega.hpp>
#include <boost/core/demangle.hpp>
#include <chrono>
#include <string>
#include <thread>

CREATE_CPP_LOCAL_LOGGER("_log.cpp");

namespace yade { // Cannot have #include directive inside.

#ifdef YADE_BOOST_LOG

int getDefaultLogLevel() { return Logging::instance().getDefaultLogLevel(); }

void setDefaultLogLevel(int level) { Logging::instance().setNamedLogLevel("Default", level); }

// accepted streams: "clog", "cerr", "cout", "filename"
// It is possible to set different levels per log file, see notes about that in Logging::setOutputStream(…)
void setOutputStream(std::string streamName, bool reset)
{
	Logging::instance().setOutputStream(streamName, reset);
	if (reset) {
		LOG_INFO("Log output stream has been set to " << streamName << ". Other output streams were removed.");
	} else {
		LOG_INFO("Additional output stream has been set to " << streamName << ".");
	}
}

void resetOutputStream()
{
	Logging::instance().setOutputStream("clog", true);
	LOG_INFO("Log output stream has been reset to std::clog. File sinks are not removed.");
}

void setLevel(std::string className, int level)
{
	Logging::instance().setNamedLogLevel(className, level);
	LOG_INFO("filter log level for " << className << " has been set to " << Logging::instance().getNamedLogLevel(className));
}

void unsetLevel(std::string className)
{
	Logging::instance().unsetNamedLogLevel(className);
	LOG_INFO("filter log level for " << className << " has been unset to " << Logging::instance().getNamedLogLevel(className));
}

py::dict getAllLevels()
{
	py::dict ret {};
	for (const auto& a : Logging::instance().getClassLogLevels()) {
		ret[a.first] = a.second;
	}
	return ret;
}

py::dict getUsedLevels()
{
	py::dict ret {};
	for (const auto& a : Logging::instance().getClassLogLevels()) {
		if (a.second != -1) { ret[a.first] = a.second; }
	}
	return ret;
}

void setUseColors(bool use) { Logging::instance().setUseColors(use); }

void readConfigFile(std::string fname) { Logging::instance().readConfigFile(fname); }

void saveConfigFile(std::string fname) { Logging::instance().saveConfigFile(fname); }

std::string defaultConfigFileName() { return Logging::instance().defaultConfigFileName(); }

int getMaxLevel() { return Logging::instance().maxLogLevel; }

#else // #ifdef YADE_BOOST_LOG, without boost::log use the simplified logging method:

void printNoBoostLogWarning()
{
	std::cerr
	        << "\nWarning: yade was compiled with cmake option -DENABLE_LOGGER=OFF, any attempts to manipulate log filter levels will not have effect.\n\n";
}

int      getDefaultLogLevel() { return std::min(MAX_LOG_LEVEL, MAX_HARDCODED_LOG_LEVEL); }
void     setDefaultLogLevel(int) { printNoBoostLogWarning(); }
void     setOutputStream(std::string, bool) { printNoBoostLogWarning(); }
void     resetOutputStream() { printNoBoostLogWarning(); }
void     setLevel(std::string, int) { printNoBoostLogWarning(); }
void     unsetLevel(std::string) { printNoBoostLogWarning(); }
py::dict getAllLevels()
{
	printNoBoostLogWarning();
	return {};
}
py::dict getUsedLevels()
{
	printNoBoostLogWarning();
	return {};
}
void        setUseColors(bool) { printNoBoostLogWarning(); }
void        readConfigFile(std::string) { printNoBoostLogWarning(); }
void        saveConfigFile(std::string) { printNoBoostLogWarning(); }
std::string defaultConfigFileName()
{
	printNoBoostLogWarning();
	return "";
}
int getMaxLevel() { return std::min(MAX_LOG_LEVEL, MAX_HARDCODED_LOG_LEVEL); }

#endif // END #ifdef YADE_BOOST_LOG

void testAllLevels()
{
	int         testInt = 0;
	std::string testStr = "test string";
	Real        testReal(11);
	Vector3r    testVec(1, 2, 3);
	Matrix3r    testMat = (Matrix3r() << 1, 2, 3, 4, 5, 6, 7, 8, 9).finished();
	Complex     testComplex(-1, 1);

	LOG_0_NOFILTER("Test log level: LOG_0_NOFILTER, test int: " << testInt++);
	LOG_1_FATAL("Test log level: LOG_1_FATAL, test int: " << testInt++);
	LOG_2_ERROR("Test log level: LOG_2_ERROR, test int: " << testInt++);
	LOG_3_WARN("Test log level: LOG_3_WARN, test int: " << testInt++);
	LOG_4_INFO("Test log level: LOG_4_INFO, test int: " << testInt++);
	LOG_5_DEBUG("Test log level: LOG_5_DEBUG, test int: " << testInt++);
	LOG_6_TRACE("Test log level: LOG_6_TRACE, test int: " << testInt++);

	LOG_0_NOFILTER("Below 6 variables are printed at filter level TRACE, then macro TRACE; is used");
	TRVAR1(testInt);
	TRVAR2(testInt, testStr);
	TRVAR3(testInt, testStr, testReal);
	TRVAR4(testInt, testStr, testReal, testVec);
	TRVAR5(testInt, testStr, testReal, testVec, testMat);
	TRVAR6(testInt, testStr, testReal, testVec, testMat, testComplex);
	LOG_TRACE("\n\nTest print of arbitrary number of variables (one argument):");
	TRVARn((testInt));
	LOG_TRACE("\n\nTest print of arbitrary number of variables (8 arguments):");
	TRVARn((testInt)(testStr)(testReal)(testVec)(testMat)(testComplex)(7)(8));

	TRACE;

	LOG_TRACE("\n\nTest print of TIMED_TRVARn macro family (every 2 seconds, one time):\n\n");
	for (int i = 0; i < 11; ++i) {
		TIMED_TRVAR1(1s, testInt);
		TIMED_TRVAR2(1s, testInt, testStr);
		TIMED_TRVAR3(1s, testInt, testStr, testReal);
		TIMED_TRVAR4(1s, testInt, testStr, testReal, testVec);
		TIMED_TRVAR5(1s, testInt, testStr, testReal, testVec, testMat);
		TIMED_TRVAR6(1s, testInt, testStr, testReal, testVec, testMat, testComplex);

		LOG_TIMED_TRACE(1s, "\n\nTest print of arbitrary number of variables (one argument):");

		TIMED_TRVARn(1s, (testInt));

		LOG_TIMED_TRACE(1s, "\n\nTest print of arbitrary number of variables (8 arguments):");

		TIMED_TRVARn(1s, (testInt)(testStr)(testReal)(testVec)(testMat)(testComplex)(7)(8));

		using namespace std::chrono_literals;
		std::this_thread::sleep_for(100ms);
	}
}

void testOnceLevels()
{
	int         testInt = 0;
	std::string testStr = "test string";
	Real        testReal(11);
	Vector3r    testVec(1, 2, 3);
	Matrix3r    testMat = (Matrix3r() << 1, 2, 3, 4, 5, 6, 7, 8, 9).finished();
	Complex     testComplex(-1, 1);

	LOG_ONCE_TRACE("\n\nTest print of ONCE_TRVARn macro family:\n\n");
	for (int i = 0; i < 10; ++i) {
		ONCE_TRVAR1(testInt);
		ONCE_TRVAR2(testInt, testStr);
		ONCE_TRVAR3(testInt, testStr, testReal);
		ONCE_TRVAR4(testInt, testStr, testReal, testVec);
		ONCE_TRVAR5(testInt, testStr, testReal, testVec, testMat);
		ONCE_TRVAR6(testInt, testStr, testReal, testVec, testMat, testComplex);

		LOG_ONCE_TRACE("\n\nTest print of arbitrary number of variables (one argument):");

		ONCE_TRVARn((testInt));

		LOG_ONCE_TRACE("\n\nTest print of arbitrary number of variables (8 arguments):");

		ONCE_TRVARn((testInt)(testStr)(testReal)(testVec)(testMat)(testComplex)(7)(8));

		LOG_ONCE_0_NOFILTER("Test log level: LOG_ONCE_0_NOFILTER, test int: " << testInt++);
		LOG_ONCE_1_FATAL("Test log level: LOG_ONCE_1_FATAL, test int: " << testInt++);
		LOG_ONCE_2_ERROR("Test log level: LOG_ONCE_2_ERROR, test int: " << testInt++);
		LOG_ONCE_3_WARN("Test log level: LOG_ONCE_3_WARN, test int: " << testInt++);
		LOG_ONCE_4_INFO("Test log level: LOG_ONCE_4_INFO, test int: " << testInt++);
		LOG_ONCE_5_DEBUG("Test log level: LOG_ONCE_5_DEBUG, test int: " << testInt++ << "\n");
		LOG_ONCE_6_TRACE("Test log level: LOG_ONCE_6_TRACE, test int: " << testInt++ << "\n");
	}
}

void testTimedLevels()
{
	unsigned long long int testInt = 0;
	using namespace std::chrono_literals; // use the following function signature:
	//
	//        constexpr chrono::duration<…,…> operator "" s(unsigned long long);
	//
	// to be able to write 1s to mean 1 second. See:
	// * https://en.cppreference.com/w/cpp/chrono/operator%22%22h
	// * https://en.cppreference.com/w/cpp/thread/sleep_for

	// with `using namespace std::chrono_literals` you can write this:
	constexpr auto wait_half = 500ms;
	constexpr auto wait_1s   = 1s;
	constexpr auto wait_2s   = 2s;
	// without `using namespace std::chrono_literals` you have to write this:
	constexpr std::chrono::duration<int64_t, std::ratio<int64_t(1), int64_t(1000)>> wait_half_without_using(
	        500); // int64_t type supports ±292.5 years in nanoseconds.
	// the constexpr above was used only for the purpose to demonstrate that these are the same:
	static_assert(wait_half == wait_half_without_using, "Half a second should be 500ms.");
	static_assert(std::is_same<decltype(wait_half), decltype(wait_half_without_using)>::value, "And the type is decltype(500ms)");

	TRVAR3(wait_half.count(), wait_1s.count(), wait_2s.count())
	using boost::core::demangle; // to print exact type of std::chrono::duration

	// This auto type declared above for 500ms, 1s, 2s is actually std::chrono::duration<…,…> like this:
	LOG_DEBUG(
	        "\n The types of LOG_TIMED_* first argument are any of std::chrono::duration<…> like for example:\n "
	        << "type of wait_half is " << demangle(typeid(decltype(wait_half)).name()) << "; wait_half.count() == " << wait_half.count() << "\n "
	        << "type of wait_1s   is " << demangle(typeid(decltype(wait_1s)).name()) << "   ; wait_1s.count()   == " << wait_1s.count() << "\n "
	        << "type of wait_2s   is " << demangle(typeid(decltype(wait_2s)).name()) << "   ; wait_2s.count()   == " << wait_2s.count()
	        << "\n----------------\n")

	LOG_NOFILTER("Starting the 2.1 second test. The '2s' logs should be printed once, the '0.5s' should be printed four times.\n\n")
	for (int i = 0; i < 21; ++i) {
		// write 2s  here     ↓ instead of using variable wait_2s, because that's how it is comfortable to be used. The variable wait_2s above was only a demonstration of how the type works.
		LOG_TIMED_0_NOFILTER(2s, "Test 2s timed log level: LOG_0_NOFILTER, test int: " << testInt++);
		LOG_TIMED_1_FATAL(2s, "Test 2s timed log level: LOG_1_FATAL, test int: " << testInt++);
		LOG_TIMED_2_ERROR(2s, "Test 2s timed log level: LOG_2_ERROR, test int: " << testInt++);
		LOG_TIMED_3_WARN(1s, "Test 1s timed log level: LOG_3_WARN, test int: " << testInt++);
		LOG_TIMED_4_INFO(1s, "Test 1s timed log level: LOG_4_INFO, test int: " << testInt++);
		LOG_TIMED_5_DEBUG(500ms, "Test 0.5s timed log level: LOG_5_DEBUG, test int: " << testInt++ << "\n");
		LOG_TIMED_6_TRACE(500ms, "Test 0.5s timed log level: LOG_6_TRACE, test int: " << testInt++ << "\n");
		std::this_thread::sleep_for(100ms);
	}

	LOG_WARN("Upon next call of this function the '2s' will be printed first also, because the timers are 'thread_local static', which means that they "
	         "preserve timers between different calls to this function, but have separate static instances for each different thread.")
}

} // namespace yade

// BOOST_PYTHON_MODULE cannot be inside yade namespace, it has 'extern "C"' keyword, which strips it out of any namespaces.
BOOST_PYTHON_MODULE(_log)
try {
	using namespace yade; // 'using namespace' inside function keeps namespace pollution under control. Alernatively I could add y:: in front of function names below and put 'namespace y  = ::yade;' here.
	namespace py = ::boost::python;
	YADE_SET_DOCSTRING_OPTS;
	// We can use C++ string literal just like """ """ in python to write docstrings (see. https://en.cppreference.com/w/cpp/language/string_literal )
	// The """ is a custom delimeter, we could use    R"RAW( instead, or any other delimeter. This decides what will be the termination delimeter.
	// The docstrings can use syntax :param ……: ……… :return: ……. For details see https://thomas-cokelaer.info/tutorials/sphinx/docstring_python.html

	py::def("testAllLevels", testAllLevels, R"""(
This function prints test messages on all log levels. Can be used to see how filtering works and to what streams the logs are written.
	)""");

	py::def("testOnceLevels", testOnceLevels, R"""(
This function prints test messages on all log levels using LOG_ONCE_* macro family.
	)""");

	py::def("testTimedLevels", testTimedLevels, R"""(
This function prints timed test messages on all log levels. In this test the log levels ∈ [0…2] are timed to print every 2 seconds, levels ∈ [3,4] every 1 second and levels ∈ [5,6] every 0.5 seconds. The loop lasts for 2.1 seconds. Can be used to see how timed filtering works and to what streams the logs are written.
	)""");

	// default level
	py::def("getDefaultLogLevel", getDefaultLogLevel, R"""(
:return: The current ``Default`` filter log level.
	)""");

	py::def("setDefaultLogLevel", setDefaultLogLevel, R"""(
:param int level: Sets the ``Default`` filter log level, same as calling ``log.setLevel("Default",level)``.
	)""");

	// output streams, files, cout, cerr, clog.
	py::def("setOutputStream", setOutputStream, R"""(
:param str streamName: sets the output stream, special names ``cout``, ``cerr``, ``clog`` use the ``std::cout``, ``std::cerr``, ``std::clog`` counterpart (``std::clog`` the is the default output stream). Every other name means that log will be written to a file with name provided in the argument.
:param bool reset: dictates whether all previously set output streams are to be removed. When set to false: the new output stream is set additionally to the current one.
	)""");

	py::def("resetOutputStream", resetOutputStream, R"""(
Resets log output stream to default state: all logs are printed on ``std::clog`` channel, which usually redirects to ``std::cerr``.
	)""");

	// filter levels
	py::def("setLevel", setLevel, R"""(
Set filter level (constants ``TRACE`` (6), ``DEBUG`` (5), ``INFO`` (4), ``WARN`` (3), ``ERROR`` (2), ``FATAL`` (1), ``NOFILTER`` (0)) for given logger.

:param str className: The logger name for which the filter level is to be set. Use name ``Default`` to change the default filter level.
:param int level: The filter level to be set.
.. warning:: setting ``Default`` log level higher than ``MAX_LOG_LEVEL`` provided during compilation will have no effect. Logs will not be printed because they are removed during compilation.
	)""");

	py::def("unsetLevel", unsetLevel, R"""(
:param str className: The logger name for which the filter level is to be unset, so that a ``Default`` will be used instead. Unsetting the ``Default`` level will change it to max level and print everything.
	)""");

	py::def("getAllLevels", getAllLevels, R"""(
:return: A python dictionary with all known loggers in yade. Those without a debug level set will have value -1 to indicate that ``Default`` filter log level is to be used for them.
	)""");

	py::def("getUsedLevels", getUsedLevels, R"""(
:return: A python dictionary with all used log levels in yade. Those without a debug level (value -1) are omitted.
	)""");

	py::def("getMaxLevel", getMaxLevel, R"""(
:return: the MAX_LOG_LEVEL of the current yade build.
	)""");

	// colors
	py::def("setUseColors", setUseColors, R"""(
Turn on/off colors in log messages. By default is on. If logging to a file then it is better to be turned off.
	)""");

	// config file
	py::def("readConfigFile", readConfigFile, R"""(
Loads the given configuration file.

:param str fname: the config file to be loaded.
	)""");
	py::def("saveConfigFile", saveConfigFile, R"""(
Saves log config to specified file.

:param str fname: the config file to be saved.
	)""");
	py::def("defaultConfigFileName", defaultConfigFileName, R"""(
:return: the default log config file, which is loaded at startup, if it exists.
	)""");

	py::scope().attr("TRACE")    = int(6);
	py::scope().attr("DEBUG")    = int(5);
	py::scope().attr("INFO")     = int(4);
	py::scope().attr("WARN")     = int(3);
	py::scope().attr("ERROR")    = int(2);
	py::scope().attr("FATAL")    = int(1);
	py::scope().attr("NOFILTER") = int(0);

} catch (...) {
	// How to work with python exceptions:
	//     https://www.boost.org/doc/libs/1_71_0/libs/python/doc/html/reference/high_level_components/boost_python_errors_hpp.html#high_level_components.boost_python_errors_hpp.example
	//     https://www.boost.org/doc/libs/1_71_0/libs/python/doc/html/tutorial/tutorial/embedding.html
	//     https://stackoverflow.com/questions/1418015/how-to-get-python-exception-text
	// If we wanted custom yade exceptions thrown to python:
	//     https://www.boost.org/doc/libs/1_71_0/libs/python/doc/html/tutorial/tutorial/exception.html
	//     https://www.boost.org/doc/libs/1_71_0/libs/python/doc/html/reference/high_level_components/boost_python_exception_translato.html
	LOG_FATAL("Importing this module caused an exception and this module is in an inconsistent state now.");
	PyErr_Print();
	PyErr_SetString(PyExc_SystemError, __FILE__);
	boost::python::handle_exception();
	throw;
}