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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
|
// Copyright Epic Games, Inc. All Rights Reserved.
#include <zencore/zencore.h>
#if ZEN_WITH_TESTS
# include "zenserver-test.h"
# include <zencore/filesystem.h>
# include <zencore/logging.h>
# include <zencore/testing.h>
# include <zenutil/zenserverprocess.h>
namespace zen::tests {
using namespace std::literals;
//////////////////////////////////////////////////////////////////////////
static bool
LogContains(const std::string& Log, std::string_view Needle)
{
return Log.find(Needle) != std::string::npos;
}
static std::string
ReadFileToString(const std::filesystem::path& Path)
{
FileContents Contents = ReadFile(Path);
if (Contents.ErrorCode)
{
return {};
}
IoBuffer Content = Contents.Flatten();
if (!Content)
{
return {};
}
return std::string(static_cast<const char*>(Content.Data()), Content.Size());
}
//////////////////////////////////////////////////////////////////////////
// Verify that a log file is created at the default location (DataDir/logs/zenserver.log)
// even without --abslog. The file must contain "server session id" (logged at INFO
// to all registered loggers during init) and "log starting at" (emitted once a file
// sink is first opened).
TEST_CASE("logging.file.default")
{
const std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
ZenServerInstance Instance(TestEnv);
Instance.SetDataDir(TestDir);
const uint16_t Port = Instance.SpawnServerAndWaitUntilReady();
CHECK_MESSAGE(Port != 0, Instance.GetLogOutput());
Instance.Shutdown();
const std::filesystem::path DefaultLogFile = TestDir / "logs" / "zenserver.log";
CHECK_MESSAGE(std::filesystem::exists(DefaultLogFile), "Default log file was not created");
const std::string FileLog = ReadFileToString(DefaultLogFile);
CHECK_MESSAGE(LogContains(FileLog, "server session id"), FileLog);
CHECK_MESSAGE(LogContains(FileLog, "log starting at"), FileLog);
}
// --quiet sets the console sink level to WARN. The formatted "[info] ..."
// entry written by the default logger's console sink must therefore not appear
// in captured stdout. (The "console" named logger — used by ZEN_CONSOLE_*
// macros — may still emit plain-text messages without a level marker, so we
// check for the absence of the full_formatter "[info]" prefix rather than the
// message text itself.)
TEST_CASE("logging.console.quiet")
{
ZenServerInstance Instance(TestEnv);
Instance.SetDataDir(TestEnv.CreateNewTestDir());
const uint16_t Port = Instance.SpawnServerAndWaitUntilReady("--quiet");
CHECK_MESSAGE(Port != 0, Instance.GetLogOutput());
Instance.Shutdown();
const std::string Log = Instance.GetLogOutput();
CHECK_MESSAGE(!LogContains(Log, "[info] server session id"), Log);
}
// --noconsole removes the stdout sink entirely, so the captured console output
// must not contain any log entries from the logging system.
TEST_CASE("logging.console.disabled")
{
ZenServerInstance Instance(TestEnv);
Instance.SetDataDir(TestEnv.CreateNewTestDir());
const uint16_t Port = Instance.SpawnServerAndWaitUntilReady("--noconsole");
CHECK_MESSAGE(Port != 0, Instance.GetLogOutput());
Instance.Shutdown();
const std::string Log = Instance.GetLogOutput();
CHECK_MESSAGE(!LogContains(Log, "server session id"), Log);
}
// --abslog <path> creates a rotating log file at the specified path.
// The file must contain "server session id" (logged at INFO to all loggers
// during init) and "log starting at" (emitted once a file sink is active).
TEST_CASE("logging.file.basic")
{
const std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
const std::filesystem::path LogFile = TestDir / "test.log";
ZenServerInstance Instance(TestEnv);
Instance.SetDataDir(TestDir);
const std::string LogArg = fmt::format("--abslog {}", LogFile.string());
const uint16_t Port = Instance.SpawnServerAndWaitUntilReady(LogArg);
CHECK_MESSAGE(Port != 0, Instance.GetLogOutput());
Instance.Shutdown();
CHECK_MESSAGE(std::filesystem::exists(LogFile), "Log file was not created");
const std::string FileLog = ReadFileToString(LogFile);
CHECK_MESSAGE(LogContains(FileLog, "server session id"), FileLog);
CHECK_MESSAGE(LogContains(FileLog, "log starting at"), FileLog);
}
// --abslog with a .json extension selects the JSON formatter.
// Each log entry must be a JSON object containing at least the "message"
// and "source" fields.
TEST_CASE("logging.file.json")
{
const std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
const std::filesystem::path LogFile = TestDir / "test.json";
ZenServerInstance Instance(TestEnv);
Instance.SetDataDir(TestDir);
const std::string LogArg = fmt::format("--abslog {}", LogFile.string());
const uint16_t Port = Instance.SpawnServerAndWaitUntilReady(LogArg);
CHECK_MESSAGE(Port != 0, Instance.GetLogOutput());
Instance.Shutdown();
CHECK_MESSAGE(std::filesystem::exists(LogFile), "JSON log file was not created");
const std::string FileLog = ReadFileToString(LogFile);
CHECK_MESSAGE(LogContains(FileLog, "\"message\""), FileLog);
CHECK_MESSAGE(LogContains(FileLog, "\"source\": \"zenserver\""), FileLog);
CHECK_MESSAGE(LogContains(FileLog, "server session id"), FileLog);
}
// --log-id <id> is automatically set to the server instance name in test mode.
// The JSON formatter emits this value as the "id" field, so every entry in a
// .json log file must carry a non-empty "id".
TEST_CASE("logging.log_id")
{
const std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
const std::filesystem::path LogFile = TestDir / "test.json";
ZenServerInstance Instance(TestEnv);
Instance.SetDataDir(TestDir);
const std::string LogArg = fmt::format("--abslog {}", LogFile.string());
const uint16_t Port = Instance.SpawnServerAndWaitUntilReady(LogArg);
CHECK_MESSAGE(Port != 0, Instance.GetLogOutput());
Instance.Shutdown();
CHECK_MESSAGE(std::filesystem::exists(LogFile), "JSON log file was not created");
const std::string FileLog = ReadFileToString(LogFile);
// The JSON formatter writes the log-id as: "id": "<value>",
CHECK_MESSAGE(LogContains(FileLog, "\"id\": \""), FileLog);
}
// --log-warn <logger> raises the level threshold above INFO so that INFO messages
// are filtered. "server session id" is broadcast at INFO to all loggers: it must
// appear in the main file sink (default logger unaffected) but must NOT appear in
// http.log where the http_requests logger now has a WARN threshold.
TEST_CASE("logging.level.warn_suppresses_info")
{
const std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
const std::filesystem::path LogFile = TestDir / "test.log";
ZenServerInstance Instance(TestEnv);
Instance.SetDataDir(TestDir);
const std::string LogArg = fmt::format("--abslog {} --log-warn http_requests", LogFile.string());
const uint16_t Port = Instance.SpawnServerAndWaitUntilReady(LogArg);
CHECK_MESSAGE(Port != 0, Instance.GetLogOutput());
Instance.Shutdown();
CHECK_MESSAGE(std::filesystem::exists(LogFile), "Log file was not created");
const std::string FileLog = ReadFileToString(LogFile);
CHECK_MESSAGE(LogContains(FileLog, "server session id"), FileLog);
const std::filesystem::path HttpLogFile = TestDir / "logs" / "http.log";
CHECK_MESSAGE(std::filesystem::exists(HttpLogFile), "http.log was not created");
const std::string HttpLog = ReadFileToString(HttpLogFile);
CHECK_MESSAGE(!LogContains(HttpLog, "server session id"), HttpLog);
}
// --log-info <logger> sets an explicit INFO threshold. The INFO "server session id"
// broadcast must still land in http.log, confirming that INFO messages are not
// filtered when the logger level is exactly INFO.
TEST_CASE("logging.level.info_allows_info")
{
const std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
const std::filesystem::path LogFile = TestDir / "test.log";
ZenServerInstance Instance(TestEnv);
Instance.SetDataDir(TestDir);
const std::string LogArg = fmt::format("--abslog {} --log-info http_requests", LogFile.string());
const uint16_t Port = Instance.SpawnServerAndWaitUntilReady(LogArg);
CHECK_MESSAGE(Port != 0, Instance.GetLogOutput());
Instance.Shutdown();
const std::filesystem::path HttpLogFile = TestDir / "logs" / "http.log";
CHECK_MESSAGE(std::filesystem::exists(HttpLogFile), "http.log was not created");
const std::string HttpLog = ReadFileToString(HttpLogFile);
CHECK_MESSAGE(LogContains(HttpLog, "server session id"), HttpLog);
}
// --log-off <logger> silences a named logger entirely.
// "server session id" is broadcast at INFO to all registered loggers via
// spdlog::apply_all during init. When the "http_requests" logger is set to
// OFF its dedicated http.log file must not contain that message.
// The main file sink (via --abslog) must be unaffected.
TEST_CASE("logging.level.off_specific_logger")
{
const std::filesystem::path TestDir = TestEnv.CreateNewTestDir();
const std::filesystem::path LogFile = TestDir / "test.log";
ZenServerInstance Instance(TestEnv);
Instance.SetDataDir(TestDir);
const std::string LogArg = fmt::format("--abslog {} --log-off http_requests", LogFile.string());
const uint16_t Port = Instance.SpawnServerAndWaitUntilReady(LogArg);
CHECK_MESSAGE(Port != 0, Instance.GetLogOutput());
Instance.Shutdown();
// Main log file must still have the startup message
CHECK_MESSAGE(std::filesystem::exists(LogFile), "Log file was not created");
const std::string FileLog = ReadFileToString(LogFile);
CHECK_MESSAGE(LogContains(FileLog, "server session id"), FileLog);
// http.log is created by the RotatingFileSink but the logger is OFF, so
// the broadcast "server session id" message must not have been written to it
const std::filesystem::path HttpLogFile = TestDir / "logs" / "http.log";
CHECK_MESSAGE(std::filesystem::exists(HttpLogFile), "http.log was not created");
const std::string HttpLog = ReadFileToString(HttpLogFile);
CHECK_MESSAGE(!LogContains(HttpLog, "server session id"), HttpLog);
}
} // namespace zen::tests
#endif
|