File: LoggingUtils.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 (207 lines) | stat: -rw-r--r-- 12,063 bytes parent folder | download
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
/*************************************************************************
*  2020      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
#include <lib/base/Logging.hpp>
#include <lib/base/Timer.hpp>

/*
 * LoggingUtils provide at the moment two additional families of macros:
 * 1. LOG_TIMED_*
 * 2. LOG_ONCE_*
 */

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

/*
 * 1. LOG_TIMED_*
 *
 * This header provides macros LOG_TIMED_* very similar to the regular LOG_* macros, with one extra functionality:
 * the log messages are printed not more often than the first macro argument 'howOften'.
 *
 * They are intended is to make it easier to debug parts of code which print a lot of messages extremely fast.
 * It adds an extra code block that checks time and compares with time of last print. It means a that bit more
 * calculations are done than typical LOG_* macros which only perform an integer comparison to check filter level.
 * Here an extra subtraction is done to check times.
 *
 * Suggested use is only during debugging, because this is not supposed to be fast. Then better remove them afterwards.
 *
 * The *_TRACE family of macros are removed by compiler during release builds. So those are very safe to use, but make sure to compile yade
 * with `cmake -DMAX_LOG_LEVEL=6` option (because default is 5), or you would never see LOG_TRACE and LOG_TIMED_TRACE print anything.
 *
 *
 * In general there are two solutions to make each message check its timing separately:
 *  1. Create a std::map< int uniqueIdentifier , std::chrono::time_point<…> lastCheck > inside class Logging (or class derived from Logging) and check there.
 *  2. Or use a thread_local static variable inside a macro
 *
 * They both would work exactly the same. But std::map<…,…> inside a class has following disadvantages:
 *  - adds an extra lookup in std::map
 *  - requires boost::log, because it requires the Logging class
 * While it would have one advantage over macro solution:
 *  + would not have to check time if the filter level integer comparison says to skip printing.
 *
 * So for now I will implement this with a macro. So this works without boost::log and no std::map<…> lookup is performed,
 * because the variable is sitting right here.
 *
 * This could be later turned into a class derived from Logging and have exactly the same interface.
 * We will see what works best.
 *
 *
 * The usage of this macro is following:
 *
 *   LOG_TIMED_TRACE( howOften , MESSAGE ) // or LOG_TIMED_DEBUG(… , …) or other macros from this family.
 *
 *   → The howOften argument is of std::chrono::duration type https://en.cppreference.com/w/cpp/chrono/duration
 *     See file py/_log.cpp function testTimedLevels(); for example usage.
 *   → The MESSAGE argument is the same message as in regular LOG_* macros.
 *
 * To make this macro "type safe", like regular function calls I put a static_assert inside. It checks that the duration is compatible
 * with std::chrono::high_resolution_clock::now(); Without static_assert it would end up with weird compilation errors.
 *
 * So it will accept arguments such as:
 *
 *   using namespace std::chrono_literals;  // use this namespace to enable 's' and 'ms' postfix literals. Nice example in https://en.cppreference.com/w/cpp/thread/sleep_for
 *                                          // I can't use this namespace inside this header file, because this is a .hpp file, and that would produce namespace pollution problems.
 *
 *   LOG_TIMED_INFO( 2s  , "Print every 2 seconds")
 *   LOG_TIMED_INFO( 20ms, "Print every 20 milliseconds")
 *
 * These 2s or 20ms is a compile-time rational constant from namespace std::chrono_literals, so the compiler optimizes it into a single integer comparison.
 * Only seconds and milliseconds are accepted as arguments, to produce a more readable compiler error message (in case of problems).
 *
 */

namespace units { // C++ standard uses int64_t because ↓ it supports ±292.5 years in nanoseconds.
	using Seconds      = ::std::chrono::duration<int64_t, std::ratio<int64_t(1), int64_t(1)>>;
	using MilliSeconds = ::std::chrono::duration<int64_t, std::ratio<int64_t(1), int64_t(1000)>>;

	template <typename TimeUnit> const constexpr bool isSecond        = std::is_same<TimeUnit, Seconds>::value;
	template <typename TimeUnit> const constexpr bool isMilliSecond   = std::is_same<TimeUnit, MilliSeconds>::value;
	template <typename TimeUnit> const constexpr bool isSecOrMilliSec = isSecond<TimeUnit> or isMilliSecond<TimeUnit>;
}

// quick test, with invocation:  yade -f6
//   import log ; yade.log.testTimedLevels()

// use std::chrono_literals; in this block to properly expand 10s or 500ms from https://en.cppreference.com/w/cpp/header/chrono#Literals, it does not leak out.

