Add the ability to log diagnostics using an ostream. Fixes #4696 (#4700)

* Fixes #4696 - Added support to Log::Write to use insertion operators for logging

* EnvironmentLogger writes to stderr on errors, stdout for non-errors; Also don't double insert crlf in messages

---------

Co-authored-by: Ahson Khan <ahkha@microsoft.com>
This commit is contained in:
Larry Osterman 2023-06-06 15:20:20 -07:00 committed by GitHub
parent 2f7a3eec8b
commit 0dd8882462
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 411 additions and 185 deletions

View File

@ -120,9 +120,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail {
auto cbs = static_cast<ClaimsBasedSecurityImpl*>(const_cast<void*>(context));
if (cbs->m_traceEnabled)
{
std::stringstream ss;
ss << "OnCbsOpenComplete: " << OpenResultStringFromLowLevel(openCompleteResult);
Log::Write(Logger::Level::Informational, ss.str());
Log::Stream(Logger::Level::Informational)
<< "OnCbsOpenComplete: " << OpenResultStringFromLowLevel(openCompleteResult) << std::endl;
}
cbs->m_openResultQueue.CompleteOperation(CbsOpenResultStateFromLowLevel(openCompleteResult));
}
@ -158,11 +157,11 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail {
auto cbs = static_cast<ClaimsBasedSecurityImpl*>(const_cast<void*>(context));
if (cbs->m_traceEnabled)
{
std::stringstream ss;
ss << "OnCbsOperationComplete: " << OperationResultStringFromLowLevel(operationCompleteResult)
<< " StatusCode: " << statusCode << " StatusDescription: "
<< (statusDescription ? std::string(statusDescription) : "(NULL)");
Log::Write(Logger::Level::Informational, ss.str());
Log::Stream(Logger::Level::Informational)
<< "OnCbsOperationComplete: "
<< OperationResultStringFromLowLevel(operationCompleteResult)
<< " StatusCode: " << statusCode << " StatusDescription: "
<< (statusDescription ? std::string(statusDescription) : "(NULL)") << std::endl;
}
cbs->m_operationResultQueue.CompleteOperation(

View File

@ -183,8 +183,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail {
ManagementClientImpl* management = static_cast<ManagementClientImpl*>(context);
if (management->m_options.EnableTrace)
{
Log::Write(
Logger::Level::Informational, "OnManagementOpenComplete: " + std::to_string(openResult));
Log::Stream(Logger::Level::Informational)
<< "OnManagementOpenComplete: " << std::to_string(openResult) << std::endl;
}
management->m_openCompleteQueue.CompleteOperation(openResult);
}

View File

@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-Licence-Identifier: MIT
// Enable declaration of strerror_s.
#define __STDC_WANT_LIB_EXT1__ 1
#include "azure/core/amqp/message_receiver.hpp"
#include "azure/core/amqp/connection.hpp"
@ -14,6 +17,7 @@
#include <azure/core/credentials/credentials.hpp>
#include <azure/core/diagnostics/logger.hpp>
#include <azure/core/internal/diagnostics/log.hpp>
#include <azure/core/platform.hpp>
#include <azure_uamqp_c/message_receiver.h>
@ -103,10 +107,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail {
m_eventHandler->OnMessageReceiverDisconnected(error);
}
// Log that an error occurred.
Log::Write(
Logger::Level::Error,
"Message receiver link detached: " + error.Condition.ToString() + ": "
+ error.Description);
Log::Stream(Logger::Level::Error)
<< "Message receiver link detached: " + error.Condition.ToString() << ": "
<< error.Description << std::endl;
// Cache the error we received in the OnDetach notification so we can return it to the user on
// the next send which fails.
@ -232,10 +235,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail {
}
else
{
std::stringstream ss;
ss << "Message receiver changed state. New: " << MESSAGE_RECEIVER_STATEStrings[newState]
<< " Old: " << MESSAGE_RECEIVER_STATEStrings[oldState] << std::endl;
Log::Write(Logger::Level::Verbose, ss.str());
Log::Stream(Logger::Level::Verbose)
<< "Message receiver changed state. New: " << MESSAGE_RECEIVER_STATEStrings[newState]
<< " Old: " << MESSAGE_RECEIVER_STATEStrings[oldState] << std::endl;
;
}
// If we are transitioning to the error state, we want to stick a response on the incoming queue
@ -279,38 +282,34 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail {
{
// LCOV_EXCL_START
auto err = errno;
#ifdef _MSC_VER
#pragma warning(push)
// warning C4996: 'strerror': This function or variable may be unsafe. Consider using gmtime_s
// instead.
#pragma warning(disable : 4996)
#if defined(AZ_PLATFORM_WINDOWS)
char buf[256];
strerror_s(buf, sizeof(buf), err);
#else
std::string buf{strerror(err)};
#endif
throw std::runtime_error(
"Could not open message receiver. errno=" + std::to_string(err) + ", \""
+ strerror(err) + "\".");
#ifdef _MSC_VER
#pragma warning(pop)
#endif
// LCOV_EXCL_STOP
}
}
throw std::runtime_error(
"Could not open message receiver. errno=" + std::to_string(err) + ", \"" + buf + "\".");
// LCOV_EXCL_STOP
}
}
void MessageReceiverImpl::Close()
{
if (messagereceiver_close(m_messageReceiver.get()))
{
throw std::runtime_error("Could not close message receiver"); // LCOV_EXCL_LINE
}
}
void MessageReceiverImpl::Close()
{
if (messagereceiver_close(m_messageReceiver.get()))
{
throw std::runtime_error("Could not close message receiver"); // LCOV_EXCL_LINE
}
}
std::string MessageReceiverImpl::GetLinkName() const
{
const char* linkName;
if (messagereceiver_get_link_name(m_messageReceiver.get(), &linkName))
{
throw std::runtime_error("Could not get link name");
}
return std::string(linkName);
}
std::string MessageReceiverImpl::GetLinkName() const
{
const char* linkName;
if (messagereceiver_get_link_name(m_messageReceiver.get(), &linkName))
{
throw std::runtime_error("Could not get link name");
}
return std::string(linkName);
}
}}}} // namespace Azure::Core::Amqp::_detail

