1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* Log file handling */
18
19#pragma once
20
21#ifndef C4ENGINE
22#error C4Log.h must not be included in non-C4ENGINE builds
23#endif
24
25#include "C4ResStrTable.h"
26
27#include "StdAdaptors.h"
28#include <StdBuf.h>
29#include <StdCompiler.h>
30
31#include <mutex>
32#include <span>
33
34#include <fmt/printf.h>
35#include <spdlog/spdlog.h>
36#include <spdlog/pattern_formatter.h>
37#include <spdlog/sinks/base_sink.h>
38
39class C4ConfigLogging;
40
41namespace C4LoggerConfig
42{
43 template<typename T>
44 struct Defaults
45 {
46 };
47
48 template<typename T>
49 struct Name;
50
51#define C4LOGGERCONFIG_NAME_TYPE(type) template<> struct C4LoggerConfig::Name<type> { static constexpr auto Value = #type; }
52
53 template<typename T>
54 concept HasName = requires { Name<T>::Value; };
55
56 struct ConfigBase
57 {
58 spdlog::level::level_enum LogLevel;
59 spdlog::level::level_enum GuiLogLevel;
60 bool ShowLoggerNameInGui;
61
62 constexpr bool operator==(const ConfigBase &other) const noexcept = default;
63 };
64
65 template<typename T>
66 struct Config : ConfigBase
67 {
68 private:
69 struct Marker
70 {
71 explicit Marker() = default;
72 };
73
74 struct ConfigDefaults
75 {
76 static constexpr spdlog::level::level_enum LogLevel{spdlog::level::trace};
77 static constexpr spdlog::level::level_enum GuiLogLevel{spdlog::level::n_levels};
78 static constexpr bool ShowLoggerNameInGui{true};
79 };
80
81 private:
82 Config() = default;
83 Config(const spdlog::level::level_enum logLevel, const spdlog::level::level_enum guiLogLevel, const bool showLoggerNameInGui)
84 : ConfigBase{.LogLevel: logLevel, .GuiLogLevel: guiLogLevel, .ShowLoggerNameInGui: showLoggerNameInGui}
85 {
86 }
87
88 public:
89 void CompileFunc(StdCompiler *const comp) requires HasName<T>
90 {
91 comp->Value(mkNamingAdapt(mkParAdapt(*this, Marker{}), C4LoggerConfig::Name<T>::Value, Default()));
92 }
93
94 void CompileFunc(StdCompiler *const comp, Marker) requires HasName<T>
95 {
96 comp->Value(mkNamingAdapt(LogLevel, "LogLevel", DefaultLogLevel()));
97 comp->Value(mkNamingAdapt(GuiLogLevel, "GuiLogLevel", DefaultGuiLogLevel()));
98 comp->Value(mkNamingAdapt(ShowLoggerNameInGui, "ShowLoggerNameInGui", DefaultShowLoggerNameInGui()));
99 }
100
101 constexpr bool operator==(const Config &other) const noexcept = default;
102
103 private:
104 static consteval spdlog::level::level_enum DefaultLogLevel() noexcept
105 {
106 if constexpr (requires { Defaults<T>::LogLevel; })
107 {
108 return Defaults<T>::LogLevel;
109 }
110 else
111 {
112 return ConfigDefaults::LogLevel;
113 }
114 }
115
116 static consteval spdlog::level::level_enum DefaultGuiLogLevel() noexcept
117 {
118 if constexpr (requires { Defaults<T>::GuiLogLevel; })
119 {
120 return Defaults<T>::GuiLogLevel;
121 }
122 else
123 {
124 return ConfigDefaults::GuiLogLevel;
125 }
126 }
127
128 static consteval bool DefaultShowLoggerNameInGui() noexcept
129 {
130 if constexpr (requires { Defaults<T>::ShowLoggerNameInGui; })
131 {
132 return Defaults<T>::ShowLoggerNameInGui;
133 }
134 else
135 {
136 return ConfigDefaults::ShowLoggerNameInGui;
137 }
138 }
139
140 static constexpr Config<T> Default() noexcept
141 {
142 return {DefaultLogLevel(), DefaultGuiLogLevel(), DefaultShowLoggerNameInGui()};
143 }
144
145 friend class ::C4ConfigLogging;
146 };
147};
148
149struct C4LogSystemCreateLoggerOptions
150{
151 spdlog::level::level_enum GuiLogLevel{spdlog::level::n_levels};
152 bool ShowLoggerNameInGui{false};
153};
154
155class C4LogSystem
156{
157 class LogSink : public spdlog::sinks::base_sink<std::mutex>
158 {
159 public:
160 LogSink(std::unique_ptr<spdlog::formatter> formatter);
161 ~LogSink();
162
163 LogSink(const LogSink &) = delete;
164 LogSink &operator=(const LogSink &) = delete;
165
166 LogSink(LogSink &&) = delete;
167 LogSink &operator=(LogSink &&) = delete;
168
169 public:
170 int GetFD() const noexcept { return fileno(stream: file); }
171
172 protected:
173 void sink_it_(const spdlog::details::log_msg &msg) override;
174 void flush_() override;
175
176 private:
177 FILE *file{nullptr};
178 };
179
180 class GuiSink : public spdlog::sinks::base_sink<spdlog::details::null_mutex>, public std::enable_shared_from_this<GuiSink>
181 {
182 public:
183 GuiSink(std::unique_ptr<spdlog::formatter> formatter) : base_sink{std::move(formatter)} {}
184 GuiSink(spdlog::level::level_enum level, bool showLoggerNameInGui);
185
186 protected:
187 void sink_it_(const spdlog::details::log_msg &msg) override;
188 void flush_() override {}
189
190 private:
191 void DoLog(const std::string &message);
192 };
193
194 class RingbufferSink : public spdlog::sinks::base_sink<std::mutex>
195 {
196 public:
197 RingbufferSink(std::unique_ptr<spdlog::formatter> formatter, const std::size_t size) : base_sink{std::move(formatter)}, size{size}, ringbuffer{size} {}
198
199 public:
200 std::vector<std::string> TakeMessages();
201 void Clear();
202
203 protected:
204 void sink_it_(const spdlog::details::log_msg &msg) override;
205 void flush_() override {}
206
207 private:
208 std::size_t size;
209 spdlog::details::circular_q<spdlog::details::log_msg_buffer> ringbuffer;
210 };
211
212 class ClonkToUtf8Sink : public spdlog::sinks::sink // Not inheriting from base_sink as we don't need the extra formatter
213 {
214 public:
215 ClonkToUtf8Sink(std::initializer_list<spdlog::sink_ptr> sinks) : sinks{sinks} {}
216
217 protected:
218 void log(const spdlog::details::log_msg &msg) override;
219 void flush() override;
220 void set_pattern(const std::string &) override {}
221 void set_formatter(std::unique_ptr<spdlog::formatter>) override {}
222
223 private:
224 std::vector<spdlog::sink_ptr> sinks;
225 };
226
227public:
228 C4LogSystem();
229
230 C4LogSystem(const C4LogSystem &) = delete;
231 C4LogSystem &operator=(const C4LogSystem &) = delete;
232
233 C4LogSystem(C4LogSystem &&) = delete;
234 C4LogSystem &operator=(C4LogSystem &&) = delete;
235
236public:
237 void OpenLog(bool verbose);
238 int GetLogFD() const noexcept { return clonkLogFD; }
239
240public:
241 const std::shared_ptr<spdlog::logger> &GetLogger() const noexcept { return logger; }
242 const std::shared_ptr<spdlog::logger> &GetLoggerSilent() const noexcept { return loggerSilent; }
243 const std::shared_ptr<spdlog::logger> &GetLoggerDebug() const noexcept { return loggerDebug; }
244
245 void AddFatalError(std::string message);
246 void ResetFatalErrors();
247 std::string GetFatalErrorString();
248 std::vector<std::string> GetRingbufferLogEntries();
249 void ClearRingbuffer();
250
251 template<typename T>
252 std::shared_ptr<spdlog::logger> CreateLogger(C4LoggerConfig::Config<T> &config)
253 {
254 return CreateLogger(C4LoggerConfig::Name<T>::Value, config);
255 }
256
257
258 template<typename T>
259 std::shared_ptr<spdlog::logger> CreateLoggerWithDifferentName(C4LoggerConfig::Config<T> &config, std::string name)
260 {
261 return CreateLogger(std::move(name), config);
262 }
263
264 template<typename T>
265 std::shared_ptr<spdlog::logger> GetOrCreate(C4LoggerConfig::Config<T> &config)
266 {
267 return GetOrCreate(C4LoggerConfig::Name<T>::Value, config);
268 }
269
270 void EnableDebugLog(bool enable);
271
272#ifdef _WIN32
273 void SetConsoleInputCharset(std::int32_t charset);
274#endif
275
276private:
277 spdlog::level::level_enum AdjustLevelIfVerbose(const spdlog::level::level_enum level) const noexcept;
278 std::shared_ptr<spdlog::logger> CreateLogger(std::string name, C4LoggerConfig::ConfigBase config);
279 std::shared_ptr<spdlog::logger> GetOrCreate(std::string name, C4LoggerConfig::ConfigBase config);
280
281private:
282 std::unique_ptr<spdlog::pattern_formatter> defaultPatternFormatter;
283 std::shared_ptr<spdlog::logger> logger;
284 std::shared_ptr<spdlog::logger> loggerSilent;
285 std::shared_ptr<spdlog::logger> loggerDebug;
286
287#ifdef _WIN32
288 spdlog::sink_ptr debugSink;
289#endif
290 std::shared_ptr<ClonkToUtf8Sink> clonkToUtf8Sink;
291 std::shared_ptr<GuiSink> loggerSilentGuiSink;
292
293 std::shared_ptr<GuiSink> loggerDebugGuiSink;
294 std::shared_ptr<RingbufferSink> ringbufferSink;
295
296 int clonkLogFD{-1};
297 bool verbose{false};
298 std::vector<std::string> fatalErrors;
299 std::mutex getOrCreateMutex;
300};
301
302void LogNTr(spdlog::level::level_enum level, std::string_view message);
303
304inline void LogNTr(const std::string_view message)
305{
306 LogNTr(level: spdlog::level::info, message);
307}
308
309template<typename... Args>
310void LogNTr(const spdlog::level::level_enum level, std::format_string<Args...> fmt, Args &&...args)
311{
312 LogNTr(level, std::format(fmt, std::forward<Args>(args)...));
313}
314
315template<typename... Args>
316void LogNTr(const std::format_string<Args...> fmt, Args &&...args)
317{
318 LogNTr(spdlog::level::info, std::format(fmt, std::forward<Args>(args)...));
319}
320
321template<typename... Args>
322void Log(const spdlog::level::level_enum level, const C4ResStrTableKeyFormat<std::type_identity_t<Args>...> id, Args &&...args)
323{
324 LogNTr(level, LoadResStr(id, std::forward<Args>(args)...));
325}
326
327template<typename... Args>
328void Log(const C4ResStrTableKeyFormat<std::type_identity_t<Args>...> id, Args &&...args)
329{
330 LogNTr(spdlog::level::info, LoadResStr(id, std::forward<Args>(args)...));
331}
332
333bool DebugLog(spdlog::level::level_enum level, std::string_view message);
334
335inline bool DebugLog(std::string_view message)
336{
337 return DebugLog(level: spdlog::level::info, message);
338}
339
340template<typename... Args>
341bool DebugLog(const std::format_string<Args...> fmt, Args &&... args)
342{
343 return DebugLog(spdlog::level::info, std::format(fmt, std::forward<Args>(args)...));
344}
345
346template<typename... Args>
347bool DebugLog(const spdlog::level::level_enum level, const std::format_string<Args...> fmt, Args &&... args)
348{
349 return DebugLog(level, std::format(fmt, std::forward<Args>(args)...));
350}
351
352void LogFatalNTr(std::string message); // log message and store it as a fatal error
353
354template<typename... Args>
355void LogFatalNTr(const std::format_string<Args...> fmt, Args &&...args)
356{
357 LogFatalNTr(std::format(fmt, std::forward<Args>(args)...));
358}
359
360template<typename... Args>
361void LogFatal(const C4ResStrTableKeyFormat<std::type_identity_t<Args>...> id, Args &&...args)
362{
363 LogFatalNTr(LoadResStr(id, std::forward<Args>(args)...));
364}
365
366// Used to print a backtrace after a crash
367int GetLogFD();
368
369inline void CompileFunc(spdlog::level::level_enum &level, StdCompiler *const comp)
370{
371 constexpr StdEnumEntry<spdlog::level::level_enum> Values[]
372 {
373 {.Name: "trace", .Val: spdlog::level::trace},
374 {.Name: "debug", .Val: spdlog::level::debug},
375 {.Name: "info", .Val: spdlog::level::info},
376 {.Name: "warn", .Val: spdlog::level::warn},
377 {.Name: "error", .Val: spdlog::level::err},
378 {.Name: "critical", .Val: spdlog::level::critical},
379 {.Name: "off", .Val: spdlog::level::off},
380 };
381
382 comp->Value(rStruct: mkEnumAdaptT<std::int32_t>(rVal&: level, pNames: Values));
383}
384