Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iox-#1755 Redirect printing in the platform layer to the logger #2222

1 change: 1 addition & 0 deletions iceoryx_hoofs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ iox_add_library(
reporting/source/hoofs_error_reporting.cpp
reporting/source/console_logger.cpp
reporting/source/logger.cpp
reporting/source/logging.cpp
time/source/duration.cpp
utility/source/unique_id.cpp

Expand Down
16 changes: 9 additions & 7 deletions iceoryx_hoofs/primitives/include/iox/iceoryx_hoofs_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
/// @note since this file will be included by many other files, it should not include other header except
/// iceoryx_platform or STL header

#include "iceoryx_platform/logging.hpp"

#include <cstdint>

namespace iox
Expand All @@ -31,13 +33,13 @@ namespace log
/// @brief This enum defines the log levels used for logging.
enum class LogLevel : uint8_t
{
OFF = 0,
FATAL,
ERROR,
WARN,
INFO,
DEBUG,
TRACE
OFF = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_OFF,
FATAL = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_FATAL,
ERROR = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_ERROR,
WARN = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_WARN,
INFO = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_INFO,
DEBUG = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_DEBUG,
TRACE = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_TRACE,
};

/// @brief converts LogLevel into a string literal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ inline void Logger<BaseLogger>::initLoggerInternal(const LogLevel logLevel) noex
{
BaseLogger::setLogLevel(logLevel);
BaseLogger::initLogger(logLevel);
iox_platform_set_log_backend(&platform_log_backend);
m_isFinalized.store(true, std::memory_order_relaxed);
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#ifndef IOX_HOOFS_REPORTING_LOG_BUILDING_BLOCKS_LOGGER_HPP
#define IOX_HOOFS_REPORTING_LOG_BUILDING_BLOCKS_LOGGER_HPP

#include "iceoryx_platform/logging.hpp"
#include "iox/iceoryx_hoofs_types.hpp"

#include <atomic>
Expand Down Expand Up @@ -52,6 +53,12 @@ LogLevel logLevelFromEnvOr(const LogLevel logLevel) noexcept;

namespace internal
{
/// @brief The backend for the platform logging frontend
/// @copydoc IceoryxPlatformLogBackend
/// @note Needs to be implemented in 'logging.cpp' in order to use the high level log API
void platform_log_backend(
const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg);

/// @brief This class acts as common interface for the Logger. It provides the common functionality and inherits from
/// the BaseLogger which is provided as template parameter. Please have a look at the design document for more details.
/// @tparam[in] BaseLogger is the actual implementation
Expand Down
55 changes: 55 additions & 0 deletions iceoryx_hoofs/reporting/source/logging.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) 2024 by Mathias Kraus <[email protected]>. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

#include "iox/logging.hpp"

namespace iox::log::internal
{
// NOLINTJUSTIFICATION Not used directly but as a function pointer to set the backend
// NOLINTNEXTLINE(readability-function-size)
void platform_log_backend(
const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg)
{
auto level = LogLevel::TRACE;
switch (log_level)
{
case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_OFF:
level = LogLevel::OFF;
break;
case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_FATAL:
level = LogLevel::FATAL;
break;
case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_ERROR:
level = LogLevel::ERROR;
break;
case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_WARN:
level = LogLevel::WARN;
break;
case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_INFO:
level = LogLevel::INFO;
break;
case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_DEBUG:
level = LogLevel::DEBUG;
break;
case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_TRACE:
level = LogLevel::TRACE;
break;
default:
level = LogLevel::TRACE;
}
IOX_LOG_INTERNAL(file, line, function, level, msg);
}
} // namespace iox::log::internal
59 changes: 59 additions & 0 deletions iceoryx_platform/generic/include/iceoryx_platform/logging.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) 2024 by Mathias Kraus <[email protected]>. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

#ifndef IOX_PLATFORM_LOGGING_HPP
#define IOX_PLATFORM_LOGGING_HPP

enum IceoryxPlatformLogLevel
{
IOX_PLATFORM_LOG_LEVEL_OFF = 0,
IOX_PLATFORM_LOG_LEVEL_FATAL,
IOX_PLATFORM_LOG_LEVEL_ERROR,
IOX_PLATFORM_LOG_LEVEL_WARN,
IOX_PLATFORM_LOG_LEVEL_INFO,
IOX_PLATFORM_LOG_LEVEL_DEBUG,
IOX_PLATFORM_LOG_LEVEL_TRACE
};

/// @brief Typedef to the platform log backend
/// @param[in] file should be the '__FILE__' compiler intrinsic
/// @param[in] line should be the '__LINE__' compiler intrinsic
/// @param[in] function should be the '__FUNCTION__' compiler intrinsic
/// @param[in] log_level is the log level to be used for the log message
/// @param[in] msg is the message to be logged
typedef void (*IceoryxPlatformLogBackend)(
const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg);

/// @brief Sets the logging backend to the provided function
/// @param[in] log_backend to be used
/// @note The log_backend must have a static lifetime and must be thread-safe
void iox_platform_set_log_backend(IceoryxPlatformLogBackend log_backend);

/// @note Implementation detail! Do not use directly
void iox_platform_detail_log(
const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg);

// NOLINTJUSTIFICATION The functionality of the macro (obtaining the source location) cannot be achieved with C++17
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
/// @brief Frontend for logging in the iceoryx platform
/// @param[in] log_level is the log level to be used for the log message
/// @param[in] msg is the message to be logged
#define IOX_PLATFORM_LOG(log_level, msg) \
iox_platform_detail_log( \
__FILE__, __LINE__, static_cast<const char*>(__FUNCTION__), IceoryxPlatformLogLevel::log_level, msg)
// NOLINTEND(cppcoreguidelines-macro-usage)

#endif // IOX_PLATFORM_LOGGING_HPP
169 changes: 169 additions & 0 deletions iceoryx_platform/generic/source/logging.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Copyright (c) 2024 by Mathias Kraus <[email protected]>. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

#include "iceoryx_platform/logging.hpp"

#include <atomic>
#include <iostream>
#include <mutex>
#include <sstream>
#include <thread>

namespace
{
// NOLINTJUSTIFICATION only used in this file; should be fine
// NOLINTNEXTLINE(readability-function-size)
void default_log_backend(
const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg)
{
if (log_level == IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_OFF)
{
return;
}

std::stringstream stream;
stream << file << ":" << line << " (" << function << ") ";

switch (log_level)
{
case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_FATAL:
stream << "[Fatal]";
break;
case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_ERROR:
stream << "[Error]";
break;
case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_WARN:
stream << "[Warn ]";
break;
case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_INFO:
stream << "[Info ]";
break;
case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_DEBUG:
stream << "[Debug]";
break;
case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_TRACE:
stream << "[Trace]";
break;
default:
stream << "[UNDEF]";
break;
}

stream << " " << msg;

// NOLINTJUSTIFICATION We want to flush the line with each log output; if performance matters a custom log backend can be used, e.g. the logger in hoofs
// NOLINTNEXTLINE(performance-avoid-endl)
std::cout << stream.str() << std::endl;
}

enum class LoggerExchangeState : uint8_t
{
DEFAULT,
PENDING,
CUSTOM,
};

struct IceoryxPlatformLogger
{
std::atomic<IceoryxPlatformLogBackend> log_backend{&default_log_backend};
std::atomic<LoggerExchangeState> logger_exchange_state{LoggerExchangeState::DEFAULT};
};

IceoryxPlatformLogger& active_logger(IceoryxPlatformLogBackend new_log_backend)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this code was copied from some place else?!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I got inspired :)

I couldn't use the approach from the hoofs logger since it was not possible to expose the atomics so I needed to get creative and came up with this solution. I might even move it to the hoofs logger since it does not require a mutex.

{
static IceoryxPlatformLogger logger;

if (new_log_backend != nullptr)
{
auto exchange_state = LoggerExchangeState::DEFAULT;
auto successful =
logger.logger_exchange_state.compare_exchange_strong(exchange_state, LoggerExchangeState::PENDING);
if (successful)
{
logger.log_backend.store(new_log_backend);
logger.logger_exchange_state.store(LoggerExchangeState::CUSTOM);
}
else
{
constexpr uint64_t YIELDS_BEFORE_SLEEP{10000};
elBoberido marked this conversation as resolved.
Show resolved Hide resolved
uint64_t yield_counter = 0;
while (logger.logger_exchange_state.load() != LoggerExchangeState::CUSTOM)
{
if (yield_counter < YIELDS_BEFORE_SLEEP)
{
std::this_thread::yield();
++yield_counter;
}
else
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}

logger.log_backend.load(std::memory_order_relaxed)(__FILE__,
__LINE__,
static_cast<const char*>(__FUNCTION__),
IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_ERROR,
"Trying to replace logger after already initialized");

new_log_backend(__FILE__,
__LINE__,
static_cast<const char*>(__FUNCTION__),
IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_ERROR,
"Trying to replace logger after already initialized");
}
}

return logger;
}
} // namespace

