2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file debug.cpp Handling of printing debug messages. */
12 #include "console_func.h"
14 #include "string_func.h"
15 #include "fileio_func.h"
16 #include "settings_type.h"
20 #include "os/windows/win32.h"
23 #include "walltime_func.h"
25 #include "network/network_admin.h"
26 SOCKET _debug_socket
= INVALID_SOCKET
;
28 #include "safeguards.h"
30 /** Element in the queue of debug messages that have to be passed to either NetworkAdminConsole or IConsolePrint.*/
31 struct QueuedDebugItem
{
32 std::string level
; ///< The used debug level.
33 std::string message
; ///< The actual formatted message.
35 std::atomic
<bool> _debug_remote_console
; ///< Whether we need to send data to either NetworkAdminConsole or IConsolePrint.
36 std::mutex _debug_remote_console_mutex
; ///< Mutex to guard the queue of debug messages for either NetworkAdminConsole or IConsolePrint.
37 std::vector
<QueuedDebugItem
> _debug_remote_console_queue
; ///< Queue for debug messages to be passed to NetworkAdminConsole or IConsolePrint.
38 std::vector
<QueuedDebugItem
> _debug_remote_console_queue_spare
; ///< Spare queue to swap with _debug_remote_console_queue.
40 int _debug_driver_level
;
43 int _debug_misc_level
;
45 int _debug_sprite_level
;
46 int _debug_oldloader_level
;
48 int _debug_yapf_level
;
49 int _debug_fontcache_level
;
50 int _debug_script_level
;
52 int _debug_gamelog_level
;
53 int _debug_desync_level
;
54 int _debug_console_level
;
56 int _debug_random_level
;
64 #define DEBUG_LEVEL(x) { #x, &_debug_##x##_level }
65 static const DebugLevel debug_level
[] = {
72 DEBUG_LEVEL(oldloader
),
75 DEBUG_LEVEL(fontcache
),
88 * Dump the available debug facility names in the help text.
89 * @param buf Start address for storing the output.
90 * @param last Last valid address for storing the output.
91 * @return Next free position in the output.
93 char *DumpDebugFacilityNames(char *buf
, char *last
)
96 for (const DebugLevel
*i
= debug_level
; i
!= endof(debug_level
); ++i
) {
98 buf
= strecpy(buf
, "List of debug facility names:\n", last
);
100 buf
= strecpy(buf
, ", ", last
);
103 buf
= strecpy(buf
, i
->name
, last
);
104 length
+= strlen(i
->name
);
107 buf
= strecpy(buf
, "\n\n", last
);
113 * Internal function for outputting the debug line.
114 * @param level Debug category.
115 * @param message The message to output.
117 void DebugPrint(const char *level
, const std::string
&message
)
119 if (_debug_socket
!= INVALID_SOCKET
) {
120 std::string msg
= fmt::format("{}dbg: [{}] {}\n", GetLogPrefix(), level
, message
);
122 /* Prevent sending a message concurrently, as that might cause interleaved messages. */
123 static std::mutex _debug_socket_mutex
;
124 std::lock_guard
<std::mutex
> lock(_debug_socket_mutex
);
126 /* Sending out an error when this fails would be nice, however... the error
127 * would have to be send over this failing socket which won't work. */
128 send(_debug_socket
, msg
.c_str(), (int)msg
.size(), 0);
131 if (strcmp(level
, "desync") == 0) {
132 static FILE *f
= FioFOpenFile("commands-out.log", "wb", AUTOSAVE_DIR
);
133 if (f
== nullptr) return;
135 fprintf(f
, "%s%s\n", GetLogPrefix(), message
.c_str());
138 } else if (strcmp(level
, "random") == 0) {
139 static FILE *f
= FioFOpenFile("random-out.log", "wb", AUTOSAVE_DIR
);
140 if (f
== nullptr) return;
142 fprintf(f
, "%s\n", message
.c_str());
146 std::string msg
= fmt::format("{}dbg: [{}] {}\n", GetLogPrefix(), level
, message
);
147 fputs(msg
.c_str(), stderr
);
149 if (_debug_remote_console
.load()) {
150 /* Only add to the queue when there is at least one consumer of the data. */
151 std::lock_guard
<std::mutex
> lock(_debug_remote_console_mutex
);
152 _debug_remote_console_queue
.push_back({ level
, message
});
158 * Set debugging levels by parsing the text in \a s.
159 * For setting individual levels a string like \c "net=3,grf=6" should be used.
160 * If the string starts with a number, the number is used as global debugging level.
161 * @param s Text describing the wanted debugging levels.
162 * @param error_func The function to call if a parse error occurs.
164 void SetDebugString(const char *s
, void (*error_func
)(const char *))
170 /* Store planned changes into map during parse */
171 std::map
<const char *, int> new_levels
;
173 /* Global debugging level? */
174 if (*s
>= '0' && *s
<= '9') {
177 v
= strtoul(s
, &end
, 0);
180 for (i
= debug_level
; i
!= endof(debug_level
); ++i
) {
181 new_levels
[i
->name
] = v
;
185 /* Individual levels */
187 /* skip delimiters */
188 while (*s
== ' ' || *s
== ',' || *s
== '\t') s
++;
189 if (*s
== '\0') break;
192 while (*s
>= 'a' && *s
<= 'z') s
++;
194 /* check debugging levels */
195 const DebugLevel
*found
= nullptr;
196 for (const DebugLevel
*i
= debug_level
; i
!= endof(debug_level
); ++i
) {
197 if (s
== t
+ strlen(i
->name
) && strncmp(t
, i
->name
, s
- t
) == 0) {
204 v
= strtoul(s
, &end
, 0);
206 if (found
!= nullptr) {
207 new_levels
[found
->name
] = v
;
209 std::string error_string
= fmt::format("Unknown debug level '{}'", std::string(t
, s
- t
));
210 error_func(error_string
.c_str());
215 /* Apply the changes after parse is successful */
216 for (const DebugLevel
*i
= debug_level
; i
!= endof(debug_level
); ++i
) {
217 const auto &nl
= new_levels
.find(i
->name
);
218 if (nl
!= new_levels
.end()) {
219 *i
->level
= nl
->second
;
225 * Print out the current debug-level.
226 * Just return a string with the values of all the debug categories.
227 * @return string with debug-levels
229 const char *GetDebugString()
232 static char dbgstr
[150];
235 memset(dbgstr
, 0, sizeof(dbgstr
));
237 seprintf(dbgstr
, lastof(dbgstr
), "%s=%d", i
->name
, *i
->level
);
239 for (i
++; i
!= endof(debug_level
); i
++) {
240 seprintf(dbgval
, lastof(dbgval
), ", %s=%d", i
->name
, *i
->level
);
241 strecat(dbgstr
, dbgval
, lastof(dbgstr
));
248 * Get the prefix for logs; if show_date_in_logs is enabled it returns
249 * the date, otherwise it returns nothing.
250 * @return the prefix for logs (do not free), never nullptr
252 const char *GetLogPrefix()
254 static char _log_prefix
[24];
255 if (_settings_client
.gui
.show_date_in_logs
) {
256 LocalTime::Format(_log_prefix
, lastof(_log_prefix
), "[%Y-%m-%d %H:%M:%S] ");
264 * Send the queued Debug messages to either NetworkAdminConsole or IConsolePrint from the
265 * GameLoop thread to prevent concurrent accesses to both the NetworkAdmin's packet queue
266 * as well as IConsolePrint's buffers.
268 * This is to be called from the GameLoop thread.
270 void DebugSendRemoteMessages()
272 if (!_debug_remote_console
.load()) return;
275 std::lock_guard
<std::mutex
> lock(_debug_remote_console_mutex
);
276 std::swap(_debug_remote_console_queue
, _debug_remote_console_queue_spare
);
279 for (auto &item
: _debug_remote_console_queue_spare
) {
280 NetworkAdminConsole(item
.level
, item
.message
);
281 if (_settings_client
.gui
.developer
>= 2) IConsolePrint(CC_DEBUG
, "dbg: [{}] {}", item
.level
, item
.message
);
284 _debug_remote_console_queue_spare
.clear();
288 * Reconsider whether we need to send debug messages to either NetworkAdminConsole
289 * or IConsolePrint. The former is when they have enabled console handling whereas
290 * the latter depends on the gui.developer setting's value.
292 * This is to be called from the GameLoop thread.
294 void DebugReconsiderSendRemoteMessages()
296 bool enable
= _settings_client
.gui
.developer
>= 2;
298 for (ServerNetworkAdminSocketHandler
*as
: ServerNetworkAdminSocketHandler::IterateActive()) {
299 if (as
->update_frequency
[ADMIN_UPDATE_CONSOLE
] & ADMIN_FREQUENCY_AUTOMATIC
) {
305 _debug_remote_console
.store(enable
);