diff --git a/libuavcan/include/uavcan/protocol/logger.hpp b/libuavcan/include/uavcan/protocol/logger.hpp new file mode 100644 index 0000000000..b1dc931616 --- /dev/null +++ b/libuavcan/include/uavcan/protocol/logger.hpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2014 Pavel Kirienko + */ + +#pragma once + +#include +#include +#include +#include +#include + +#if !defined(UAVCAN_CPP_VERSION) || !defined(UAVCAN_CPP11) +# error UAVCAN_CPP_VERSION +#endif + +namespace uavcan +{ + +class ILogSink +{ +public: + virtual ~ILogSink() { } + virtual void log(const protocol::debug::LogMessage& message) = 0; +}; + + +class Logger +{ +public: + typedef typename StorageType::Type LogLevel; + + static const LogLevel LevelAboveAll = (protocol::debug::LogLevel::FieldTypes::value::BitLen << 1) - 1; + +private: + enum { DefaultTxTimeoutMs = 2000 }; + + Publisher logmsg_pub_; + protocol::debug::LogMessage msg_buf_; + LogLevel level_; + ILogSink* external_sink_; + +public: + Logger(INode& node) + : logmsg_pub_(node) + , external_sink_(NULL) + { + level_ = protocol::debug::LogLevel::ERROR; + setTxTimeout(MonotonicDuration::fromMSec(DefaultTxTimeoutMs)); + assert(getTxTimeout() == MonotonicDuration::fromMSec(DefaultTxTimeoutMs)); + } + + int log(const protocol::debug::LogMessage& message); + + LogLevel getLevel() const { return level_; } + void setLevel(LogLevel level) { level_ = level; } + + ILogSink* getExternalSink() const { return external_sink_; } + void setExternalSink(ILogSink* sink) { external_sink_ = sink; } + + MonotonicDuration getTxTimeout() const { return logmsg_pub_.getTxTimeout(); } + void setTxTimeout(MonotonicDuration val) { logmsg_pub_.setTxTimeout(val); } + +#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 + + template + int log(LogLevel level, const char* source, const char* format, Args... args) + { + if (level >= level_) + { + msg_buf_.level.value = level; + msg_buf_.source = source; + msg_buf_.text.clear(); + CharArrayFormatter formatter(msg_buf_.text); + formatter.write(format, args...); + return log(msg_buf_); + } + return 0; + } + + template + int logDebug(const char* source, const char* format, Args... args) + { + return log(protocol::debug::LogLevel::DEBUG, source, format, args...); + } + + template + int logInfo(const char* source, const char* format, Args... args) + { + return log(protocol::debug::LogLevel::INFO, source, format, args...); + } + + template + int logWarning(const char* source, const char* format, Args... args) + { + return log(protocol::debug::LogLevel::WARNING, source, format, args...); + } + + template + int logError(const char* source, const char* format, Args... args) + { + return log(protocol::debug::LogLevel::ERROR, source, format, args...); + } + +#else + + int log(LogLevel level, const char* source, const char* text) + { + if (level >= level_) + { + msg_buf_.level.value = level; + msg_buf_.source = source; + msg_buf_.text = text; + return log(msg_buf_); + } + return 0; + } + + int logDebug(const char* source, const char* text) + { + return log(protocol::debug::LogLevel::DEBUG, source, text); + } + + int logInfo(const char* source, const char* text) + { + return log(protocol::debug::LogLevel::INFO, source, text); + } + + int logWarning(const char* source, const char* text) + { + return log(protocol::debug::LogLevel::WARNING, source, text); + } + + int logError(const char* source, const char* text) + { + return log(protocol::debug::LogLevel::ERROR, source, text); + } + +#endif +}; + +} diff --git a/libuavcan/src/protocol/logger.cpp b/libuavcan/src/protocol/logger.cpp new file mode 100644 index 0000000000..ce63cabf47 --- /dev/null +++ b/libuavcan/src/protocol/logger.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2014 Pavel Kirienko + */ + +#include + +namespace uavcan +{ + +const Logger::LogLevel Logger::LevelAboveAll; + +int Logger::log(const protocol::debug::LogMessage& message) +{ + if (message.level.value >= level_) + { + const int res = logmsg_pub_.broadcast(message); + if (external_sink_) + { + external_sink_->log(message); + } + return res; + } + return 0; +} + +} diff --git a/libuavcan/test/protocol/logger.cpp b/libuavcan/test/protocol/logger.cpp new file mode 100644 index 0000000000..016e36b8fb --- /dev/null +++ b/libuavcan/test/protocol/logger.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2014 Pavel Kirienko + */ + +#include +#include +#include "helpers.hpp" + + +struct LogSink : public uavcan::ILogSink +{ + std::queue msgs; + + void log(const uavcan::protocol::debug::LogMessage& message) + { + msgs.push(message); + std::cout << message << std::endl; + } + + uavcan::protocol::debug::LogMessage pop() + { + if (msgs.empty()) + { + std::cout << "LogSink is empty" << std::endl; + std::abort(); + } + const uavcan::protocol::debug::LogMessage m = msgs.front(); + msgs.pop(); + return m; + } + + bool popMatchByLevelAndText(int level, const std::string& source, const std::string& text) + { + const uavcan::protocol::debug::LogMessage m = pop(); + return + level == m.level.value && + source == m.source && + text == m.text; + } +}; + + +TEST(Logger, Basic) +{ + InterlinkedTestNodes nodes; + + uavcan::Logger logger(nodes.a); + + ASSERT_EQ(uavcan::protocol::debug::LogLevel::ERROR, logger.getLevel()); + + LogSink sink; + + // Will fail - types are not registered + uavcan::GlobalDataTypeRegistry::instance().reset(); + ASSERT_GT(0, logger.logError("foo", "Error (fail - type is not registered)")); + ASSERT_EQ(0, logger.logDebug("foo", "Debug (ignored - low logging level)")); + + ASSERT_FALSE(logger.getExternalSink()); + logger.setExternalSink(&sink); + ASSERT_EQ(&sink, logger.getExternalSink()); + + uavcan::GlobalDataTypeRegistry::instance().reset(); + uavcan::DefaultDataTypeRegistrator _reg1; + + SubscriberWithCollector log_sub(nodes.b); + ASSERT_LE(0, log_sub.start()); + + ASSERT_EQ(0, logger.logDebug("foo", "Debug (ignored due to low logging level)")); + ASSERT_LE(0, logger.logError("foo", "Error")); + nodes.spinBoth(uavcan::MonotonicDuration::fromMSec(2)); + ASSERT_EQ(log_sub.collector.msg->level.value, uavcan::protocol::debug::LogLevel::ERROR); + ASSERT_EQ(log_sub.collector.msg->source, "foo"); + ASSERT_EQ(log_sub.collector.msg->text, "Error"); + + logger.setLevel(uavcan::protocol::debug::LogLevel::DEBUG); + ASSERT_LE(0, logger.logWarning("foo", "Warning")); + nodes.spinBoth(uavcan::MonotonicDuration::fromMSec(2)); + ASSERT_EQ(log_sub.collector.msg->level.value, uavcan::protocol::debug::LogLevel::WARNING); + ASSERT_EQ(log_sub.collector.msg->source, "foo"); + ASSERT_EQ(log_sub.collector.msg->text, "Warning"); + + ASSERT_LE(0, logger.logInfo("foo", "Info")); + nodes.spinBoth(uavcan::MonotonicDuration::fromMSec(2)); + ASSERT_EQ(log_sub.collector.msg->level.value, uavcan::protocol::debug::LogLevel::INFO); + ASSERT_EQ(log_sub.collector.msg->source, "foo"); + ASSERT_EQ(log_sub.collector.msg->text, "Info"); + + ASSERT_LE(0, logger.logDebug("foo", "Debug")); + nodes.spinBoth(uavcan::MonotonicDuration::fromMSec(2)); + ASSERT_EQ(log_sub.collector.msg->level.value, uavcan::protocol::debug::LogLevel::DEBUG); + ASSERT_EQ(log_sub.collector.msg->source, "foo"); + ASSERT_EQ(log_sub.collector.msg->text, "Debug"); + + ASSERT_TRUE(sink.popMatchByLevelAndText(uavcan::protocol::debug::LogLevel::ERROR, "foo", "Error")); + ASSERT_TRUE(sink.popMatchByLevelAndText(uavcan::protocol::debug::LogLevel::WARNING, "foo", "Warning")); + ASSERT_TRUE(sink.popMatchByLevelAndText(uavcan::protocol::debug::LogLevel::INFO, "foo", "Info")); + ASSERT_TRUE(sink.popMatchByLevelAndText(uavcan::protocol::debug::LogLevel::DEBUG, "foo", "Debug")); +} + +#if !defined(UAVCAN_CPP_VERSION) || !defined(UAVCAN_CPP11) +# error UAVCAN_CPP_VERSION +#endif + +#if UAVCAN_CPP_VERSION >= UAVCAN_CPP11 + +TEST(Logger, Cpp11Formatting) +{ + InterlinkedTestNodes nodes; + + uavcan::Logger logger(nodes.a); + logger.setLevel(uavcan::protocol::debug::LogLevel::DEBUG); + + SubscriberWithCollector log_sub(nodes.b); + ASSERT_LE(0, log_sub.start()); + + ASSERT_LE(0, logger.logWarning("foo", "char='%*', %* is %*", '$', "double", 12.34)); + nodes.spinBoth(uavcan::MonotonicDuration::fromMSec(2)); + ASSERT_EQ(log_sub.collector.msg->level.value, uavcan::protocol::debug::LogLevel::WARNING); + ASSERT_EQ(log_sub.collector.msg->source, "foo"); + ASSERT_EQ(log_sub.collector.msg->text, "char='$', double is 12.340000"); +} + +#endif