// NOLINTJUSTIFICATION only used from a macro which hides most of the parameter; should be fine
// NOLINTNEXTLINE(readability-function-size)
void iox_platform_detail_log(
const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg)
{
// NOLINTJUSTIFICATION needed for the functionality
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static IceoryxPlatformLogger* logger = &active_logger(nullptr);
thread_local static LoggerExchangeState current_logger_exchange_state = logger->logger_exchange_state.load();
// NOTE if the logger got updated in between, the current_logger_exchange_state has an old value and we will enter
// the if statement
thread_local static IceoryxPlatformLogBackend log_backend = logger->log_backend.load();

if (current_logger_exchange_state == LoggerExchangeState::PENDING
|| current_logger_exchange_state != logger->logger_exchange_state.load(std::memory_order_relaxed))
{
// the logger can be changed only once and if we enter this if statement the 'active_logger' function will only
// return once 'logger_exchange_state' equals 'CUSTOM'
logger = &active_logger(nullptr);
log_backend = logger->log_backend.load();
current_logger_exchange_state = logger->logger_exchange_state.load();
}

log_backend(file, line, function, log_level, msg);
}

void iox_platform_set_log_backend(IceoryxPlatformLogBackend log_backend)
{
if (log_backend == nullptr)
{
IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "'log_backend' must not be a nullptr!");
return;
}

active_logger(log_backend);
}
Loading
Loading