omni/log/ILog.h

File members: omni/log/ILog.h

// Copyright (c) 2020-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 "../core/Api.h"
#include "../core/BuiltIn.h"
#include "../core/IObject.h"
#include "../str/IReadOnlyCString.h"
#include "../log/LogChannel.h"
#include "../extras/OutArrayUtils.h"
#include "../../carb/thread/Util.h"

#include <cstring>
#include <vector>

#define OMNI_LOG_VERBOSE(channelOrFormat_, ...)                                                                        \
    OMNI_LOG_WRITE(channelOrFormat_, omni::log::Level::eVerbose, ##__VA_ARGS__)

#define OMNI_LOG_INFO(channelOrFormat_, ...) OMNI_LOG_WRITE(channelOrFormat_, omni::log::Level::eInfo, ##__VA_ARGS__)

#define OMNI_LOG_WARN(channelOrFormat_, ...) OMNI_LOG_WRITE(channelOrFormat_, omni::log::Level::eWarn, ##__VA_ARGS__)

#define OMNI_LOG_ERROR(channelOrFormat_, ...) OMNI_LOG_WRITE(channelOrFormat_, omni::log::Level::eError, ##__VA_ARGS__)

#define OMNI_LOG_FATAL(channelOrFormat_, ...) OMNI_LOG_WRITE(channelOrFormat_, omni::log::Level::eFatal, ##__VA_ARGS__)

#define OMNI_LOG_WRITE(channelOrFormat_, level_, ...)                                                                  \
    do                                                                                                                 \
    {                                                                                                                  \
        OMNI_LOG_VALIDATE_FORMAT(channelOrFormat_, ##__VA_ARGS__)                                                      \
        if (omni::log::detail::isChannelEnabled(level_, OMNI_LOG_DEFAULT_CHANNEL, channelOrFormat_))                   \
        {                                                                                                              \
            omni::log::ILog* log_ = omniGetLogWithoutAcquire();                                                        \
            if (log_)                                                                                                  \
            {                                                                                                          \
                omni::log::detail::writeLog(OMNI_LOG_DEFAULT_CHANNEL, channelOrFormat_, log_, level_, __FILE__,        \
                                            __func__, __LINE__, ##__VA_ARGS__);                                        \
            }                                                                                                          \
        }                                                                                                              \
    } while (0)

namespace omni
{

namespace log
{

enum class OMNI_ATTR("prefix=e") SettingBehavior : uint32_t
{
    eInherit,
    eOverride
};

enum class OMNI_ATTR("prefix=e") ChannelUpdateReason : uint32_t
{
    eChannelAdded,
    eChannelRemoved,
    eLevelUpdated,
    eEnabledUpdated,
    eDescriptionUpdated,
};

enum class OMNI_ATTR("prefix=e") Level : int32_t
{
    eVerbose = -2,

    eInfo = -1,

    eWarn = 0,

    eError = 1,

    eFatal = 2,

    eDisabled = 3,
};

class ILogMessageConsumer_abi;
class ILogMessageConsumer;

class ILogChannelUpdateConsumer_abi;
class ILogChannelUpdateConsumer;

class ILog_abi;
class ILog;

class ILogMessageConsumer_abi
    : public omni::core::Inherits<omni::core::IObject, OMNI_TYPE_ID("omni.log.ILogMessageConsumer")>
{
protected:
    virtual void onMessage_abi(OMNI_ATTR("c_str, not_null") const char* channel,
                               Level level,
                               OMNI_ATTR("c_str") const char* moduleName,
                               OMNI_ATTR("c_str") const char* fileName,
                               OMNI_ATTR("c_str") const char* functionName,
                               uint32_t lineNumber,
                               OMNI_ATTR("c_str, not_null") const char* msg,
                               carb::thread::ProcessId pid,
                               carb::thread::ThreadId tid,
                               uint64_t timestamp) noexcept = 0;
};

class ILogChannelUpdateConsumer_abi
    : public omni::core::Inherits<omni::core::IObject, OMNI_TYPE_ID("omni.log.ILogChannelUpdateConsumer")>
{
protected:
    virtual void onChannelUpdate_abi(OMNI_ATTR("not_null") ILog* log,
                                     omni::str::IReadOnlyCString* name,
                                     ChannelUpdateReason reason) noexcept = 0;
};

class ILog_abi : public omni::core::Inherits<omni::core::IObject, OMNI_TYPE_ID("omni.log.ILog")>
{
protected:
    virtual OMNI_ATTR("no_py") void log_abi(OMNI_ATTR("c_str, not_null") const char* channel,
                                            Level level,
                                            OMNI_ATTR("c_str") const char* moduleName,
                                            OMNI_ATTR("c_str") const char* fileName,
                                            OMNI_ATTR("c_str") const char* functionName,
                                            uint32_t lineNumber,
                                            OMNI_ATTR("c_str, not_null") const char* str,
                                            uint32_t strCharCount) noexcept = 0;

    virtual OMNI_ATTR("no_py") void logf_abi(OMNI_ATTR("c_str, not_null") const char* channel,
                                             Level level,
                                             OMNI_ATTR("c_str") const char* moduleName,
                                             OMNI_ATTR("c_str") const char* fileName,
                                             OMNI_ATTR("c_str") const char* functionName,
                                             uint32_t lineNumber,
                                             OMNI_ATTR("c_str, not_null") const char* format,
                                             va_list args) noexcept = 0;

    virtual OMNI_ATTR("consumer=onMessage_abi") void addMessageConsumer_abi(OMNI_ATTR("not_null")
                                                                                ILogMessageConsumer* consumer) noexcept = 0;

    virtual void removeMessageConsumer_abi(ILogMessageConsumer* consumer) noexcept = 0;

    virtual OMNI_ATTR("no_api, no_py") omni::core::Result getMessageConsumers_abi( // disable omni.bind until OM-21202
        OMNI_ATTR("out, count=*consumersCount, *not_null") ILogMessageConsumer** consumers,
        OMNI_ATTR("in, out, not_null") uint32_t* consumersCount) noexcept = 0;

    virtual void setLevel_abi(Level level) noexcept = 0;

    virtual Level getLevel_abi() noexcept = 0;

    virtual void setEnabled_abi(bool isEnabled) noexcept = 0;

    virtual bool isEnabled_abi() noexcept = 0;

    virtual OMNI_ATTR("py_not_prop") bool setAsync_abi(bool logAsync) noexcept = 0;

    virtual OMNI_ATTR("py_not_prop") bool isAsync_abi() noexcept = 0;

    virtual OMNI_ATTR("no_py") void addChannel_abi(OMNI_ATTR("c_str, not_null") const char* name,
                                                   OMNI_ATTR("in, out, not_null") Level* level,
                                                   OMNI_ATTR("c_str") const char* description) noexcept = 0;

    virtual OMNI_ATTR("no_py") void removeChannel_abi(OMNI_ATTR("c_str, not_null") const char* name,
                                                      OMNI_ATTR("in, out, not_null") Level* level) noexcept = 0;

    virtual OMNI_ATTR("no_api, no_py") omni::core::Result getChannelNames_abi( // disable omni.bind until OM-21202
        OMNI_ATTR("out, count=*namesCount, *not_null") omni::str::IReadOnlyCString** names,
        OMNI_ATTR("in, out, not_null") uint32_t* namesCount) noexcept = 0;

    virtual void setChannelLevel_abi(OMNI_ATTR("c_str, not_null") const char* name,
                                     Level level,
                                     SettingBehavior behavior) noexcept = 0;

    virtual omni::core::Result getChannelLevel_abi(OMNI_ATTR("c_str, not_null") const char* name,
                                                   OMNI_ATTR("out, not_null") Level* outLevel,
                                                   OMNI_ATTR("out, not_null") SettingBehavior* outBehavior) noexcept = 0;

    virtual void setChannelEnabled_abi(OMNI_ATTR("c_str, not_null") const char* name,
                                       bool isEnabled,
                                       SettingBehavior behavior) noexcept = 0;

    virtual omni::core::Result getChannelEnabled_abi(OMNI_ATTR("c_str, not_null") const char* name,
                                                     OMNI_ATTR("out, not_null") bool* outIsEnabled,
                                                     OMNI_ATTR("out, not_null")
                                                         SettingBehavior* outBehavior) noexcept = 0;

    virtual void setChannelDescription_abi(OMNI_ATTR("c_str, not_null") const char* name,
                                           OMNI_ATTR("c_str, not_null") const char* description) noexcept = 0;

    virtual OMNI_ATTR("no_py") omni::core::Result getChannelDescription_abi( // OM-21456: disable omni.bind py bindings
        OMNI_ATTR("c_str, not_null") const char* name,
        OMNI_ATTR("out, not_null") omni::str::IReadOnlyCString** outDescription) noexcept = 0;

    virtual bool isLoggingAtLevel_abi(OMNI_ATTR("c_str, not_null") const char* name, Level level) noexcept = 0;

    virtual void flush_abi() noexcept = 0;

    virtual void OMNI_ATTR("consumer=onChannelUpdate_abi")
        addChannelUpdateConsumer_abi(OMNI_ATTR("not_null") ILogChannelUpdateConsumer* consumer) noexcept = 0;

    virtual void removeChannelUpdateConsumer_abi(ILogChannelUpdateConsumer* consumer) noexcept = 0;

    virtual OMNI_ATTR("no_api, no_py") omni::core::Result getChannelUpdateConsumers_abi( // disable omni.bind until
                                                                                         // OM-21202
        OMNI_ATTR("out, count=*consumersCount, *not_null") ILogChannelUpdateConsumer** consumers,
        OMNI_ATTR("in, out, not_null") uint32_t* consumersCount) noexcept = 0;
};

} // namespace log
} // namespace omni

#define OMNI_BIND_INCLUDE_INTERFACE_DECL
#include "ILog.gen.h"

#ifdef OMNI_COMPILE_AS_DYNAMIC_LIBRARY
OMNI_API omni::log::ILog* omniGetLogWithoutAcquire();
#else
inline omni::log::ILog* omniGetLogWithoutAcquire()
{
    return static_cast<omni::log::ILog*>(omniGetBuiltInWithoutAcquire(OmniBuiltIn::eILog));
}
#endif

OMNI_API omni::log::ILog* omniCreateLog();

// omniGetModuleFilename is also forward declared in omni/core/Omni.h.  Exhale doesn't like that.
#ifndef DOXYGEN_SHOULD_SKIP_THIS

OMNI_API const char* omniGetModuleFilename();

#endif

class omni::log::ILogMessageConsumer : public omni::core::Generated<omni::log::ILogMessageConsumer_abi>
{
};

class omni::log::ILogChannelUpdateConsumer : public omni::core::Generated<omni::log::ILogChannelUpdateConsumer_abi>
{
};

class omni::log::ILog : public omni::core::Generated<omni::log::ILog_abi>
{
public:
    std::vector<omni::core::ObjectPtr<omni::log::ILogMessageConsumer>> getMessageConsumers() noexcept;

    std::vector<omni::core::ObjectPtr<omni::str::IReadOnlyCString>> getChannelNames() noexcept;

    std::vector<omni::core::ObjectPtr<omni::log::ILogChannelUpdateConsumer>> getChannelUpdateConsumers() noexcept;

    void setChannelEnabled(const omni::log::LogChannelData& channel,
                           bool isEnabled,
                           omni::log::SettingBehavior behavior) noexcept
    {
        setChannelEnabled(channel.name, isEnabled, behavior);
    }

    omni::core::Result getChannelEnabled(const omni::log::LogChannelData& channel,
                                         bool* outEnabled,
                                         omni::log::SettingBehavior* outBehavior) noexcept
    {
        return getChannelEnabled(channel.name, outEnabled, outBehavior);
    }

    // We must expose setChannelEnabled(const char*, ...) since setChannelEnabled(LogChannelData, ...) hides it.
    using omni::core::Generated<omni::log::ILog_abi>::setChannelEnabled;
    using omni::core::Generated<omni::log::ILog_abi>::getChannelEnabled;

    void setChannelLevel(const omni::log::LogChannelData& channel,
                         omni::log::Level level,
                         omni::log::SettingBehavior behavior) noexcept
    {
        setChannelLevel(channel.name, level, behavior);
    }

    omni::core::Result getChannelLevel(const omni::log::LogChannelData& channel,
                                       omni::log::Level* outLevel,
                                       omni::log::SettingBehavior* outBehavior) noexcept
    {
        return getChannelLevel(channel.name, outLevel, outBehavior);
    }

    // We must expose setChannelLevel(const char*, ...) since setChannelEnabled(LogChannelData, ...) hides it.
    using omni::core::Generated<omni::log::ILog_abi>::setChannelLevel;
    using omni::core::Generated<omni::log::ILog_abi>::getChannelLevel;

    bool isLoggingAtLevel(const omni::log::LogChannelData& channel, omni::log::Level level)
    {
        return isLoggingAtLevel(channel.name, level);
    }

    // We must expose isLoggingAtLevel(const char*, ...) since isLoggingAtLevel(LogChannelData, ...) hides it.
    using omni::core::Generated<omni::log::ILog_abi>::isLoggingAtLevel;
};

#define OMNI_BIND_INCLUDE_INTERFACE_IMPL
#include "ILog.gen.h"

namespace omni
{
namespace log
{

#ifndef DOXYGEN_SHOULD_SKIP_THIS

namespace detail
{
#    if CARB_COMPILER_GNUC || CARB_TOOLCHAIN_CLANG
// clang sees compileTimeValidateFormat<>() as an unimplemented function (which it intentionally
// is) on mac and generates a warning.  We'll just silence that warning.
CARB_IGNOREWARNING_CLANG_WITH_PUSH("-Wundefined-internal")

// Utilizes an __attribute__ to validates the given fmt at compile time.  Does not produce any code. Works for GCC but
// not MSVC.
void compileTimeValidateFormat(const LogChannelData& channel, const char* fmt, ...) CARB_PRINTF_FUNCTION(2, 3);

// Utilizes an __attribute__ to validates the given fmt at compile time.  Does not produce any code. Works for GCC but
// not MSVC.
void compileTimeValidateFormat(const char* fmt, ...) CARB_PRINTF_FUNCTION(1, 2);

#        define OMNI_LOG_VALIDATE_FORMAT(...)                                                                          \
            if (false)                                                                                                 \
            {                                                                                                          \
                omni::log::detail::compileTimeValidateFormat(__VA_ARGS__);                                             \
            }

CARB_IGNOREWARNING_CLANG_POP
#    else
#        define OMNI_LOG_VALIDATE_FORMAT(...)
#    endif

// Implementation detail. Checks the message's level against the channel's level and logs as appropriate.
template <class... ArgList>
void writeLog(const LogChannelData& /*ignore*/,
              const LogChannelData& channel,
              ILog* log,
              Level level,
              const char* filename,
              const char* function,
              int32_t line,
              const char* fmt,
              ArgList&&... args)
{
    log->logf(
        channel.name, level, omniGetModuleFilename(), filename, function, line, fmt, std::forward<ArgList>(args)...);
}

// Implementation detail. Initiates logging to the default channel.
template <class... ArgList>
void writeLog(const LogChannelData& channel,
              const char* fmt,
              ILog* log,
              Level level,
              const char* filename,
              const char* function,
              int32_t line,
              ArgList&&... args)
{
    log->logf(
        channel.name, level, omniGetModuleFilename(), filename, function, line, fmt, std::forward<ArgList>(args)...);
}

// Implementation detail. Checks the message's level against the channel's level.
inline bool isChannelEnabled(Level level, const LogChannelData& /*ignore*/, const LogChannelData& channel)
{
    return (static_cast<Level>(channel.level) <= level);
}

// Implementation detail. Checks the message's level against the default channel
inline bool isChannelEnabled(Level level, const LogChannelData& channel, const char* /*fmt*/)
{
    return (static_cast<Level>(channel.level) <= level);
}

} // namespace detail

#endif

inline void addModulesChannels()
{
    auto log = omniGetLogWithoutAcquire();
    if (log)
    {
        for (auto channel = getModuleLogChannels(); channel; channel = channel->next)
        {
            log->addChannel(channel->name, reinterpret_cast<Level*>(&channel->level), channel->description);
        }
    }
}

inline void removeModulesChannels()
{
    auto log = omniGetLogWithoutAcquire();
    if (log)
    {
        for (auto channel = getModuleLogChannels(); channel; channel = channel->next)
        {
            log->removeChannel(channel->name, reinterpret_cast<Level*>(&channel->level));
        }
    }
}

} // namespace log
} // namespace omni

#ifndef DOXYGEN_SHOULD_SKIP_THIS

inline std::vector<omni::core::ObjectPtr<omni::log::ILogMessageConsumer>> omni::log::ILog::getMessageConsumers() noexcept
{
    std::vector<omni::core::ObjectPtr<omni::log::ILogMessageConsumer>> out;
    auto result = omni::extras::getOutArray<omni::log::ILogMessageConsumer*>(
        [this](omni::log::ILogMessageConsumer** consumers, uint32_t* consumersCount) // get func
        {
            std::memset(consumers, 0, sizeof(omni::log::ILogMessageConsumer*) * *consumersCount); // incoming ptrs
                                                                                                  // must be nullptr
            return this->getMessageConsumers_abi(consumers, consumersCount);
        },
        [&out](omni::log::ILogMessageConsumer** names, uint32_t namesCount) // fill func
        {
            out.reserve(namesCount);
            for (uint32_t i = 0; i < namesCount; ++i)
            {
                out.emplace_back(names[i], omni::core::kSteal);
            }
        });

    if (OMNI_FAILED(result))
    {
        OMNI_LOG_ERROR("unable to retrieve log channel settings consumers: 0x%08X", result);
    }

    return out;
}

inline std::vector<omni::core::ObjectPtr<omni::str::IReadOnlyCString>> omni::log::ILog::getChannelNames() noexcept
{
    std::vector<omni::core::ObjectPtr<omni::str::IReadOnlyCString>> out;
    auto result = omni::extras::getOutArray<omni::str::IReadOnlyCString*>(
        [this](omni::str::IReadOnlyCString** names, uint32_t* namesCount) // get func
        {
            std::memset(names, 0, sizeof(omni::str::IReadOnlyCString*) * *namesCount); // incoming ptrs must be nullptr
            return this->getChannelNames_abi(names, namesCount);
        },
        [&out](omni::str::IReadOnlyCString** names, uint32_t namesCount) // fill func
        {
            out.reserve(namesCount);
            for (uint32_t i = 0; i < namesCount; ++i)
            {
                out.emplace_back(names[i], omni::core::kSteal);
            }
        });

    if (OMNI_FAILED(result))
    {
        OMNI_LOG_ERROR("unable to retrieve log channel names: 0x%08X", result);
    }

    return out;
}

inline std::vector<omni::core::ObjectPtr<omni::log::ILogChannelUpdateConsumer>> omni::log::ILog::getChannelUpdateConsumers() noexcept
{
    std::vector<omni::core::ObjectPtr<omni::log::ILogChannelUpdateConsumer>> out;
    auto result = omni::extras::getOutArray<omni::log::ILogChannelUpdateConsumer*>(
        [this](omni::log::ILogChannelUpdateConsumer** consumers, uint32_t* consumersCount) // get func
        {
            std::memset(consumers, 0, sizeof(omni::log::ILogChannelUpdateConsumer*) * *consumersCount); // incoming
                                                                                                        // ptrs must
                                                                                                        // be nullptr
            return this->getChannelUpdateConsumers_abi(consumers, consumersCount);
        },
        [&out](omni::log::ILogChannelUpdateConsumer** names, uint32_t namesCount) // fill func
        {
            out.reserve(namesCount);
            for (uint32_t i = 0; i < namesCount; ++i)
            {
                out.emplace_back(names[i], omni::core::kSteal);
            }
        });

    if (OMNI_FAILED(result))
    {
        OMNI_LOG_ERROR("unable to retrieve log channel updated consumers: 0x%08X", result);
    }

    return out;
}

#endif