Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/core/io/logger.cpp
9973 views
1
/**************************************************************************/
2
/* logger.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "logger.h"
32
33
#include "core/core_globals.h"
34
#include "core/io/dir_access.h"
35
#include "core/os/time.h"
36
#include "core/templates/rb_set.h"
37
38
#include "modules/modules_enabled.gen.h" // For regex.
39
40
#ifdef MODULE_REGEX_ENABLED
41
#include "modules/regex/regex.h"
42
#endif // MODULE_REGEX_ENABLED
43
44
#if defined(MINGW_ENABLED) || defined(_MSC_VER)
45
#define sprintf sprintf_s
46
#endif
47
48
bool Logger::should_log(bool p_err) {
49
return (!p_err || CoreGlobals::print_error_enabled) && (p_err || CoreGlobals::print_line_enabled);
50
}
51
52
void Logger::set_flush_stdout_on_print(bool value) {
53
_flush_stdout_on_print = value;
54
}
55
56
void Logger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const Vector<Ref<ScriptBacktrace>> &p_script_backtraces) {
57
if (!should_log(true)) {
58
return;
59
}
60
61
const char *err_type = error_type_string(p_type);
62
63
const char *err_details;
64
if (p_rationale && *p_rationale) {
65
err_details = p_rationale;
66
} else {
67
err_details = p_code;
68
}
69
70
logf_error("%s: %s\n", err_type, err_details);
71
logf_error(" at: %s (%s:%i)\n", p_function, p_file, p_line);
72
73
for (const Ref<ScriptBacktrace> &backtrace : p_script_backtraces) {
74
if (!backtrace->is_empty()) {
75
logf_error("%s\n", backtrace->format(3).utf8().get_data());
76
}
77
}
78
}
79
80
void Logger::logf(const char *p_format, ...) {
81
if (!should_log(false)) {
82
return;
83
}
84
85
va_list argp;
86
va_start(argp, p_format);
87
88
logv(p_format, argp, false);
89
90
va_end(argp);
91
}
92
93
void Logger::logf_error(const char *p_format, ...) {
94
if (!should_log(true)) {
95
return;
96
}
97
98
va_list argp;
99
va_start(argp, p_format);
100
101
logv(p_format, argp, true);
102
103
va_end(argp);
104
}
105
106
void RotatedFileLogger::clear_old_backups() {
107
int max_backups = max_files - 1; // -1 for the current file
108
109
String basename = base_path.get_file().get_basename();
110
String extension = base_path.get_extension();
111
112
Ref<DirAccess> da = DirAccess::open(base_path.get_base_dir());
113
if (da.is_null()) {
114
return;
115
}
116
117
da->list_dir_begin();
118
String f = da->get_next();
119
// backups is a RBSet because it guarantees that iterating on it is done in sorted order.
120
// RotatedFileLogger depends on this behavior to delete the oldest log file first.
121
RBSet<String> backups;
122
while (!f.is_empty()) {
123
if (!da->current_is_dir() && f.begins_with(basename) && f.get_extension() == extension && f != base_path.get_file()) {
124
backups.insert(f);
125
}
126
f = da->get_next();
127
}
128
da->list_dir_end();
129
130
if (backups.size() > max_backups) {
131
// since backups are appended with timestamp and Set iterates them in sorted order,
132
// first backups are the oldest
133
int to_delete = backups.size() - max_backups;
134
for (RBSet<String>::Element *E = backups.front(); E && to_delete > 0; E = E->next(), --to_delete) {
135
da->remove(E->get());
136
}
137
}
138
}
139
140
void RotatedFileLogger::rotate_file() {
141
file.unref();
142
143
if (FileAccess::exists(base_path)) {
144
if (max_files > 1) {
145
String timestamp = Time::get_singleton()->get_datetime_string_from_system().replace_char(':', '.');
146
String backup_name = base_path.get_basename() + timestamp;
147
if (!base_path.get_extension().is_empty()) {
148
backup_name += "." + base_path.get_extension();
149
}
150
151
Ref<DirAccess> da = DirAccess::open(base_path.get_base_dir());
152
if (da.is_valid()) {
153
da->copy(base_path, backup_name);
154
}
155
clear_old_backups();
156
}
157
} else {
158
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_USERDATA);
159
if (da.is_valid()) {
160
da->make_dir_recursive(base_path.get_base_dir());
161
}
162
}
163
164
file = FileAccess::open(base_path, FileAccess::WRITE);
165
file->detach_from_objectdb(); // Note: This FileAccess instance will exist longer than ObjectDB, therefore can't be registered in ObjectDB.
166
}
167
168
RotatedFileLogger::RotatedFileLogger(const String &p_base_path, int p_max_files) :
169
base_path(p_base_path.simplify_path()),
170
max_files(p_max_files > 0 ? p_max_files : 1) {
171
rotate_file();
172
173
#ifdef MODULE_REGEX_ENABLED
174
strip_ansi_regex.instantiate();
175
strip_ansi_regex->detach_from_objectdb(); // Note: This RegEx instance will exist longer than ObjectDB, therefore can't be registered in ObjectDB.
176
strip_ansi_regex->compile("\u001b\\[((?:\\d|;)*)([a-zA-Z])");
177
#endif // MODULE_REGEX_ENABLED
178
}
179
180
void RotatedFileLogger::logv(const char *p_format, va_list p_list, bool p_err) {
181
if (!should_log(p_err)) {
182
return;
183
}
184
185
if (file.is_valid()) {
186
const int static_buf_size = 512;
187
char static_buf[static_buf_size];
188
char *buf = static_buf;
189
va_list list_copy;
190
va_copy(list_copy, p_list);
191
int len = vsnprintf(buf, static_buf_size, p_format, p_list);
192
if (len >= static_buf_size) {
193
buf = (char *)Memory::alloc_static(len + 1);
194
vsnprintf(buf, len + 1, p_format, list_copy);
195
}
196
va_end(list_copy);
197
198
#ifdef MODULE_REGEX_ENABLED
199
// Strip ANSI escape codes (such as those inserted by `print_rich()`)
200
// before writing to file, as text editors cannot display those
201
// correctly.
202
file->store_string(strip_ansi_regex->sub(String::utf8(buf), "", true));
203
#else
204
file->store_buffer((uint8_t *)buf, len);
205
#endif // MODULE_REGEX_ENABLED
206
207
if (len >= static_buf_size) {
208
Memory::free_static(buf);
209
}
210
211
if (p_err || _flush_stdout_on_print) {
212
// Don't always flush when printing stdout to avoid performance
213
// issues when `print()` is spammed in release builds.
214
file->flush();
215
}
216
}
217
}
218
219
void StdLogger::logv(const char *p_format, va_list p_list, bool p_err) {
220
if (!should_log(p_err)) {
221
return;
222
}
223
224
if (p_err) {
225
vfprintf(stderr, p_format, p_list);
226
} else {
227
vprintf(p_format, p_list);
228
if (_flush_stdout_on_print) {
229
// Don't always flush when printing stdout to avoid performance
230
// issues when `print()` is spammed in release builds.
231
fflush(stdout);
232
}
233
}
234
}
235
236
CompositeLogger::CompositeLogger(const Vector<Logger *> &p_loggers) :
237
loggers(p_loggers) {
238
}
239
240
void CompositeLogger::logv(const char *p_format, va_list p_list, bool p_err) {
241
if (!should_log(p_err)) {
242
return;
243
}
244
245
for (int i = 0; i < loggers.size(); ++i) {
246
va_list list_copy;
247
va_copy(list_copy, p_list);
248
loggers[i]->logv(p_format, list_copy, p_err);
249
va_end(list_copy);
250
}
251
}
252
253
void CompositeLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type, const Vector<Ref<ScriptBacktrace>> &p_script_backtraces) {
254
if (!should_log(true)) {
255
return;
256
}
257
258
for (int i = 0; i < loggers.size(); ++i) {
259
loggers[i]->log_error(p_function, p_file, p_line, p_code, p_rationale, p_editor_notify, p_type, p_script_backtraces);
260
}
261
}
262
263
void CompositeLogger::add_logger(Logger *p_logger) {
264
loggers.push_back(p_logger);
265
}
266
267
CompositeLogger::~CompositeLogger() {
268
for (int i = 0; i < loggers.size(); ++i) {
269
memdelete(loggers[i]);
270
}
271
}
272
273