Skip to content

Commit 0fb9278

Browse files
Make LogLevel a smart enum (#163)
Makes LogLevel into a smart enum. It can be used as an enum still for most cases, but makes the log API simplier, and also allows setting log levels from strings instead of just enum constants which makes it easier to configure
1 parent 8496dca commit 0fb9278

7 files changed

Lines changed: 422 additions & 155 deletions

File tree

src/LogLevel.hpp

Lines changed: 148 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -39,93 +39,178 @@ namespace NUClear {
3939
* Log levels are used to provide different levels of detail on a per-reactor basis.
4040
* The logging level of a reactor can be changed by setting it in the install function.
4141
*/
42-
enum LogLevel : uint8_t {
43-
/**
44-
* Don't use this log level when emitting logs, it is for setting reactor log level from non reactor sources.
45-
*
46-
* Specifically when a NUClear::log is called from code that is not running in a reaction (even transitively) then
47-
* the reactor_level will be set to UNKNOWN.
48-
*/
49-
UNKNOWN,
42+
class LogLevel {
43+
public:
44+
enum Value : uint8_t {
45+
/**
46+
* Don't use this log level when emitting logs, it is for setting reactor log level from non reactor sources.
47+
*
48+
* Specifically when a NUClear::log is called from code that is not running in a reaction (even transitively)
49+
* then the reactor_level will be set to UNKNOWN.
50+
*/
51+
UNKNOWN,
52+
53+
/**
54+
* The Trace level contains messages that are used to trace the exact flow of execution.
55+
*
56+
* This level is extremely verbose and often has a message per line of code.
57+
*/
58+
TRACE,
59+
60+
/**
61+
* Debug contains messages that represent the inputs and outputs of different computation units.
62+
*
63+
* If you have a function that performs three steps to do something then it's likely that you will have a
64+
* message for the input and output of those three steps. Additionally you would likely have messages that check
65+
* if it hit different branches.
66+
*/
67+
DEBUG,
68+
69+
/**
70+
* The info level is used to provide high level goal messages such as function start or successful completion.
71+
*
72+
* This shows when key user-facing functionality is executed and tells us that everything is working without
73+
* getting into the details.
74+
*/
75+
INFO,
76+
77+
/**
78+
* The warning level is used to notify us that everything might not be working perfectly.
79+
*
80+
* Warnings are errors or inconsistencies that aren't fatal and generally do not completely break the system.
81+
* However a warning message should require action and should point to a section of the system that needs
82+
* attention.
83+
*/
84+
WARN,
85+
86+
/**
87+
* The error level is used to report unexpected behavior.
88+
89+
* This level doesn't need to prefix a program-crashing issue but should be used to report major unexpected
90+
branches
91+
* in logic or other constraint breaking problems such as failed assertions.
92+
* All errors should require action from someone and should be addressed immediately.
93+
*/
94+
ERROR,
95+
96+
/**
97+
* Fatal is a program destroying error that needs to be addressed immediately.
98+
*
99+
* If a fatal message is sent it should point to something that should never ever happen and ideally provide as
100+
* much information as possible as to why it crashed. Fatal messages require action immediately and should
101+
* always be addressed.
102+
*/
103+
FATAL
104+
};
50105

51106
/**
52-
* The Trace level contains messages that are used to trace the exact flow of execution.
107+
* Construct a LogLevel from a Value
53108
*
54-
* This level is extremely verbose and often has a message per line of code.
109+
* @param value The value to construct the LogLevel from
55110
*/
56-
TRACE,
111+
constexpr LogLevel(const Value& value = Value::UNKNOWN) : value(value) {};
57112

58113
/**
59-
* Debug contains messages that represent the inputs and outputs of different computation units.
114+
* Construct a LogLevel from a string
60115
*
61-
* If you have a function that performs three steps to do something then it's likely that you will have a message
62-
* for the input and output of those three steps.
63-
* Additionally you would likely have messages that check if it hit different branches.
116+
* @param level The string to construct the LogLevel from
64117
*/
65-
DEBUG,
118+
LogLevel(const std::string& level)
119+
: value(level == "TRACE" ? LogLevel::TRACE
120+
: level == "DEBUG" ? LogLevel::DEBUG
121+
: level == "INFO" ? LogLevel::INFO
122+
: level == "WARN" ? LogLevel::WARN
123+
: level == "ERROR" ? LogLevel::ERROR
124+
: level == "FATAL" ? LogLevel::FATAL
125+
: LogLevel::UNKNOWN) {};
66126

67127
/**
68-
* The info level is used to provide high level goal messages such as function start or successful completion.
128+
* A call operator which will return the value of the LogLevel
129+
* This can be useful in situations where the implicit conversion operators are ambiguous.
69130
*
70-
* This shows when key user-facing functionality is executed and tells us that everything is working without getting
71-
* into the details.
131+
* @return The value of the LogLevel
72132
*/
73-
INFO,
133+
constexpr Value operator()() const {
134+
return value;
135+
}
74136

75137
/**
76-
* The warning level is used to notify us that everything might not be working perfectly.
138+
* A conversion operator which will return the value of the LogLevel
77139
*
78-
* Warnings are errors or inconsistencies that aren't fatal and generally do not completely break the system.
79-
* However a warning message should require action and should point to a section of the system that needs attention.
140+
* @return The value of the LogLevel
80141
*/
81-
WARN,
142+
constexpr operator Value() const {
143+
return value;
144+
}
82145

83146
/**
84-
* The error level is used to report unexpected behavior.
85-
86-
* This level doesn't need to prefix a program-crashing issue but should be used to report major unexpected branches
87-
* in logic or other constraint breaking problems such as failed assertions.
88-
* All errors should require action from someone and should be addressed immediately.
147+
* A conversion operator which will return the string representation of the LogLevel
148+
*
149+
* @return The string representation of the LogLevel
89150
*/
90-
ERROR,
151+
operator std::string() const {
152+
return value == LogLevel::TRACE ? "TRACE"
153+
: value == LogLevel::DEBUG ? "DEBUG"
154+
: value == LogLevel::INFO ? "INFO"
155+
: value == LogLevel::WARN ? "WARN"
156+
: value == LogLevel::ERROR ? "ERROR"
157+
: value == LogLevel::FATAL ? "FATAL"
158+
: "UNKNOWN";
159+
}
91160

92161
/**
93-
* Fatal is a program destroying error that needs to be addressed immediately.
162+
* Stream the LogLevel to an ostream, it will output the string representation of the LogLevel
163+
*
164+
* @param os The ostream to output to
165+
* @param level The LogLevel to output
94166
*
95-
* If a fatal message is sent it should point to something that should never ever happen and ideally provide as much
96-
* information as possible as to why it crashed.
97-
* Fatal messages require action immediately and should always be addressed.
167+
* @return The ostream that was passed in
98168
*/
99-
FATAL
169+
friend std::ostream& operator<<(std::ostream& os, LogLevel level) {
170+
return os << static_cast<std::string>(level);
171+
}
172+
173+
// Operators to compare LogLevel values and LogLevel to Value
174+
// clang-format off
175+
friend constexpr bool operator<(const LogLevel& lhs, const LogLevel& rhs) { return lhs.value < rhs.value; }
176+
friend constexpr bool operator>(const LogLevel& lhs, const LogLevel& rhs) { return lhs.value > rhs.value; }
177+
friend constexpr bool operator<=(const LogLevel& lhs, const LogLevel& rhs) { return lhs.value <= rhs.value; }
178+
friend constexpr bool operator>=(const LogLevel& lhs, const LogLevel& rhs) { return lhs.value >= rhs.value; }
179+
friend constexpr bool operator==(const LogLevel& lhs, const LogLevel& rhs) { return lhs.value == rhs.value; }
180+
friend constexpr bool operator!=(const LogLevel& lhs, const LogLevel& rhs) { return lhs.value != rhs.value; }
181+
182+
friend constexpr bool operator<(const LogLevel& lhs, const Value& rhs) { return lhs.value < rhs; }
183+
friend constexpr bool operator>(const LogLevel& lhs, const Value& rhs) { return lhs.value > rhs; }
184+
friend constexpr bool operator<=(const LogLevel& lhs, const Value& rhs) { return lhs.value <= rhs; }
185+
friend constexpr bool operator>=(const LogLevel& lhs, const Value& rhs) { return lhs.value >= rhs; }
186+
friend constexpr bool operator==(const LogLevel& lhs, const Value& rhs) { return lhs.value == rhs; }
187+
friend constexpr bool operator!=(const LogLevel& lhs, const Value& rhs) { return lhs.value != rhs; }
188+
friend constexpr bool operator<(const Value& lhs, const LogLevel& rhs) { return lhs < rhs.value; }
189+
friend constexpr bool operator>(const Value& lhs, const LogLevel& rhs) { return lhs > rhs.value; }
190+
friend constexpr bool operator<=(const Value& lhs, const LogLevel& rhs) { return lhs <= rhs.value; }
191+
friend constexpr bool operator>=(const Value& lhs, const LogLevel& rhs) { return lhs >= rhs.value; }
192+
friend constexpr bool operator==(const Value& lhs, const LogLevel& rhs) { return lhs == rhs.value; }
193+
friend constexpr bool operator!=(const Value& lhs, const LogLevel& rhs) { return lhs != rhs.value; }
194+
195+
friend bool operator<(const LogLevel& lhs, const std::string& rhs) { return static_cast<std::string>(lhs) < rhs; }
196+
friend bool operator>(const LogLevel& lhs, const std::string& rhs) { return static_cast<std::string>(lhs) > rhs; }
197+
friend bool operator<=(const LogLevel& lhs, const std::string& rhs) { return static_cast<std::string>(lhs) <= rhs; }
198+
friend bool operator>=(const LogLevel& lhs, const std::string& rhs) { return static_cast<std::string>(lhs) >= rhs; }
199+
friend bool operator==(const LogLevel& lhs, const std::string& rhs) { return static_cast<std::string>(lhs) == rhs; }
200+
friend bool operator!=(const LogLevel& lhs, const std::string& rhs) { return static_cast<std::string>(lhs) != rhs; }
201+
friend bool operator<(const std::string& lhs, const LogLevel& rhs) { return lhs < static_cast<std::string>(rhs); }
202+
friend bool operator>(const std::string& lhs, const LogLevel& rhs) { return lhs > static_cast<std::string>(rhs); }
203+
friend bool operator<=(const std::string& lhs, const LogLevel& rhs) { return lhs <= static_cast<std::string>(rhs); }
204+
friend bool operator>=(const std::string& lhs, const LogLevel& rhs) { return lhs >= static_cast<std::string>(rhs); }
205+
friend bool operator==(const std::string& lhs, const LogLevel& rhs) { return lhs == static_cast<std::string>(rhs); }
206+
friend bool operator!=(const std::string& lhs, const LogLevel& rhs) { return lhs != static_cast<std::string>(rhs); }
207+
// clang-format on
208+
209+
210+
private:
211+
/// The stored enum value
212+
Value value;
100213
};
101-
102-
/**
103-
* This function is used to convert a LogLevel into a string
104-
*
105-
* @param level the LogLevel to convert
106-
*
107-
* @return the string representation of the LogLevel
108-
*/
109-
std::string to_string(const LogLevel& level);
110-
111-
/**
112-
* This function is used to convert a string into a LogLevel
113-
*
114-
* @param level the string to convert
115-
*
116-
* @return the LogLevel representation of the string
117-
*/
118-
LogLevel from_string(const std::string& level);
119-
120-
/**
121-
* This function is used to convert a LogLevel into a string for printing.
122-
*
123-
* @param os the output stream to write to
124-
* @param level the LogLevel to convert
125-
* @return the output stream
126-
*/
127-
std::ostream& operator<<(std::ostream& os, const LogLevel& level);
128-
129214
} // namespace NUClear
130215

131216
#endif // NUCLEAR_LOGLEVEL_HPP

src/PowerPlant.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ class PowerPlant {
194194
*
195195
* @param args The arguments we are logging
196196
*/
197-
template <enum LogLevel level, typename... Arguments>
197+
template <LogLevel::Value level, typename... Arguments>
198198
void log(Arguments&&... args) {
199199
logger.log(nullptr, level, std::forward<Arguments>(args)...);
200200
}
@@ -216,7 +216,7 @@ class PowerPlant {
216216
* @param reactor The reactor that is logging
217217
* @param args The arguments we are logging
218218
*/
219-
template <enum LogLevel level, typename... Arguments>
219+
template <LogLevel::Value level, typename... Arguments>
220220
void log(const Reactor* reactor, Arguments&&... args) {
221221
logger.log(reactor, level, std::forward<Arguments>(args)...);
222222
}
@@ -349,7 +349,7 @@ class PowerPlant {
349349
*
350350
* @param args The arguments to log.
351351
*/
352-
template <enum LogLevel level = NUClear::DEBUG, typename... Arguments>
352+
template <LogLevel::Value level = NUClear::LogLevel::DEBUG, typename... Arguments>
353353
void log(Arguments&&... args) {
354354
if (PowerPlant::powerplant != nullptr) {
355355
PowerPlant::powerplant->log<level>(std::forward<Arguments>(args)...);

src/LogLevel.cpp renamed to src/Reactor.cpp

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,37 +20,15 @@
2020
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2121
*/
2222

23-
#include "LogLevel.hpp"
24-
25-
#include <ostream>
23+
#include "Reactor.hpp"
2624

2725
namespace NUClear {
2826

29-
std::string to_string(const LogLevel& level) {
30-
switch (level) {
31-
case LogLevel::TRACE: return "TRACE";
32-
case LogLevel::DEBUG: return "DEBUG";
33-
case LogLevel::INFO: return "INFO";
34-
case LogLevel::WARN: return "WARN";
35-
case LogLevel::ERROR: return "ERROR";
36-
case LogLevel::FATAL: return "FATAL";
37-
default:
38-
case LogLevel::UNKNOWN: return "UNKNOWN";
39-
}
40-
}
41-
42-
LogLevel from_string(const std::string& level) {
43-
return level == "TRACE" ? LogLevel::TRACE
44-
: level == "DEBUG" ? LogLevel::DEBUG
45-
: level == "INFO" ? LogLevel::INFO
46-
: level == "WARN" ? LogLevel::WARN
47-
: level == "ERROR" ? LogLevel::ERROR
48-
: level == "FATAL" ? LogLevel::FATAL
49-
: LogLevel::UNKNOWN;
50-
}
51-
52-
std::ostream& operator<<(std::ostream& os, const LogLevel& level) {
53-
return os << to_string(level);
54-
}
27+
constexpr LogLevel Reactor::TRACE;
28+
constexpr LogLevel Reactor::DEBUG;
29+
constexpr LogLevel Reactor::INFO;
30+
constexpr LogLevel Reactor::WARN;
31+
constexpr LogLevel Reactor::ERROR;
32+
constexpr LogLevel Reactor::FATAL;
5533

5634
} // namespace NUClear

src/Reactor.hpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,19 @@ class Reactor {
320320
using WATCHDOG = dsl::word::emit::Watchdog<T>;
321321
};
322322

323+
/// @copydoc NUClear::LogLevel::Value::TRACE
324+
static constexpr LogLevel TRACE = LogLevel::TRACE;
325+
// @copydoc NUClear::LogLevel::Value::DEBUG
326+
static constexpr LogLevel DEBUG = LogLevel::DEBUG;
327+
// @copydoc NUClear::LogLevel::Value::INFO
328+
static constexpr LogLevel INFO = LogLevel::INFO;
329+
// @copydoc NUClear::LogLevel::Value::WARN
330+
static constexpr LogLevel WARN = LogLevel::WARN;
331+
// @copydoc NUClear::LogLevel::Value::ERROR
332+
static constexpr LogLevel ERROR = LogLevel::ERROR;
333+
// @copydoc NUClear::LogLevel::Value::FATAL
334+
static constexpr LogLevel FATAL = LogLevel::FATAL;
335+
323336
/// This provides functions to modify how an on statement runs after it has been created
324337
using ReactionHandle = threading::ReactionHandle;
325338

@@ -436,7 +449,7 @@ class Reactor {
436449
*
437450
* @param args The arguments we are logging
438451
*/
439-
template <enum LogLevel level = DEBUG, typename... Arguments>
452+
template <LogLevel::Value level = DEBUG, typename... Arguments>
440453
void log(Arguments&&... args) const {
441454
// Short circuit here before going to the more expensive log function
442455
if (level >= min_log_level || level >= log_level) {

src/util/Logger.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ namespace util {
6767
* Describes the log levels for a particular reactor.
6868
*/
6969
struct LogLevels {
70-
LogLevels(LogLevel display_log_level, LogLevel min_log_level)
70+
LogLevels(const LogLevel& display_log_level, const LogLevel& min_log_level)
7171
: display_log_level(display_log_level), min_log_level(min_log_level) {}
7272

7373
/// The log level that should be displayed

0 commit comments

Comments
 (0)