#define LOG_TIMED(howOften, MSG)                                                                                                                               \
	{                                                                                                                                                      \
		using namespace std::chrono_literals;                                                                                                          \
		static_assert(units::isSecOrMilliSec<decltype(howOften)>, "LOG_TIMED_* Bad first argument, see testTimedLevels(); in file py/_log.cpp.");      \
		thread_local static auto t = Timer(true);                                                                                                      \
		if (t.check(howOften)) { MSG }                                                                                                                 \
	}

#define LOG_TIMED_NOFILTER(howOften, msg) LOG_TIMED(howOften, LOG_NOFILTER(msg))
#define LOG_TIMED_FATAL(howOften, msg) LOG_TIMED(howOften, LOG_FATAL(msg))
#define LOG_TIMED_ERROR(howOften, msg) LOG_TIMED(howOften, LOG_ERROR(msg))
#define LOG_TIMED_WARN(howOften, msg) LOG_TIMED(howOften, LOG_WARN(msg))
#define LOG_TIMED_INFO(howOften, msg) LOG_TIMED(howOften, LOG_INFO(msg))
#define LOG_TIMED_DEBUG(howOften, msg) LOG_TIMED(howOften, LOG_DEBUG(msg))
#define LOG_TIMED_TRACE(howOften, msg) LOG_TIMED(howOften, LOG_TRACE(msg))

// Logger aliases:
#define LOG_TIMED_6_TRACE(t, msg) LOG_TIMED_TRACE(t, msg)
#define LOG_TIMED_5_DEBUG(t, msg) LOG_TIMED_DEBUG(t, msg)
#define LOG_TIMED_4_INFO(t, msg) LOG_TIMED_INFO(t, msg)
#define LOG_TIMED_3_WARN(t, msg) LOG_TIMED_WARN(t, msg)
#define LOG_TIMED_2_ERROR(t, msg) LOG_TIMED_ERROR(t, msg)
#define LOG_TIMED_1_FATAL(t, msg) LOG_TIMED_FATAL(t, msg)
#define LOG_TIMED_0_NOFILTER(t, msg) LOG_TIMED_NOFILTER(t, msg)

#define LOG_TRACE_TIMED(t, msg) LOG_TIMED_TRACE(t, msg)
#define LOG_DEBUG_TIMED(t, msg) LOG_TIMED_DEBUG(t, msg)
#define LOG_INFO_TIMED(t, msg) LOG_TIMED_INFO(t, msg)
#define LOG_WARN_TIMED(t, msg) LOG_TIMED_WARN(t, msg)
#define LOG_ERROR_TIMED(t, msg) LOG_TIMED_ERROR(t, msg)
#define LOG_FATAL_TIMED(t, msg) LOG_TIMED_FATAL(t, msg)
#define LOG_NOFILTER_TIMED(t, msg) LOG_TIMED_NOFILTER(t, msg)

#define LOG_TIMED_6(t, msg) LOG_TIMED_TRACE(t, msg)
#define LOG_TIMED_5(t, msg) LOG_TIMED_DEBUG(t, msg)
#define LOG_TIMED_4(t, msg) LOG_TIMED_INFO(t, msg)
#define LOG_TIMED_3(t, msg) LOG_TIMED_WARN(t, msg)
#define LOG_TIMED_2(t, msg) LOG_TIMED_ERROR(t, msg)
#define LOG_TIMED_1(t, msg) LOG_TIMED_FATAL(t, msg)
#define LOG_TIMED_0(t, msg) LOG_TIMED_NOFILTER(t, msg)

// macros for quick debugging without spamming terminal:
#define TIMED_TRVAR1(T, a) LOG_TIMED_TRACE(T, _TRV(a))
#define TIMED_TRVAR2(T, a, b) LOG_TIMED_TRACE(T, _TRV(a) << _TRV(b))
#define TIMED_TRVAR3(T, a, b, c) LOG_TIMED_TRACE(T, _TRV(a) << _TRV(b) << _TRV(c))
#define TIMED_TRVAR4(T, a, b, c, d) LOG_TIMED_TRACE(T, _TRV(a) << _TRV(b) << _TRV(c) << _TRV(d))
#define TIMED_TRVAR5(T, a, b, c, d, e) LOG_TIMED_TRACE(T, _TRV(a) << _TRV(b) << _TRV(c) << _TRV(d) << _TRV(e))
#define TIMED_TRVAR6(T, a, b, c, d, e, f) LOG_TIMED_TRACE(T, _TRV(a) << _TRV(b) << _TRV(c) << _TRV(d) << _TRV(e) << _TRV(f))

