游戏引擎从零开始(3)-日志系统spdlog

883 阅读2分钟

前言

日志系统是所有程序中的刚需,本章节将开源的spdlog集成到引擎中。

spdlog是一个只有头文件,性能很好的C++日志库

集成spdlog

spdlog以submodule的形式集成到项目中。

所有的第三方库我们放到/Sanbox/Hazel/vendor下面

vendor供应商的意思,也可以表示第三方库

download spdlog

在项目根目录下执行下面命令:

git submodule add https://github.com/gabime/spdlog.git Sanbox/Hazel/vendor/spdlog

在根目录下会生成一个.gitmodules文件,而spdlog并不会加入到本项目的git记录中。

.gitmodules

[submodule "Sandbox/Hazel/vendor/spdlog"]
	path = Sandbox/Hazel/vendor/spdlog
	url = https://github.com/gabime/spdlog.git

增加了一些ignore

Hazel/.gitignore

/Sandbox/cmake-build-debug/
.idea/
build

更新Hazel引擎的CMake

更新CMake文件,增加spdlog引用

参考diff,修改的地方一目了然

实现Log封装

Log.h

声明两个静态的log实例,分别用于引擎内核的日志打印,和业务层的日志打印。这样做可以设置不同的tag,以及设置不同的日志策略。

两个Log对应两个静态的获取实例的函数。

namespace Hazel {
    class Log {
    public:

        static void Init();

        inline static std::shared_ptr<spdlog::logger>& GetCoreLogger(){
            return s_CoreLogger;
        }

        inline static std::shared_ptr<spdlog::logger>& GetClientLogger(){
            return s_ClientLogger;
        };

    private:
        static std::shared_ptr<spdlog::logger> s_CoreLogger;
        static std::shared_ptr<spdlog::logger> s_ClientLogger;
    };
}

Log.cpp中初始化spdlog。

spdlog的使用可以参考官方文档,有详细的说明,此处不做展开spdlog文档

#include "Log.h"

namespace Hazel{
    std::shared_ptr<spdlog::logger> Log::s_CoreLogger;
    std::shared_ptr<spdlog::logger> Log::s_ClientLogger;

    void Log::Init() {
        spdlog::set_pattern("%^[%T] %n : %v%$");
        Log::s_CoreLogger = spdlog::stdout_color_mt("HAZEL", spdlog::color_mode::always);
        Log::s_CoreLogger->set_level(spdlog::level::trace);

        Log::s_ClientLogger = spdlog::stdout_color_mt("APP", spdlog::color_mode::always);
        Log::s_ClientLogger->set_level(spdlog::level::trace);
    }
}

这里我们两个日志等级都设置成trace级别,tag分别设置为"HAZEL"和"APP"

注意,C++中,静态变量定义和声明需要分开写。
如Log::s_CoreLogger和Log::s_ClientLogger声明和定义。

Hazel.h文件中增加Log.h的引用


#ifndef SANBOX_HAZEL_H
#define SANBOX_HAZEL_H

#include <stdio.h>
#include "Hazel/Application.h"
#include "Hazel/Log.h"

// -----Entry Point-------
#include "Hazel/EntryPoint.h"

#endif //SANBOX_HAZEL_H

Log的使用及优化

回到EntryPoint.h文件中,使用Log打印日志

int main(int argc, char** argv) {
    Hazel::Log::Init();
    Hazel::Log::GetCoreLogger()->warn("Initialized Log!");
    Hazel::Log::GetClientLogger()->info("Hello!");

    auto app = Hazel::CreateApplication();
    app->Run();
    delete app;
}

可以看到虽然可以用了,但是打印一行日志的代码有点啰嗦,用宏定义简化。

回到Log.h中,在末尾增加宏定义

// Core log macros
#define HZ_CORE_TRACE(...)  ::Hazel::Log::GetCoreLogger()->trace(__VA_ARGS__)
#define HZ_CORE_INFO(...)   ::Hazel::Log::GetCoreLogger()->info(__VA_ARGS__)
#define HZ_CORE_WARN(...)   ::Hazel::Log::GetCoreLogger()->warn(__VA_ARGS__)
#define HZ_CORE_ERROR(...)  ::Hazel::Log::GetCoreLogger()->error(__VA_ARGS__)
#define HZ_CORE_FATAL(...)  ::Hazel::Log::GetCoreLogger()->fatal(__VA_ARGS__)


// Client log macros
#define HZ_TRACE(...)  ::Hazel::Log::GetClientLogger()->trace(__VA_ARGS__)
#define HZ_INFO(...)   ::Hazel::Log::GetClientLogger()->info(__VA_ARGS__)
#define HZ_WARN(...)   ::Hazel::Log::GetClientLogger()->warn(__VA_ARGS__)
#define HZ_ERROR(...)  ::Hazel::Log::GetClientLogger()->error(__VA_ARGS__)
#define HZ_FATAL(...)  ::Hazel::Log::GetClientLogger()->fatal(__VA_ARGS__)

EntryPoint.h中的调用就简单多了

int main(int argc, char** argv) {
    Hazel::Log::Init();
//    Hazel::Log::GetCoreLogger()->warn("Initialized Log!");
//    Hazel::Log::GetClientLogger()->info("Hello!");
    HZ_CORE_WARN("Initialed Log!");
    int a = 5;
    HZ_INFO("Hello! Var={0}", a);

    auto app = Hazel::CreateApplication();
    app->Run();
    delete app;
}

基于宏定义的日志除了简化调用逻辑,还有个重要的作用,区分不同包做不同的逻辑,比如在正式版本不希望打印调试日志,可以增加宏定义:
#define HZ_TRACE(...)

这样预编译的时候替换成空,相当于什么都没干。

完整代码commit参考:

commit-include spdlog into Hazel