View File

@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-Licence-Identifier: MIT
// Enable declaration of strerror_s.
#define __STDC_WANT_LIB_EXT1__ 1
#include "azure/core/amqp/claims_based_security.hpp"
#include "azure/core/amqp/common/completion_operation.hpp"
#include "azure/core/amqp/models/amqp_message.hpp"
@ -13,6 +16,7 @@
#include <azure/core/credentials/credentials.hpp>
#include <azure/core/diagnostics/logger.hpp>
#include <azure/core/internal/diagnostics/log.hpp>
#include <azure/core/platform.hpp>
#include <azure_uamqp_c/message_sender.h>
@ -117,9 +121,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail {
m_events->OnMessageSenderDisconnected(error);
}
// Log that an error occurred.
Log::Write(
Logger::Level::Error,
"Message sender link detached: " + error.Condition.ToString() + ": " + error.Description);
Log::Stream(Logger::Level::Error)
<< "Message sender link detached: " << error.Condition.ToString() << ": "
<< error.Description << std::endl;
// Cache the error we received in the OnDetach notification so we can return it to the user on
// the next send which fails.
@ -202,118 +206,114 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail {
{
// LCOV_EXCL_START
auto err = errno;
#ifdef _MSC_VER
#pragma warning(push)
// warning C4996: 'strerror': This function or variable may be unsafe. Consider using strerror_s
// instead.
#pragma warning(disable : 4996)
#if defined(AZ_PLATFORM_WINDOWS)
char buf[256];
strerror_s(buf, sizeof(buf), err);
#else
std::string buf{strerror(err)};
#endif
throw std::runtime_error(
"Could not open message sender. errno=" + std::to_string(err) + ", \"" + strerror(err)
+ "\".");
#ifdef _MSC_VER
#pragma warning(pop)
#endif
// LCOV_EXCL_STOP
}
}
void MessageSenderImpl::Close()
{
if (messagesender_close(m_messageSender.get()))
{
throw std::runtime_error("Could not close message sender"); // LCOV_EXCL_LINE
}
}
throw std::runtime_error(
"Could not open message sender. errno=" + std::to_string(err) + ", \"" + buf + "\".");
// LCOV_EXCL_STOP
}
}
void MessageSenderImpl::Close()
{
if (messagesender_close(m_messageSender.get()))
{
throw std::runtime_error("Could not close message sender"); // LCOV_EXCL_LINE
}
}
template <typename CompleteFn> struct RewriteSendComplete
template <typename CompleteFn> struct RewriteSendComplete
{
static void OnOperation(
CompleteFn onComplete,
MESSAGE_SEND_RESULT sendResult,
AMQP_VALUE disposition)
{
_internal::MessageSendStatus result{_internal::MessageSendStatus::Ok};
switch (sendResult)
{
static void OnOperation(
CompleteFn onComplete,
MESSAGE_SEND_RESULT sendResult,
AMQP_VALUE disposition)
{
_internal::MessageSendStatus result{_internal::MessageSendStatus::Ok};
switch (sendResult)
case MESSAGE_SEND_RESULT_INVALID: // LCOV_EXCL_LINE
result = _internal::MessageSendStatus::Invalid; // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
case MESSAGE_SEND_OK:
result = _internal::MessageSendStatus::Ok;
break;
case MESSAGE_SEND_CANCELLED: // LCOV_EXCL_LINE
result = _internal::MessageSendStatus::Cancelled; // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
case MESSAGE_SEND_ERROR: // LCOV_EXCL_LINE
result = _internal::MessageSendStatus::Error; // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
case MESSAGE_SEND_TIMEOUT: // LCOV_EXCL_LINE
result = _internal::MessageSendStatus::Timeout; // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
}
onComplete(result, disposition);
}
};
void MessageSenderImpl::QueueSend(
Models::AmqpMessage const& message,
Azure::Core::Amqp::_internal::MessageSender::MessageSendCompleteCallback onSendComplete,
Context const& context)
{
auto operation(std::make_unique<Azure::Core::Amqp::Common::_internal::CompletionOperation<
decltype(onSendComplete),
RewriteSendComplete<decltype(onSendComplete)>>>(onSendComplete));
auto result = messagesender_send_async(
m_messageSender.get(),
Models::_internal::AmqpMessageFactory::ToUamqp(message).get(),
std::remove_pointer<decltype(operation)::element_type>::type::OnOperationFn,
operation.release(),
0 /*timeout*/);
if (result == nullptr)
{
throw std::runtime_error("Could not send message"); // LCOV_EXCL_LINE
}
(void)context;
}
std::tuple<_internal::MessageSendStatus, Models::AmqpValue> MessageSenderImpl::Send(
Models::AmqpMessage const& message,
Context const& context)
{
Azure::Core::Amqp::Common::_internal::
AsyncOperationQueue<Azure::Core::Amqp::_internal::MessageSendStatus, Models::AmqpValue>
sendCompleteQueue;
QueueSend(
message,
[&sendCompleteQueue, this](
Azure::Core::Amqp::_internal::MessageSendStatus sendResult,
Models::AmqpValue deliveryStatus) {
// If the send failed. then we need to return the error. If the send completed because
// of an error, it's possible that the deliveryStatus provided is null. In that case,
// we use the cached saved error because it is highly likely to be better than
// nothing.
if (sendResult != _internal::MessageSendStatus::Ok)
{
case MESSAGE_SEND_RESULT_INVALID: // LCOV_EXCL_LINE
result = _internal::MessageSendStatus::Invalid; // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
case MESSAGE_SEND_OK:
result = _internal::MessageSendStatus::Ok;
break;
case MESSAGE_SEND_CANCELLED: // LCOV_EXCL_LINE
result = _internal::MessageSendStatus::Cancelled; // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
case MESSAGE_SEND_ERROR: // LCOV_EXCL_LINE
result = _internal::MessageSendStatus::Error; // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
case MESSAGE_SEND_TIMEOUT: // LCOV_EXCL_LINE
result = _internal::MessageSendStatus::Timeout; // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
if (deliveryStatus.IsNull())
{
deliveryStatus = m_savedMessageError;
}
}
onComplete(result, disposition);
}
};
void MessageSenderImpl::QueueSend(
Models::AmqpMessage const& message,
Azure::Core::Amqp::_internal::MessageSender::MessageSendCompleteCallback onSendComplete,
Context const& context)
{
auto operation(std::make_unique<Azure::Core::Amqp::Common::_internal::CompletionOperation<
decltype(onSendComplete),
RewriteSendComplete<decltype(onSendComplete)>>>(onSendComplete));
auto result = messagesender_send_async(
m_messageSender.get(),
Models::_internal::AmqpMessageFactory::ToUamqp(message).get(),
std::remove_pointer<decltype(operation)::element_type>::type::OnOperationFn,
operation.release(),
0 /*timeout*/);
if (result == nullptr)
{
throw std::runtime_error("Could not send message"); // LCOV_EXCL_LINE
}
(void)context;
}
std::tuple<_internal::MessageSendStatus, Models::AmqpValue> MessageSenderImpl::Send(
Models::AmqpMessage const& message,
Context const& context)
{
Azure::Core::Amqp::Common::_internal::
AsyncOperationQueue<Azure::Core::Amqp::_internal::MessageSendStatus, Models::AmqpValue>
sendCompleteQueue;
QueueSend(
message,
[&sendCompleteQueue, this](
Azure::Core::Amqp::_internal::MessageSendStatus sendResult,
Models::AmqpValue deliveryStatus) {
// If the send failed. then we need to return the error. If the send completed because
// of an error, it's possible that the deliveryStatus provided is null. In that case,
// we use the cached saved error because it is highly likely to be better than
// nothing.
if (sendResult != _internal::MessageSendStatus::Ok)
{
if (deliveryStatus.IsNull())
{
deliveryStatus = m_savedMessageError;
}
}
else
{
// If we successfully sent the message, then whatever saved error should be cleared,
// it's no longer valid.
m_savedMessageError = Models::AmqpValue();
}
sendCompleteQueue.CompleteOperation(sendResult, deliveryStatus);
},
context);
auto result = sendCompleteQueue.WaitForPolledResult(context, *m_session->GetConnection());
if (result)
{
return std::move(*result);
}
throw std::runtime_error("Error sending message"); // LCOV_EXCL_LINE
}
else
{
// If we successfully sent the message, then whatever saved error should be cleared,
// it's no longer valid.
m_savedMessageError = Models::AmqpValue();
}
sendCompleteQueue.CompleteOperation(sendResult, deliveryStatus);
},
context);
auto result = sendCompleteQueue.WaitForPolledResult(context, *m_session->GetConnection());
if (result)
{
return std::move(*result);
}
throw std::runtime_error("Error sending message"); // LCOV_EXCL_LINE
}
}}}} // namespace Azure::Core::Amqp::_detail

