diff --git a/sdk/core/azure-core-amqp/src/amqp/claim_based_security.cpp b/sdk/core/azure-core-amqp/src/amqp/claim_based_security.cpp index a37d8b1b9..9cf495378 100644 --- a/sdk/core/azure-core-amqp/src/amqp/claim_based_security.cpp +++ b/sdk/core/azure-core-amqp/src/amqp/claim_based_security.cpp @@ -120,9 +120,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { auto cbs = static_cast(const_cast(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(const_cast(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( diff --git a/sdk/core/azure-core-amqp/src/amqp/management.cpp b/sdk/core/azure-core-amqp/src/amqp/management.cpp index b92e94a55..55f15238f 100644 --- a/sdk/core/azure-core-amqp/src/amqp/management.cpp +++ b/sdk/core/azure-core-amqp/src/amqp/management.cpp @@ -183,8 +183,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { ManagementClientImpl* management = static_cast(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); } diff --git a/sdk/core/azure-core-amqp/src/amqp/message_receiver.cpp b/sdk/core/azure-core-amqp/src/amqp/message_receiver.cpp index 5f435aca7..cadb4c370 100644 --- a/sdk/core/azure-core-amqp/src/amqp/message_receiver.cpp +++ b/sdk/core/azure-core-amqp/src/amqp/message_receiver.cpp @@ -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 #include #include +#include #include @@ -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 diff --git a/sdk/core/azure-core-amqp/src/amqp/message_sender.cpp b/sdk/core/azure-core-amqp/src/amqp/message_sender.cpp index a7b337cb4..58cad4c6e 100644 --- a/sdk/core/azure-core-amqp/src/amqp/message_sender.cpp +++ b/sdk/core/azure-core-amqp/src/amqp/message_sender.cpp @@ -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 #include #include +#include #include @@ -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 struct RewriteSendComplete + template 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>>(onSendComplete)); + auto result = messagesender_send_async( + m_messageSender.get(), + Models::_internal::AmqpMessageFactory::ToUamqp(message).get(), + std::remove_pointer::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 + 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>>(onSendComplete)); - auto result = messagesender_send_async( - m_messageSender.get(), - Models::_internal::AmqpMessageFactory::ToUamqp(message).get(), - std::remove_pointer::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 - 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 diff --git a/sdk/core/azure-core-amqp/src/amqp/session.cpp b/sdk/core/azure-core-amqp/src/amqp/session.cpp index c29a858c6..40b3ce3e3 100644 --- a/sdk/core/azure-core-amqp/src/amqp/session.cpp +++ b/sdk/core/azure-core-amqp/src/amqp/session.cpp @@ -237,9 +237,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { m_claimsBasedSecurity = std::make_shared(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) { diff --git a/sdk/core/azure-core-amqp/src/network/socket_transport.cpp b/sdk/core/azure-core-amqp/src/network/socket_transport.cpp index 441f73e91..8945e9cba 100644 --- a/sdk/core/azure-core-amqp/src/network/socket_transport.cpp +++ b/sdk/core/azure-core-amqp/src/network/socket_transport.cpp @@ -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( diff --git a/sdk/core/azure-core/CHANGELOG.md b/sdk/core/azure-core/CHANGELOG.md index 3e06de28b..8318cc097 100644 --- a/sdk/core/azure-core/CHANGELOG.md +++ b/sdk/core/azure-core/CHANGELOG.md @@ -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 diff --git a/sdk/core/azure-core/inc/azure/core/internal/diagnostics/log.hpp b/sdk/core/azure-core/inc/azure/core/internal/diagnostics/log.hpp index c4b700ed7..e983ecf8e 100644 --- a/sdk/core/azure-core/inc/azure/core/internal/diagnostics/log.hpp +++ b/sdk/core/azure-core/inc/azure/core/internal/diagnostics/log.hpp @@ -7,6 +7,8 @@ #include "azure/core/dll_import_export.hpp" #include +#include +#include #include 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 { + 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 diff --git a/sdk/core/azure-core/src/environment_log_level_listener.cpp b/sdk/core/azure-core/src/environment_log_level_listener.cpp index d20347644..e8fea9492 100644 --- a/sdk/core/azure-core/src/environment_log_level_listener.cpp +++ b/sdk/core/azure-core/src/environment_log_level_listener.cpp @@ -120,11 +120,30 @@ EnvironmentLogLevelListener::GetLogListener() static std::function 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; diff --git a/sdk/core/azure-core/src/http/curl/curl.cpp b/sdk/core/azure-core/src/http/curl/curl.cpp index 89c0ade9d..6717e84a2 100644 --- a/sdk/core/azure-core/src/http/curl/curl.cpp +++ b/sdk/core/azure-core/src/http/curl/curl.cpp @@ -1487,7 +1487,7 @@ namespace Azure { namespace Core { Azure::Core::_internal::UniqueHandle 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 crl; #if defined(USE_OPENSSL_3) crl = Azure::Core::_internal::MakeUniqueHandle( diff --git a/sdk/core/azure-core/src/logger.cpp b/sdk/core/azure-core/src/logger.cpp index ded80a435..71d6c5276 100644 --- a/sdk/core/azure-core/src/logger.cpp +++ b/sdk/core/azure-core/src/logger.cpp @@ -6,8 +6,10 @@ #include "azure/core/internal/diagnostics/log.hpp" #include "private/environment_log_level_listener.hpp" +#include #include #include +#include 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."); +} diff --git a/sdk/core/azure-core/test/ut/environment_log_level_listener_test.cpp b/sdk/core/azure-core/test/ut/environment_log_level_listener_test.cpp index dae8a371f..03357e163 100644 --- a/sdk/core/azure-core/test/ut/environment_log_level_listener_test.cpp +++ b/sdk/core/azure-core/test/ut/environment_log_level_listener_test.cpp @@ -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(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); } diff --git a/sdk/core/azure-core/test/ut/logging_test.cpp b/sdk/core/azure-core/test/ut/logging_test.cpp index d71e7250b..9a033a63c 100644 --- a/sdk/core/azure-core/test/ut/logging_test.cpp +++ b/sdk/core/azure-core/test/ut/logging_test.cpp @@ -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(); + } +}