Skip to content

Commit

Permalink
iox-eclipse-iceoryx#1755 Add a log frontend to the platform
Browse files Browse the repository at this point in the history
  • Loading branch information
elBoberido committed Mar 11, 2024
1 parent 19184d4 commit b846437
Show file tree
Hide file tree
Showing 3 changed files with 235 additions and 7 deletions.
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
58 changes: 58 additions & 0 deletions iceoryx_platform/generic/include/iceoryx_platform/logging.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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__), log_level, msg)
// NOLINTEND(cppcoreguidelines-macro-usage)

#endif // IOX_PLATFORM_LOGGING_HPP
168 changes: 168 additions & 0 deletions iceoryx_platform/generic/source/logging.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// 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)
{
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.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");

constexpr uint64_t YIELDS_BEFORE_SLEEP{10000};
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));
}
}
return logger;
}

logger.log_backend.store(new_log_backend);
logger.logger_exchange_state.store(LoggerExchangeState::CUSTOM);
}

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);
}

0 comments on commit b846437

Please sign in to comment.