1 | /* |
2 | Simple DirectMedia Layer |
3 | Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org> |
4 | |
5 | This software is provided 'as-is', without any express or implied |
6 | warranty. In no event will the authors be held liable for any damages |
7 | arising from the use of this software. |
8 | |
9 | Permission is granted to anyone to use this software for any purpose, |
10 | including commercial applications, and to alter it and redistribute it |
11 | freely, subject to the following restrictions: |
12 | |
13 | 1. The origin of this software must not be misrepresented; you must not |
14 | claim that you wrote the original software. If you use this software |
15 | in a product, an acknowledgment in the product documentation would be |
16 | appreciated but is not required. |
17 | 2. Altered source versions must be plainly marked as such, and must not be |
18 | misrepresented as being the original software. |
19 | 3. This notice may not be removed or altered from any source distribution. |
20 | */ |
21 | #include "./SDL_internal.h" |
22 | |
23 | #if defined(__WIN32__) || defined(__WINRT__) |
24 | #include "core/windows/SDL_windows.h" |
25 | #endif |
26 | |
27 | /* Simple log messages in SDL */ |
28 | |
29 | #include "SDL_error.h" |
30 | #include "SDL_log.h" |
31 | |
32 | #if HAVE_STDIO_H |
33 | #include <stdio.h> |
34 | #endif |
35 | |
36 | #if defined(__ANDROID__) |
37 | #include <android/log.h> |
38 | #endif |
39 | |
40 | #define DEFAULT_PRIORITY SDL_LOG_PRIORITY_CRITICAL |
41 | #define DEFAULT_ASSERT_PRIORITY SDL_LOG_PRIORITY_WARN |
42 | #define DEFAULT_APPLICATION_PRIORITY SDL_LOG_PRIORITY_INFO |
43 | #define DEFAULT_TEST_PRIORITY SDL_LOG_PRIORITY_VERBOSE |
44 | |
45 | typedef struct SDL_LogLevel |
46 | { |
47 | int category; |
48 | SDL_LogPriority priority; |
49 | struct SDL_LogLevel *next; |
50 | } SDL_LogLevel; |
51 | |
52 | /* The default log output function */ |
53 | static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority, const char *message); |
54 | |
55 | static SDL_LogLevel *SDL_loglevels; |
56 | static SDL_LogPriority SDL_default_priority = DEFAULT_PRIORITY; |
57 | static SDL_LogPriority SDL_assert_priority = DEFAULT_ASSERT_PRIORITY; |
58 | static SDL_LogPriority SDL_application_priority = DEFAULT_APPLICATION_PRIORITY; |
59 | static SDL_LogPriority SDL_test_priority = DEFAULT_TEST_PRIORITY; |
60 | static SDL_LogOutputFunction SDL_log_function = SDL_LogOutput; |
61 | static void *SDL_log_userdata = NULL; |
62 | |
63 | static const char *SDL_priority_prefixes[SDL_NUM_LOG_PRIORITIES] = { |
64 | NULL, |
65 | "VERBOSE" , |
66 | "DEBUG" , |
67 | "INFO" , |
68 | "WARN" , |
69 | "ERROR" , |
70 | "CRITICAL" |
71 | }; |
72 | |
73 | #ifdef __ANDROID__ |
74 | static const char *SDL_category_prefixes[SDL_LOG_CATEGORY_RESERVED1] = { |
75 | "APP" , |
76 | "ERROR" , |
77 | "SYSTEM" , |
78 | "AUDIO" , |
79 | "VIDEO" , |
80 | "RENDER" , |
81 | "INPUT" |
82 | }; |
83 | |
84 | static int SDL_android_priority[SDL_NUM_LOG_PRIORITIES] = { |
85 | ANDROID_LOG_UNKNOWN, |
86 | ANDROID_LOG_VERBOSE, |
87 | ANDROID_LOG_DEBUG, |
88 | ANDROID_LOG_INFO, |
89 | ANDROID_LOG_WARN, |
90 | ANDROID_LOG_ERROR, |
91 | ANDROID_LOG_FATAL |
92 | }; |
93 | #endif /* __ANDROID__ */ |
94 | |
95 | |
96 | void |
97 | SDL_LogSetAllPriority(SDL_LogPriority priority) |
98 | { |
99 | SDL_LogLevel *entry; |
100 | |
101 | for (entry = SDL_loglevels; entry; entry = entry->next) { |
102 | entry->priority = priority; |
103 | } |
104 | SDL_default_priority = priority; |
105 | SDL_assert_priority = priority; |
106 | SDL_application_priority = priority; |
107 | } |
108 | |
109 | void |
110 | SDL_LogSetPriority(int category, SDL_LogPriority priority) |
111 | { |
112 | SDL_LogLevel *entry; |
113 | |
114 | for (entry = SDL_loglevels; entry; entry = entry->next) { |
115 | if (entry->category == category) { |
116 | entry->priority = priority; |
117 | return; |
118 | } |
119 | } |
120 | |
121 | /* Create a new entry */ |
122 | entry = (SDL_LogLevel *)SDL_malloc(sizeof(*entry)); |
123 | if (entry) { |
124 | entry->category = category; |
125 | entry->priority = priority; |
126 | entry->next = SDL_loglevels; |
127 | SDL_loglevels = entry; |
128 | } |
129 | } |
130 | |
131 | SDL_LogPriority |
132 | SDL_LogGetPriority(int category) |
133 | { |
134 | SDL_LogLevel *entry; |
135 | |
136 | for (entry = SDL_loglevels; entry; entry = entry->next) { |
137 | if (entry->category == category) { |
138 | return entry->priority; |
139 | } |
140 | } |
141 | |
142 | if (category == SDL_LOG_CATEGORY_TEST) { |
143 | return SDL_test_priority; |
144 | } else if (category == SDL_LOG_CATEGORY_APPLICATION) { |
145 | return SDL_application_priority; |
146 | } else if (category == SDL_LOG_CATEGORY_ASSERT) { |
147 | return SDL_assert_priority; |
148 | } else { |
149 | return SDL_default_priority; |
150 | } |
151 | } |
152 | |
153 | void |
154 | SDL_LogResetPriorities(void) |
155 | { |
156 | SDL_LogLevel *entry; |
157 | |
158 | while (SDL_loglevels) { |
159 | entry = SDL_loglevels; |
160 | SDL_loglevels = entry->next; |
161 | SDL_free(entry); |
162 | } |
163 | |
164 | SDL_default_priority = DEFAULT_PRIORITY; |
165 | SDL_assert_priority = DEFAULT_ASSERT_PRIORITY; |
166 | SDL_application_priority = DEFAULT_APPLICATION_PRIORITY; |
167 | SDL_test_priority = DEFAULT_TEST_PRIORITY; |
168 | } |
169 | |
170 | void |
171 | SDL_Log(SDL_PRINTF_FORMAT_STRING const char *fmt, ...) |
172 | { |
173 | va_list ap; |
174 | |
175 | va_start(ap, fmt); |
176 | SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, ap); |
177 | va_end(ap); |
178 | } |
179 | |
180 | void |
181 | SDL_LogVerbose(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) |
182 | { |
183 | va_list ap; |
184 | |
185 | va_start(ap, fmt); |
186 | SDL_LogMessageV(category, SDL_LOG_PRIORITY_VERBOSE, fmt, ap); |
187 | va_end(ap); |
188 | } |
189 | |
190 | void |
191 | SDL_LogDebug(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) |
192 | { |
193 | va_list ap; |
194 | |
195 | va_start(ap, fmt); |
196 | SDL_LogMessageV(category, SDL_LOG_PRIORITY_DEBUG, fmt, ap); |
197 | va_end(ap); |
198 | } |
199 | |
200 | void |
201 | SDL_LogInfo(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) |
202 | { |
203 | va_list ap; |
204 | |
205 | va_start(ap, fmt); |
206 | SDL_LogMessageV(category, SDL_LOG_PRIORITY_INFO, fmt, ap); |
207 | va_end(ap); |
208 | } |
209 | |
210 | void |
211 | SDL_LogWarn(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) |
212 | { |
213 | va_list ap; |
214 | |
215 | va_start(ap, fmt); |
216 | SDL_LogMessageV(category, SDL_LOG_PRIORITY_WARN, fmt, ap); |
217 | va_end(ap); |
218 | } |
219 | |
220 | void |
221 | SDL_LogError(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) |
222 | { |
223 | va_list ap; |
224 | |
225 | va_start(ap, fmt); |
226 | SDL_LogMessageV(category, SDL_LOG_PRIORITY_ERROR, fmt, ap); |
227 | va_end(ap); |
228 | } |
229 | |
230 | void |
231 | SDL_LogCritical(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) |
232 | { |
233 | va_list ap; |
234 | |
235 | va_start(ap, fmt); |
236 | SDL_LogMessageV(category, SDL_LOG_PRIORITY_CRITICAL, fmt, ap); |
237 | va_end(ap); |
238 | } |
239 | |
240 | void |
241 | SDL_LogMessage(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) |
242 | { |
243 | va_list ap; |
244 | |
245 | va_start(ap, fmt); |
246 | SDL_LogMessageV(category, priority, fmt, ap); |
247 | va_end(ap); |
248 | } |
249 | |
250 | #ifdef __ANDROID__ |
251 | static const char * |
252 | GetCategoryPrefix(int category) |
253 | { |
254 | if (category < SDL_LOG_CATEGORY_RESERVED1) { |
255 | return SDL_category_prefixes[category]; |
256 | } |
257 | if (category < SDL_LOG_CATEGORY_CUSTOM) { |
258 | return "RESERVED" ; |
259 | } |
260 | return "CUSTOM" ; |
261 | } |
262 | #endif /* __ANDROID__ */ |
263 | |
264 | void |
265 | SDL_LogMessageV(int category, SDL_LogPriority priority, const char *fmt, va_list ap) |
266 | { |
267 | char *message; |
268 | size_t len; |
269 | |
270 | /* Nothing to do if we don't have an output function */ |
271 | if (!SDL_log_function) { |
272 | return; |
273 | } |
274 | |
275 | /* Make sure we don't exceed array bounds */ |
276 | if ((int)priority < 0 || priority >= SDL_NUM_LOG_PRIORITIES) { |
277 | return; |
278 | } |
279 | |
280 | /* See if we want to do anything with this message */ |
281 | if (priority < SDL_LogGetPriority(category)) { |
282 | return; |
283 | } |
284 | |
285 | /* !!! FIXME: why not just "char message[SDL_MAX_LOG_MESSAGE];" ? */ |
286 | message = SDL_stack_alloc(char, SDL_MAX_LOG_MESSAGE); |
287 | if (!message) { |
288 | return; |
289 | } |
290 | |
291 | SDL_vsnprintf(message, SDL_MAX_LOG_MESSAGE, fmt, ap); |
292 | |
293 | /* Chop off final endline. */ |
294 | len = SDL_strlen(message); |
295 | if ((len > 0) && (message[len-1] == '\n')) { |
296 | message[--len] = '\0'; |
297 | if ((len > 0) && (message[len-1] == '\r')) { /* catch "\r\n", too. */ |
298 | message[--len] = '\0'; |
299 | } |
300 | } |
301 | |
302 | SDL_log_function(SDL_log_userdata, category, priority, message); |
303 | SDL_stack_free(message); |
304 | } |
305 | |
306 | #if defined(__WIN32__) && !defined(HAVE_STDIO_H) && !defined(__WINRT__) |
307 | /* Flag tracking the attachment of the console: 0=unattached, 1=attached to a console, 2=attached to a file, -1=error */ |
308 | static int consoleAttached = 0; |
309 | |
310 | /* Handle to stderr output of console. */ |
311 | static HANDLE stderrHandle = NULL; |
312 | #endif |
313 | |
314 | static void SDLCALL |
315 | SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority, |
316 | const char *message) |
317 | { |
318 | #if defined(__WIN32__) || defined(__WINRT__) |
319 | /* Way too many allocations here, urgh */ |
320 | /* Note: One can't call SDL_SetError here, since that function itself logs. */ |
321 | { |
322 | char *output; |
323 | size_t length; |
324 | LPTSTR tstr; |
325 | SDL_bool isstack; |
326 | |
327 | #if !defined(HAVE_STDIO_H) && !defined(__WINRT__) |
328 | BOOL attachResult; |
329 | DWORD attachError; |
330 | unsigned long charsWritten; |
331 | DWORD consoleMode; |
332 | |
333 | /* Maybe attach console and get stderr handle */ |
334 | if (consoleAttached == 0) { |
335 | attachResult = AttachConsole(ATTACH_PARENT_PROCESS); |
336 | if (!attachResult) { |
337 | attachError = GetLastError(); |
338 | if (attachError == ERROR_INVALID_HANDLE) { |
339 | /* This is expected when running from Visual Studio */ |
340 | /*OutputDebugString(TEXT("Parent process has no console\r\n"));*/ |
341 | consoleAttached = -1; |
342 | } else if (attachError == ERROR_GEN_FAILURE) { |
343 | OutputDebugString(TEXT("Could not attach to console of parent process\r\n" )); |
344 | consoleAttached = -1; |
345 | } else if (attachError == ERROR_ACCESS_DENIED) { |
346 | /* Already attached */ |
347 | consoleAttached = 1; |
348 | } else { |
349 | OutputDebugString(TEXT("Error attaching console\r\n" )); |
350 | consoleAttached = -1; |
351 | } |
352 | } else { |
353 | /* Newly attached */ |
354 | consoleAttached = 1; |
355 | } |
356 | |
357 | if (consoleAttached == 1) { |
358 | stderrHandle = GetStdHandle(STD_ERROR_HANDLE); |
359 | |
360 | if (GetConsoleMode(stderrHandle, &consoleMode) == 0) { |
361 | /* WriteConsole fails if the output is redirected to a file. Must use WriteFile instead. */ |
362 | consoleAttached = 2; |
363 | } |
364 | } |
365 | } |
366 | #endif /* !defined(HAVE_STDIO_H) && !defined(__WINRT__) */ |
367 | |
368 | length = SDL_strlen(SDL_priority_prefixes[priority]) + 2 + SDL_strlen(message) + 1 + 1 + 1; |
369 | output = SDL_small_alloc(char, length, &isstack); |
370 | SDL_snprintf(output, length, "%s: %s\r\n" , SDL_priority_prefixes[priority], message); |
371 | tstr = WIN_UTF8ToString(output); |
372 | |
373 | /* Output to debugger */ |
374 | OutputDebugString(tstr); |
375 | |
376 | #if !defined(HAVE_STDIO_H) && !defined(__WINRT__) |
377 | /* Screen output to stderr, if console was attached. */ |
378 | if (consoleAttached == 1) { |
379 | if (!WriteConsole(stderrHandle, tstr, SDL_tcslen(tstr), &charsWritten, NULL)) { |
380 | OutputDebugString(TEXT("Error calling WriteConsole\r\n" )); |
381 | if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) { |
382 | OutputDebugString(TEXT("Insufficient heap memory to write message\r\n" )); |
383 | } |
384 | } |
385 | |
386 | } else if (consoleAttached == 2) { |
387 | if (!WriteFile(stderrHandle, output, SDL_strlen(output), &charsWritten, NULL)) { |
388 | OutputDebugString(TEXT("Error calling WriteFile\r\n" )); |
389 | } |
390 | } |
391 | #endif /* !defined(HAVE_STDIO_H) && !defined(__WINRT__) */ |
392 | |
393 | SDL_free(tstr); |
394 | SDL_small_free(output, isstack); |
395 | } |
396 | #elif defined(__ANDROID__) |
397 | { |
398 | char tag[32]; |
399 | |
400 | SDL_snprintf(tag, SDL_arraysize(tag), "SDL/%s" , GetCategoryPrefix(category)); |
401 | __android_log_write(SDL_android_priority[priority], tag, message); |
402 | } |
403 | #elif defined(__APPLE__) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT)) |
404 | /* Technically we don't need Cocoa/UIKit, but that's where this function is defined for now. |
405 | */ |
406 | extern void SDL_NSLog(const char *text); |
407 | { |
408 | char *text; |
409 | /* !!! FIXME: why not just "char text[SDL_MAX_LOG_MESSAGE];" ? */ |
410 | text = SDL_stack_alloc(char, SDL_MAX_LOG_MESSAGE); |
411 | if (text) { |
412 | SDL_snprintf(text, SDL_MAX_LOG_MESSAGE, "%s: %s" , SDL_priority_prefixes[priority], message); |
413 | SDL_NSLog(text); |
414 | SDL_stack_free(text); |
415 | return; |
416 | } |
417 | } |
418 | #elif defined(__PSP__) |
419 | { |
420 | FILE* pFile; |
421 | pFile = fopen ("SDL_Log.txt" , "a" ); |
422 | fprintf(pFile, "%s: %s\n" , SDL_priority_prefixes[priority], message); |
423 | fclose (pFile); |
424 | } |
425 | #elif defined(__VITA__) |
426 | { |
427 | FILE* pFile; |
428 | pFile = fopen ("ux0:/data/SDL_Log.txt" , "a" ); |
429 | fprintf(pFile, "%s: %s\n" , SDL_priority_prefixes[priority], message); |
430 | fclose (pFile); |
431 | } |
432 | #endif |
433 | #if HAVE_STDIO_H |
434 | fprintf(stderr, "%s: %s\n" , SDL_priority_prefixes[priority], message); |
435 | #if __NACL__ |
436 | fflush(stderr); |
437 | #endif |
438 | #endif |
439 | } |
440 | |
441 | void |
442 | SDL_LogGetOutputFunction(SDL_LogOutputFunction *callback, void **userdata) |
443 | { |
444 | if (callback) { |
445 | *callback = SDL_log_function; |
446 | } |
447 | if (userdata) { |
448 | *userdata = SDL_log_userdata; |
449 | } |
450 | } |
451 | |
452 | void |
453 | SDL_LogSetOutputFunction(SDL_LogOutputFunction callback, void *userdata) |
454 | { |
455 | SDL_log_function = callback; |
456 | SDL_log_userdata = userdata; |
457 | } |
458 | |
459 | /* vi: set ts=4 sw=4 expandtab: */ |
460 | |