aboutsummaryrefslogtreecommitdiff
path: root/src/core/log.h
blob: ca96b26fe5db76342451d9bd1351e8a022881028 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#pragma once

#include "core/memory.h"

#include <string>
#include <vector>
#include <sstream>
#include <iostream>
#include <fstream>
#include <chrono>
#include <iomanip>
#include <mutex>
#include <string_view>

namespace Donut
{
    enum class LogLevel
    {
        trace = 0,
        Info  = 2,
        Warn  = 3,
        Err   = 4,
        Fatal = 5
    };

    class Logger
    {
    public:
        Logger();
        ~Logger();

        static auto init() -> void;
        static auto shutdown() -> void;

        static auto get_logger() -> Ref<Logger>;

        template<typename... Args>
        static auto trace(const std::string_view& format, const Args&... args) -> void
        {
            auto logger = get_logger();
            if (logger) logger->log_message(LogLevel::trace, format, args...);
        }

        template<typename... Args>
        static auto info(const std::string_view& format, const Args&... args) -> void
        {
            auto logger = get_logger();
            if (logger) logger->log_message(LogLevel::Info, format, args...);
        }

        template<typename... Args>
        static auto warn(const std::string_view& format, const Args&... args) -> void
        {
            auto logger = get_logger();
            if (logger) logger->log_message(LogLevel::Warn, format, args...);
        }

        template<typename... Args>
        static auto error(const std::string_view& format, const Args&... args) -> void
        {
            auto logger = get_logger();
            if (logger) logger->log_message(LogLevel::Err, format, args...);
        }

        template<typename... Args>
        static auto fatal(const std::string_view& format, const Args&... args) -> void
        {
            auto logger = get_logger();
            if (logger) logger->log_message(LogLevel::Fatal, format, args...);
        }

        template<typename... Args>
        auto log_message(LogLevel level, const std::string_view& format, const Args&... args) -> void
        {
            if (level < m_log_level)
                return;

            std::lock_guard<std::mutex> lock(m_mutex);
            std::string message = format_string(format, args...);
            std::string full_message = get_time_stamp() + " [" + get_log_level_string(level) + "] " + message;

            if (m_console_output)
            {
                set_console_color(level);
                std::cout << full_message << std::endl;
                reset_console_color();
            }

            if (m_file_output && m_log_file.is_open())
            {
                m_log_file << full_message << std::endl;
                m_log_file.flush();
            }
        }

        template<typename... Args>
        auto format_string(const std::string_view& format, const Args&... args) -> std::string
        {
            std::string result = format.data();
            std::vector<std::string> arg_strings = { to_string(args)... };

            size_t arg_index = 0;
            size_t pos = 0;

            while ((pos = result.find("{}", pos)) != std::string::npos && arg_index < arg_strings.size())
            {
                result.replace(pos, 2, arg_strings[arg_index]);
                pos += arg_strings[arg_index].length();
                arg_index++;
            }

            return result;
        }

        template<typename T>
        auto to_string(const T& value) -> std::string
        {
            std::stringstream ss;
            ss << value;
            return ss.str();
        }

        auto set_log_level(LogLevel level) -> void      { m_log_level      = level;  }
        auto enable_console_output(bool enable) -> void { m_console_output = enable; }
        auto enable_file_output(bool enable) -> void    { m_file_output    = enable; }
        auto set_log_file(const std::string& filename) -> void;

        auto get_time_stamp() -> std::string;
        auto get_log_level_string(LogLevel level) -> std::string;
        auto set_console_color(LogLevel level) -> void;
        auto reset_console_color() -> void;

    private:
        LogLevel      m_log_level;
        bool          m_console_output;
        bool          m_file_output;
        std::ofstream m_log_file;
        std::mutex    m_mutex;

        static Ref<Logger> s_logger;
    };
}

// trace/info are debug-only chatter. warnings and errors stay in every build:
// a release build that fails on someone else's machine should still say why.
#if defined(DONUT_DEBUG)
    #define DONUT_TRACE(format, ...)    ::Donut::Logger::trace(format __VA_OPT__(,) __VA_ARGS__)
    #define DONUT_INFO(format, ...)     ::Donut::Logger::info(format __VA_OPT__(,) __VA_ARGS__)
#else
    #define DONUT_TRACE(format, ...)    {}
    #define DONUT_INFO(format, ...)     {}
#endif
#define DONUT_WARN(format, ...)     ::Donut::Logger::warn(format __VA_OPT__(,) __VA_ARGS__)
#define DONUT_ERROR(format, ...)    ::Donut::Logger::error(format __VA_OPT__(,) __VA_ARGS__)
#define DONUT_FATAL(format, ...)    ::Donut::Logger::fatal(format __VA_OPT__(,) __VA_ARGS__)