#define TIMED_TRVARn_PRINT_ONE(r, WAIT, VARn) TIMED_TRVAR1(WAIT, 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 TIMED_TRVARn(WAIT, ALL_VARS) BOOST_PP_SEQ_FOR_EACH(TIMED_TRVARn_PRINT_ONE, WAIT, ALL_VARS)

/*
 * 2. LOG_ONCE_*
 *
 * Very similar in construction to LOG_TIMED_*, just a bit simpler.
 */

#define LOG_ONCE(MSG)                                                                                                                                          \
	{                                                                                                                                                      \
		static bool printed = false;                                                                                                                   \
		if (not printed) {                                                                                                                             \
			printed = true;                                                                                                                        \
			MSG                                                                                                                                    \
		}                                                                                                                                              \
	}

#define LOG_ONCE_NOFILTER(msg) LOG_ONCE(LOG_NOFILTER(msg))
#define LOG_ONCE_FATAL(msg) LOG_ONCE(LOG_FATAL(msg))
#define LOG_ONCE_ERROR(msg) LOG_ONCE(LOG_ERROR(msg))
#define LOG_ONCE_WARN(msg) LOG_ONCE(LOG_WARN(msg))
#define LOG_ONCE_INFO(msg) LOG_ONCE(LOG_INFO(msg))
#define LOG_ONCE_DEBUG(msg) LOG_ONCE(LOG_DEBUG(msg))
#define LOG_ONCE_TRACE(msg) LOG_ONCE(LOG_TRACE(msg))

// Logger aliases:
#define LOG_ONCE_6_TRACE(msg) LOG_ONCE_TRACE(msg)
#define LOG_ONCE_5_DEBUG(msg) LOG_ONCE_DEBUG(msg)
#define LOG_ONCE_4_INFO(msg) LOG_ONCE_INFO(msg)
#define LOG_ONCE_3_WARN(msg) LOG_ONCE_WARN(msg)
#define LOG_ONCE_2_ERROR(msg) LOG_ONCE_ERROR(msg)
#define LOG_ONCE_1_FATAL(msg) LOG_ONCE_FATAL(msg)
#define LOG_ONCE_0_NOFILTER(msg) LOG_ONCE_NOFILTER(msg)

#define LOG_TRACE_ONCE(msg) LOG_ONCE_TRACE(msg)
#define LOG_DEBUG_ONCE(msg) LOG_ONCE_DEBUG(msg)
#define LOG_INFO_ONCE(msg) LOG_ONCE_INFO(msg)
#define LOG_WARN_ONCE(msg) LOG_ONCE_WARN(msg)
#define LOG_ERROR_ONCE(msg) LOG_ONCE_ERROR(msg)
#define LOG_FATAL_ONCE(msg) LOG_ONCE_FATAL(msg)
#define LOG_NOFILTER_ONCE(msg) LOG_ONCE_NOFILTER(msg)

#define LOG_ONCE_6(msg) LOG_ONCE_TRACE(msg)
#define LOG_ONCE_5(msg) LOG_ONCE_DEBUG(msg)
#define LOG_ONCE_4(msg) LOG_ONCE_INFO(msg)
#define LOG_ONCE_3(msg) LOG_ONCE_WARN(msg)
#define LOG_ONCE_2(msg) LOG_ONCE_ERROR(msg)
#define LOG_ONCE_1(msg) LOG_ONCE_FATAL(msg)
#define LOG_ONCE_0(msg) LOG_ONCE_NOFILTER(msg)

// macros for quick debugging without spamming terminal:
#define ONCE_TRVAR1(a) LOG_ONCE_TRACE(_TRV(a))
#define ONCE_TRVAR2(a, b) LOG_ONCE_TRACE(_TRV(a) << _TRV(b))
#define ONCE_TRVAR3(a, b, c) LOG_ONCE_TRACE(_TRV(a) << _TRV(b) << _TRV(c))
#define ONCE_TRVAR4(a, b, c, d) LOG_ONCE_TRACE(_TRV(a) << _TRV(b) << _TRV(c) << _TRV(d))
#define ONCE_TRVAR5(a, b, c, d, e) LOG_ONCE_TRACE(_TRV(a) << _TRV(b) << _TRV(c) << _TRV(d) << _TRV(e))
#define ONCE_TRVAR6(a, b, c, d, e, f) LOG_ONCE_TRACE(_TRV(a) << _TRV(b) << _TRV(c) << _TRV(d) << _TRV(e) << _TRV(f))

#define ONCE_TRVARn_PRINT_ONE(r, WAIT, VARn) ONCE_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 ONCE_TRVARn(ALL_VARS) BOOST_PP_SEQ_FOR_EACH(ONCE_TRVARn_PRINT_ONE, ~, ALL_VARS)

} // namespace yade