View File

@ -237,9 +237,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail {
m_claimsBasedSecurity = std::make_shared<ClaimsBasedSecurityImpl>(shared_from_this());
}
auto accessToken = GetConnection()->GetSecurityToken(audience, context);
Log::Write(
Logger::Level::Informational,
"Authenticate with audience: " + audience + ", token: " + accessToken);
Log::Stream(Logger::Level::Informational)
<< "Authenticate with audience: " << audience << ", token: " << accessToken << std::endl;
m_claimsBasedSecurity->SetTrace(GetConnection()->EnableTrace());
if (!m_cbsOpen)
{

View File

@ -24,9 +24,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace Network { namespac
uint16_t port,
TransportEvents* eventHandler)
{
Log::Write(
Logger::Level::Verbose,
"Create socket transport for host " + host + " port: " + std::to_string(port));
Log::Stream(Logger::Level::Verbose)
<< "Create socket transport for host " << host << " port: " << port << std::endl;
SOCKETIO_CONFIG socketConfig{host.c_str(), port, nullptr};
return _detail::TransportImpl::CreateFromXioHandle(

View File

@ -10,6 +10,8 @@
### Other Changes
- The default logger now logs all non-error outputs to `stdout` instead of `stderr`. Log level `Error` is still logged to `stderr`.
## 1.10.0 (2023-06-01)
### Features Added

View File

@ -7,6 +7,8 @@
#include "azure/core/dll_import_export.hpp"
#include <atomic>
#include <iostream>
#include <sstream>
#include <type_traits>
namespace Azure { namespace Core { namespace Diagnostics { namespace _internal {
@ -25,7 +27,29 @@ namespace Azure { namespace Core { namespace Diagnostics { namespace _internal {
Log() = delete;
~Log() = delete;
class LoggerStringBuffer : public std::stringbuf {
public:
LoggerStringBuffer(Logger::Level level) : m_level{level} {}
LoggerStringBuffer(LoggerStringBuffer&& that) = default;
LoggerStringBuffer& operator=(LoggerStringBuffer&& that) = default;
~LoggerStringBuffer() override = default;
virtual int sync() override;
private:
Logger::Level m_level;
};
public:
class LoggerStream : public std::basic_ostream<char> {
public:
LoggerStream(Logger::Level level) : std::ostream(&m_stringBuffer), m_stringBuffer{level} {}
~LoggerStream() override = default;
private:
LoggerStringBuffer m_stringBuffer;
};
static bool ShouldWrite(Logger::Level level)
{
return g_isLoggingEnabled && level >= g_logLevel;
@ -35,5 +59,6 @@ namespace Azure { namespace Core { namespace Diagnostics { namespace _internal {
static void EnableLogging(bool isEnabled);
static void SetLogLevel(Logger::Level logLevel);
static LoggerStream& Stream(Logger::Level level);
};
}}}} // namespace Azure::Core::Diagnostics::_internal

View File

@ -120,11 +120,30 @@ EnvironmentLogLevelListener::GetLogListener()
static std::function<void(Logger::Level level, std::string const& message)> const consoleLogger =
[](auto level, auto message) {
std::cerr << '['
<< Azure::DateTime(std::chrono::system_clock::now())
.ToString(
DateTime::DateFormat::Rfc3339, DateTime::TimeFractionFormat::AllDigits)
<< "] " << LogLevelToConsoleString(level) << " : " << message << std::endl;
std::ostream* os = &std::cout;
if (level == Logger::Level::Error)
{
os = &std::cerr;
}
*os << '['
<< Azure::DateTime(std::chrono::system_clock::now())
.ToString(DateTime::DateFormat::Rfc3339, DateTime::TimeFractionFormat::AllDigits)
<< "] " << LogLevelToConsoleString(level) << " : " << message;
// If the message ends with a new line, flush the stream otherwise insert a new line to
// terminate the message.
//
// If the client of the logger APIs is using the stream form of the logger, then it will
// insert a \n character when the client uses std::endl. This check ensures that we don't
// insert unnecessary new lines.
if (!message.empty() && message.back() == '\n')
{
*os << std::flush;
}
else
{
*os << std::endl;
}
};
return consoleLogger;

View File

@ -1487,7 +1487,7 @@ namespace Azure { namespace Core {
Azure::Core::_internal::UniqueHandle<X509_CRL> LoadCrlFromUrl(std::string const& url)
{
Log::Write(Logger::Level::Informational, "Load CRL from Url: " + url);
Log::Stream(Logger::Level::Informational) << "Load CRL from Url: " << url << std::endl;
Azure::Core::_internal::UniqueHandle<X509_CRL> crl;
#if defined(USE_OPENSSL_3)
crl = Azure::Core::_internal::MakeUniqueHandle(

View File

@ -6,8 +6,10 @@
#include "azure/core/internal/diagnostics/log.hpp"
#include "private/environment_log_level_listener.hpp"
#include <iostream>
#include <mutex>
#include <shared_mutex>
#include <sstream>
using namespace Azure::Core::Diagnostics;
using namespace Azure::Core::Diagnostics::_internal;
@ -49,3 +51,35 @@ void Logger::SetListener(
}
void Logger::SetLevel(Logger::Level level) { Log::SetLogLevel(level); }
int Log::LoggerStringBuffer::sync()
{
Log::Write(m_level, str());
str(std::string());
return 0;
}
static Log::LoggerStream g_verboseLogger{Logger::Level::Verbose};
static Log::LoggerStream g_informationalLogger{Logger::Level::Informational};
static Log::LoggerStream g_warningLogger{Logger::Level::Warning};
static Log::LoggerStream g_errorLogger{Logger::Level::Error};
/** Returns a custom ostream implementation with a logger based stream buffer.
* @param level The level of the log message.
* @return A custom ostream implementation.
*/
Log::LoggerStream& Log::Stream(Logger::Level level)
{
switch (level)
{
case Logger::Level::Verbose:
return g_verboseLogger;
case Logger::Level::Informational:
return g_informationalLogger;
case Logger::Level::Warning:
return g_warningLogger;
case Logger::Level::Error:
return g_errorLogger;
}
throw std::runtime_error("Unknown stream logger level.");
}

View File

@ -127,16 +127,15 @@ TEST_F(EnvironmentLogLevelListenerTest, GetLogListenerVerbose)
SetLogLevel("verbose");
std::stringstream buffer;
std::streambuf* old = std::cerr.rdbuf(buffer.rdbuf());
std::streambuf* old = std::cout.rdbuf(buffer.rdbuf());
std::string text = buffer.str(); // text will now contain "Bla\n"
auto listener = EnvironmentLogLevelListener::GetLogListener();
EXPECT_NE(listener, nullptr);
listener(Logger::Level::Verbose, "message");
EXPECT_NE(buffer.str().find("DEBUG : message"), std::string::npos);
std::cerr.rdbuf(old);
std::cout.rdbuf(old);
}
TEST_F(EnvironmentLogLevelListenerTest, GetLogListenerError)
@ -144,10 +143,10 @@ TEST_F(EnvironmentLogLevelListenerTest, GetLogListenerError)
EnvironmentLogLevelListener::SetInitialized(false);
SetLogLevel("verbose");
// Error logging goes to cerr, all other logging goes to cout.
std::stringstream buffer;
std::streambuf* old = std::cerr.rdbuf(buffer.rdbuf());
std::string text = buffer.str(); // text will now contain "Bla\n"
auto listener = EnvironmentLogLevelListener::GetLogListener();
EXPECT_NE(listener, nullptr);
@ -162,17 +161,17 @@ TEST_F(EnvironmentLogLevelListenerTest, GetLogListenerWarning)
EnvironmentLogLevelListener::SetInitialized(false);
SetLogLevel("verbose");
// Error logging goes to cerr, all other logging goes to cout.
std::stringstream buffer;
std::streambuf* old = std::cerr.rdbuf(buffer.rdbuf());
std::streambuf* old = std::cout.rdbuf(buffer.rdbuf());
std::string text = buffer.str(); // text will now contain "Bla\n"
auto listener = EnvironmentLogLevelListener::GetLogListener();
EXPECT_NE(listener, nullptr);
listener(Logger::Level::Warning, "message");
EXPECT_NE(buffer.str().find("WARN : message"), std::string::npos);
std::cerr.rdbuf(old);
std::cout.rdbuf(old);
}
TEST_F(EnvironmentLogLevelListenerTest, GetLogListenerInformational)
@ -180,17 +179,17 @@ TEST_F(EnvironmentLogLevelListenerTest, GetLogListenerInformational)
EnvironmentLogLevelListener::SetInitialized(false);
SetLogLevel("verbose");
// Error logging goes to cerr, all other logging goes to cout.
std::stringstream buffer;
std::streambuf* old = std::cerr.rdbuf(buffer.rdbuf());
std::streambuf* old = std::cout.rdbuf(buffer.rdbuf());
std::string text = buffer.str(); // text will now contain "Bla\n"
auto listener = EnvironmentLogLevelListener::GetLogListener();
EXPECT_NE(listener, nullptr);
listener(Logger::Level::Informational, "message");
EXPECT_NE(buffer.str().find("INFO : message"), std::string::npos);
std::cerr.rdbuf(old);
std::cout.rdbuf(old);
}
TEST_F(EnvironmentLogLevelListenerTest, GetLogListenerUnknown)
@ -198,15 +197,74 @@ TEST_F(EnvironmentLogLevelListenerTest, GetLogListenerUnknown)
EnvironmentLogLevelListener::SetInitialized(false);
SetLogLevel("verbose");
// Error logging goes to cerr, all other logging goes to cout.
std::stringstream buffer;
std::streambuf* old = std::cerr.rdbuf(buffer.rdbuf());
std::streambuf* old = std::cout.rdbuf(buffer.rdbuf());
std::string text = buffer.str(); // text will now contain "Bla\n"
auto listener = EnvironmentLogLevelListener::GetLogListener();
EXPECT_NE(listener, nullptr);
listener(static_cast<Logger::Level>(42), "message");
EXPECT_NE(buffer.str().find("????? : message"), std::string::npos);
std::cerr.rdbuf(old);
std::cout.rdbuf(old);
}
// Verify that the log listener inserts a crlf at the end of the message if none is provided.
TEST_F(EnvironmentLogLevelListenerTest, GetLogListenerWithNoCrlf)
{
EnvironmentLogLevelListener::SetInitialized(false);
SetLogLevel("verbose");
// Error logging goes to cerr, all other logging goes to cout.
std::stringstream buffer;
std::streambuf* old = std::cout.rdbuf(buffer.rdbuf());
auto listener = EnvironmentLogLevelListener::GetLogListener();
EXPECT_NE(listener, nullptr);
listener(Logger::Level::Informational, "message");
EXPECT_NE(buffer.str().find("INFO : message\n"), std::string::npos);
std::cout.rdbuf(old);
}
// Verify that the log listener does not insert a crlf at the end of the message if one is provided.
TEST_F(EnvironmentLogLevelListenerTest, GetLogListenerWithCrlf)
{
EnvironmentLogLevelListener::SetInitialized(false);
SetLogLevel("verbose");
// Error logging goes to cerr, all other logging goes to cout.
std::stringstream buffer;
std::streambuf* old = std::cout.rdbuf(buffer.rdbuf());
auto listener = EnvironmentLogLevelListener::GetLogListener();
EXPECT_NE(listener, nullptr);
listener(Logger::Level::Informational, "message\n");
EXPECT_NE(buffer.str().find("INFO : message\n"), std::string::npos);
EXPECT_EQ(buffer.str().find("INFO : message\n\n"), std::string::npos);
std::cout.rdbuf(old);
}
// Verify that the log listener handles empty strings correctly.
TEST_F(EnvironmentLogLevelListenerTest, GetLogListenerWithEmptyString)
{
EnvironmentLogLevelListener::SetInitialized(false);
SetLogLevel("verbose");
std::stringstream buffer;
// Error logging goes to cerr, all other logging goes to cout.
std::streambuf* old = std::cout.rdbuf(buffer.rdbuf());
auto listener = EnvironmentLogLevelListener::GetLogListener();
EXPECT_NE(listener, nullptr);
listener(Logger::Level::Informational, "");
EXPECT_NE(buffer.str().find("INFO : \n"), std::string::npos);
EXPECT_EQ(buffer.str().find("INFO : \n\n"), std::string::npos);
std::cout.rdbuf(old);
}

View File

@ -202,3 +202,94 @@ TEST(Logger, Message)
throw;
}
}
TEST(Logger, LoggerStream)
{
Logger::Level level = Logger::Level::Error;
std::string message;
Logger::SetListener([&](auto lvl, auto msg) {
level = lvl;
message = msg;
});
Logger::SetLevel(Logger::Level::Verbose);
{
Log::Stream(Logger::Level::Verbose) << "Verbose" << std::endl;
EXPECT_EQ(message, "Verbose\n");
Log::Stream(Logger::Level::Informational) << "Informational" << std::endl;
EXPECT_EQ(message, "Informational\n");
message.clear();
Log::Stream(Logger::Level::Warning) << "Warning" << std::endl;
EXPECT_EQ(message, "Warning\n");
message.clear();
Log::Stream(Logger::Level::Error) << "Error" << std::endl;
EXPECT_EQ(message, "Error\n");
message.clear();
Log::Stream(Logger::Level::Verbose) << "Test";
// std::endl flushes the stream, so we need to manually flush the ostream.
Log::Stream(Logger::Level::Verbose).flush();
EXPECT_EQ(message, "Test");
message.clear();
}
Logger::SetLevel(Logger::Level::Informational);
{
Log::Stream(Logger::Level::Verbose) << "Verbose" << std::endl;
EXPECT_EQ(message, "");
message.clear();
Log::Stream(Logger::Level::Informational) << "Informational" << std::endl;
EXPECT_EQ(message, "Informational\n");
message.clear();
Log::Stream(Logger::Level::Warning) << "Warning" << std::endl;
EXPECT_EQ(message, "Warning\n");
message.clear();
Log::Stream(Logger::Level::Error) << "Error" << std::endl;
EXPECT_EQ(message, "Error\n");
message.clear();
}
Logger::SetLevel(Logger::Level::Warning);
{
Log::Stream(Logger::Level::Verbose) << "Verbose" << std::endl;
EXPECT_EQ(message, "");
message.clear();
Log::Stream(Logger::Level::Informational) << "Informational" << std::endl;
EXPECT_EQ(message, "");
message.clear();
Log::Stream(Logger::Level::Warning) << "Warning" << std::endl;
EXPECT_EQ(message, "Warning\n");
message.clear();
Log::Stream(Logger::Level::Error) << "Error" << std::endl;
EXPECT_EQ(message, "Error\n");
message.clear();
}
Logger::SetLevel(Logger::Level::Error);
{
Log::Stream(Logger::Level::Verbose) << "Verbose" << std::endl;
EXPECT_EQ(message, "");
message.clear();
Log::Stream(Logger::Level::Informational) << "Informational" << std::endl;
EXPECT_EQ(message, "");
message.clear();
Log::Stream(Logger::Level::Warning) << "Warning" << std::endl;
EXPECT_EQ(message, "");
message.clear();
Log::Stream(Logger::Level::Error) << "Error" << std::endl;
EXPECT_EQ(message, "Error\n");
message.clear();
}
}