carb/logging/Log.h

File members: carb/logging/Log.h

// Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//

#pragma once

#include "../Framework.h"
#include "ILogging.h"

#include "../../omni/log/ILog.h"

#include <cctype>
#include <cstdint>
#include <cstdio>

// example-begin Log levels
namespace carb
{
namespace logging
{

const int32_t kLevelVerbose = -2;
const int32_t kLevelInfo = -1;
const int32_t kLevelWarn = 0;
const int32_t kLevelError = 1;
const int32_t kLevelFatal = 2;

} // namespace logging
} // namespace carb
// example-end

CARB_WEAKLINK int32_t g_carbLogLevel = carb::logging::kLevelWarn;
CARB_WEAKLINK carb::logging::LogFn g_carbLogFn CARB_PRINTF_FUNCTION(6, 7);
CARB_WEAKLINK carb::logging::ILogging* g_carbLogging;

#if CARB_COMPILER_GNUC || defined(DOXYGEN_BUILD)
#    define CARB_FAKE_PRINTF(fmt, ...)                                                                                 \
        do                                                                                                             \
        {                                                                                                              \
        } while (0)
#else
#    define CARB_FAKE_PRINTF(fmt, ...)                                                                                 \
        do                                                                                                             \
        {                                                                                                              \
            if (false)                                                                                                 \
                ::printf(fmt, ##__VA_ARGS__);                                                                          \
        } while (0)
#endif

#define CARB_LOG(level, fmt, ...)                                                                                      \
    do                                                                                                                 \
    {                                                                                                                  \
        auto lvl = (level);                                                                                            \
        if (g_carbLogFn && g_carbLogLevel <= lvl)                                                                      \
        {                                                                                                              \
            CARB_FAKE_PRINTF(fmt, ##__VA_ARGS__);                                                                      \
            g_carbLogFn(g_carbClientName, lvl, __FILE__, __func__, __LINE__, fmt, ##__VA_ARGS__);                      \
        }                                                                                                              \
    } while (0)

#define CARB_LOG_VERBOSE(fmt, ...) CARB_LOG(carb::logging::kLevelVerbose, fmt, ##__VA_ARGS__)
#define CARB_LOG_INFO(fmt, ...) CARB_LOG(carb::logging::kLevelInfo, fmt, ##__VA_ARGS__)
#define CARB_LOG_WARN(fmt, ...) CARB_LOG(carb::logging::kLevelWarn, fmt, ##__VA_ARGS__)
#define CARB_LOG_ERROR(fmt, ...) CARB_LOG(carb::logging::kLevelError, fmt, ##__VA_ARGS__)
#define CARB_LOG_FATAL(fmt, ...) CARB_LOG(carb::logging::kLevelFatal, fmt, ##__VA_ARGS__)

#define CARB_LOG_ONCE(level, fmt, ...)                                                                                   \
    do                                                                                                                   \
    {                                                                                                                    \
        static bool CARB_JOIN(logged_, __LINE__) =                                                                       \
            (g_carbLogFn && g_carbLogLevel <= (level)) ?                                                                 \
                (false ?                                                                                                 \
                     (::printf(fmt, ##__VA_ARGS__), true) :                                                              \
                     (g_carbLogFn(g_carbClientName, (level), __FILE__, __func__, __LINE__, fmt, ##__VA_ARGS__), true)) : \
                false;                                                                                                   \
        CARB_UNUSED(CARB_JOIN(logged_, __LINE__));                                                                       \
    } while (0)

#define CARB_LOG_VERBOSE_ONCE(fmt, ...) CARB_LOG_ONCE(carb::logging::kLevelVerbose, fmt, ##__VA_ARGS__)
#define CARB_LOG_INFO_ONCE(fmt, ...) CARB_LOG_ONCE(carb::logging::kLevelInfo, fmt, ##__VA_ARGS__)
#define CARB_LOG_WARN_ONCE(fmt, ...) CARB_LOG_ONCE(carb::logging::kLevelWarn, fmt, ##__VA_ARGS__)
#define CARB_LOG_ERROR_ONCE(fmt, ...) CARB_LOG_ONCE(carb::logging::kLevelError, fmt, ##__VA_ARGS__)
#define CARB_LOG_FATAL_ONCE(fmt, ...) CARB_LOG_ONCE(carb::logging::kLevelFatal, fmt, ##__VA_ARGS__)

#define CARB_LOG_GLOBALS()

namespace carb
{
namespace logging
{

inline ILogging* getLogging()
{
    return g_carbLogging;
}

inline void registerLoggingForClient() noexcept
{
    g_carbLogging = getFramework()->tryAcquireInterface<ILogging>();
    if (g_carbLogging)
    {
        g_carbLogging->registerSource(g_carbClientName, [](int32_t logLevel) { g_carbLogLevel = logLevel; });
        g_carbLogFn = g_carbLogging->log;
    }

    omni::log::addModulesChannels();
}

inline void deregisterLoggingForClient() noexcept
{
    omni::log::removeModulesChannels();

    if (g_carbLogging)
    {
        g_carbLogFn = nullptr;
        if (getFramework() && getFramework()->verifyInterface<ILogging>(g_carbLogging))
        {
            g_carbLogging->unregisterSource(g_carbClientName);
        }
        g_carbLogging = nullptr;
    }
}

struct StringToLogLevelMapping
{
    const char* name;
    int32_t level;
};

const StringToLogLevelMapping kStringToLevelMappings[] = { { "verbose", kLevelVerbose },
                                                           { "info", kLevelInfo },
                                                           { "warning", kLevelWarn },
                                                           { "error", kLevelError },
                                                           { "fatal", kLevelFatal } };

const size_t kStringToLevelMappingsCount = CARB_COUNTOF(kStringToLevelMappings);

inline int32_t stringToLevel(const char* levelString)
{
    const int32_t kFallbackLevel = kLevelFatal;
    if (!levelString)
        return kFallbackLevel;

    // Since our log level identifiers start with different characters, we're allowed to just compare the first one.
    // However, whether the need arises, it is worth making a score-based system, where full match will win over
    // partial match, if there are similarly starting log levels.
    int lcLevelChar = tolower((int)levelString[0]);
    for (size_t lm = 0; lm < kStringToLevelMappingsCount; ++lm)
    {
        int lcMappingChar = tolower((int)kStringToLevelMappings[lm].name[0]);
        if (lcLevelChar == lcMappingChar)
            return kStringToLevelMappings[lm].level;
    }

    // Ideally, this should never happen if level string is valid.
    CARB_ASSERT(false);
    CARB_LOG_ERROR("Unknown log level string: %s", levelString);
    return kFallbackLevel;
}
} // namespace logging
} // namespace carb