From 358bb520eb30984abee874fe87318d597d071f0b Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Fri, 9 Aug 2024 16:00:48 +1000 Subject: [PATCH] Fix up the log test and add extra functions to log level --- src/LogLevel.cpp | 55 +++++++++++++++++++++++++++++++++++++++++ src/LogLevel.hpp | 30 +++++++++++++++++++++- src/PowerPlant.cpp | 4 +-- src/PowerPlant.hpp | 22 ++++++++++++----- src/Reactor.hpp | 17 +++++++++++++ tests/tests/log/Log.cpp | 10 ++++---- 6 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 src/LogLevel.cpp diff --git a/src/LogLevel.cpp b/src/LogLevel.cpp new file mode 100644 index 00000000..ee4c276c --- /dev/null +++ b/src/LogLevel.cpp @@ -0,0 +1,55 @@ +/* + * MIT License + * + * Copyright (c) 2013 NUClear Contributors + * + * This file is part of the NUClear codebase. + * See https://github.com/Fastcode/NUClear for further info. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "LogLevel.hpp" + +#include + +namespace NUClear { + +std::string to_string(const LogLevel& level) { + switch (level) { + case LogLevel::TRACE: return "TRACE"; + case LogLevel::DEBUG: return "DEBUG"; + case LogLevel::INFO: return "INFO"; + case LogLevel::WARN: return "WARN"; + case LogLevel::ERROR: return "ERROR"; + case LogLevel::FATAL: return "FATAL"; + default: + case LogLevel::UNKNOWN: return "UNKNOWN"; + } +} + +LogLevel from_string(const std::string& level) { + return level == "TRACE" ? LogLevel::TRACE + : level == "DEBUG" ? LogLevel::DEBUG + : level == "INFO" ? LogLevel::INFO + : level == "WARN" ? LogLevel::WARN + : level == "ERROR" ? LogLevel::ERROR + : level == "FATAL" ? LogLevel::FATAL + : LogLevel::UNKNOWN; +} + +std::ostream& operator<<(std::ostream& os, const LogLevel& level) { + return os << to_string(level); +} + +} // namespace NUClear diff --git a/src/LogLevel.hpp b/src/LogLevel.hpp index 249308dc..09050b43 100644 --- a/src/LogLevel.hpp +++ b/src/LogLevel.hpp @@ -19,10 +19,11 @@ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - #ifndef NUCLEAR_LOGLEVEL_HPP #define NUCLEAR_LOGLEVEL_HPP +#include + // Why do we need to include platform.hpp here? // Because windows defines a bunch of things for legacy reasons, one of which is a #define for ERROR as blank // Of course this causes a problem when we define our own token below as error as the preprocessor removes it @@ -98,6 +99,33 @@ enum LogLevel : uint8_t { FATAL }; +/** + * This function is used to convert a LogLevel into a string + * + * @param level the LogLevel to convert + * + * @return the string representation of the LogLevel + */ +std::string to_string(const LogLevel& level); + +/** + * This function is used to convert a string into a LogLevel + * + * @param level the string to convert + * + * @return the LogLevel representation of the string + */ +LogLevel from_string(const std::string& level); + +/** + * This function is used to convert a LogLevel into a string for printing. + * + * @param os the output stream to write to + * @param level the LogLevel to convert + * @return the output stream + */ +std::ostream& operator<<(std::ostream& os, const LogLevel& level); + } // namespace NUClear #endif // NUCLEAR_LOGLEVEL_HPP diff --git a/src/PowerPlant.cpp b/src/PowerPlant.cpp index 4ee0a402..73a7038f 100644 --- a/src/PowerPlant.cpp +++ b/src/PowerPlant.cpp @@ -95,7 +95,7 @@ void PowerPlant::submit(std::unique_ptr&& task, const b } } -void PowerPlant::log(const LogLevel& level, const std::string& message) { +void PowerPlant::log(const LogLevel& level, std::string message) { // Get the current task const auto* current_task = threading::ReactionTask::get_current_task(); @@ -103,7 +103,7 @@ void PowerPlant::log(const LogLevel& level, const std::string& message) { emit(std::make_unique( level, current_task != nullptr ? current_task->parent->reactor.log_level : LogLevel::UNKNOWN, - message, + std::move(message), current_task != nullptr ? current_task->stats : nullptr)); } void PowerPlant::log(const LogLevel& level, std::stringstream& message) { diff --git a/src/PowerPlant.hpp b/src/PowerPlant.hpp index 34cb82b0..4148c664 100644 --- a/src/PowerPlant.hpp +++ b/src/PowerPlant.hpp @@ -199,21 +199,25 @@ class PowerPlant { */ template void log(Arguments&&... args) { + log(level, std::forward(args)...); + } + template + void log(const LogLevel& level, Arguments&&... args) { std::stringstream ss; log(level, ss, std::forward(args)...); } - template - void log(const LogLevel& level, std::stringstream& ss, Last&& last) { - ss << std::forward(last); - log(level, ss); - } template void log(const LogLevel& level, std::stringstream& ss, First&& first, Arguments&&... args) { ss << std::forward(first) << " "; log(level, ss, std::forward(args)...); } + template + void log(const LogLevel& level, std::stringstream& ss, Last&& last) { + ss << std::forward(last); + log(level, ss); + } void log(const LogLevel& level, std::stringstream& message); - void log(const LogLevel& level, const std::string& message); + void log(const LogLevel& level, std::string message); /** * Emits data to the system and routes it to the other systems that use it. @@ -348,6 +352,12 @@ void log(Arguments&&... args) { PowerPlant::powerplant->log(std::forward(args)...); } } +template +void log(const LogLevel& level, Arguments&&... args) { + if (PowerPlant::powerplant != nullptr) { + PowerPlant::powerplant->log(level, std::forward(args)...); + } +} } // namespace NUClear diff --git a/src/Reactor.hpp b/src/Reactor.hpp index 9dacf5a3..ac339fe9 100644 --- a/src/Reactor.hpp +++ b/src/Reactor.hpp @@ -418,6 +418,23 @@ class Reactor { // If the log is above or equal to our log level powerplant.log(std::forward(args)...); } + + /** + * Log a message through NUClear's system. + * + * Logs a message through the system so the various log handlers can access it. + * + * @tparam Arguments The types of the arguments we are logging + * + * @param level The level to log at + * @param args The arguments we are logging + */ + template + void log(const LogLevel& level, Arguments&&... args) const { + + // If the log is above or equal to our log level + powerplant.log(level, std::forward(args)...); + } }; } // namespace NUClear diff --git a/tests/tests/log/Log.cpp b/tests/tests/log/Log.cpp index f1f7619c..c0088118 100644 --- a/tests/tests/log/Log.cpp +++ b/tests/tests/log/Log.cpp @@ -180,7 +180,7 @@ TEST_CASE("Testing the Log<>() function", "[api][log]") { // Test logs from reactions directly for (const auto& log_level : levels) { if (display_level <= log_level) { - const std::string expected = "Direct Reaction " + std::to_string(log_level); + const std::string expected = "Direct Reaction " + NUClear::to_string(log_level); REQUIRE(messages[i].message == expected); REQUIRE(messages[i].level == log_level); REQUIRE(messages[i++].from_reaction); @@ -189,7 +189,7 @@ TEST_CASE("Testing the Log<>() function", "[api][log]") { // Test logs from reactions indirectly for (const auto& log_level : levels) { if (display_level <= log_level) { - const std::string expected = "Indirect Reaction " + std::to_string(log_level); + const std::string expected = "Indirect Reaction " + NUClear::to_string(log_level); REQUIRE(messages[i].message == expected); REQUIRE(messages[i].level == log_level); REQUIRE(messages[i++].from_reaction); @@ -198,7 +198,7 @@ TEST_CASE("Testing the Log<>() function", "[api][log]") { // Test logs from free floating functions for (const auto& log_level : levels) { // No filter here, free floating prints everything - const std::string expected = "Non Reaction " + std::to_string(log_level); + const std::string expected = "Non Reaction " + NUClear::to_string(log_level); REQUIRE(messages[i].message == expected); REQUIRE(messages[i].level == log_level); REQUIRE_FALSE(messages[i++].from_reaction); @@ -207,7 +207,7 @@ TEST_CASE("Testing the Log<>() function", "[api][log]") { // Test post-shutdown logs { - const std::string expected = "Post Powerplant Shutdown " + std::to_string(NUClear::FATAL); + const std::string expected = "Post Powerplant Shutdown " + NUClear::to_string(NUClear::FATAL); REQUIRE(messages[i].message == expected); REQUIRE(messages[i].level == NUClear::FATAL); REQUIRE(messages[i++].from_reaction); @@ -219,7 +219,7 @@ TEST_CASE("Testing the Log<>() function", "[api][log]") { // Test logs from free floating functions for (const auto& log_level : levels) { // No filter here, free floating prints everything - const std::string expected = "Non Reaction " + std::to_string(log_level); + const std::string expected = "Non Reaction " + NUClear::to_string(log_level); REQUIRE(messages[i].message == expected); REQUIRE(messages[i].level == log_level); REQUIRE_FALSE(messages[i++].from_reaction);