1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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(SDL_PLATFORM_WINDOWS)
24#include "core/windows/SDL_windows.h"
25#endif
26
27// Simple log messages in SDL
28
29#include "SDL_log_c.h"
30
31#ifdef HAVE_STDIO_H
32#include <stdio.h>
33#endif
34
35#ifdef SDL_PLATFORM_ANDROID
36#include <android/log.h>
37#endif
38
39#include "stdlib/SDL_vacopy.h"
40
41// The size of the stack buffer to use for rendering log messages.
42#define SDL_MAX_LOG_MESSAGE_STACK 256
43
44#define DEFAULT_CATEGORY -1
45
46typedef struct SDL_LogLevel
47{
48 int category;
49 SDL_LogPriority priority;
50 struct SDL_LogLevel *next;
51} SDL_LogLevel;
52
53
54// The default log output function
55static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority, const char *message);
56
57static void CleanupLogPriorities(void);
58static void CleanupLogPrefixes(void);
59
60static SDL_InitState SDL_log_init;
61static SDL_Mutex *SDL_log_lock;
62static SDL_Mutex *SDL_log_function_lock;
63static SDL_LogLevel *SDL_loglevels SDL_GUARDED_BY(SDL_log_lock);
64static SDL_LogPriority SDL_log_priorities[SDL_LOG_CATEGORY_CUSTOM] SDL_GUARDED_BY(SDL_log_lock);
65static SDL_LogPriority SDL_log_default_priority SDL_GUARDED_BY(SDL_log_lock);
66static SDL_LogOutputFunction SDL_log_function SDL_GUARDED_BY(SDL_log_function_lock) = SDL_LogOutput;
67static void *SDL_log_userdata SDL_GUARDED_BY(SDL_log_function_lock) = NULL;
68
69#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
70#pragma GCC diagnostic push
71#pragma GCC diagnostic ignored "-Wunused-variable"
72#endif
73
74// If this list changes, update the documentation for SDL_HINT_LOGGING
75static const char * const SDL_priority_names[] = {
76 NULL,
77 "TRACE",
78 "VERBOSE",
79 "DEBUG",
80 "INFO",
81 "WARN",
82 "ERROR",
83 "CRITICAL"
84};
85SDL_COMPILE_TIME_ASSERT(priority_names, SDL_arraysize(SDL_priority_names) == SDL_LOG_PRIORITY_COUNT);
86
87// This is guarded by SDL_log_function_lock because it's the logging function that calls GetLogPriorityPrefix()
88static char *SDL_priority_prefixes[SDL_LOG_PRIORITY_COUNT] SDL_GUARDED_BY(SDL_log_function_lock);
89
90// If this list changes, update the documentation for SDL_HINT_LOGGING
91static const char * const SDL_category_names[] = {
92 "APP",
93 "ERROR",
94 "ASSERT",
95 "SYSTEM",
96 "AUDIO",
97 "VIDEO",
98 "RENDER",
99 "INPUT",
100 "TEST",
101 "GPU"
102};
103SDL_COMPILE_TIME_ASSERT(category_names, SDL_arraysize(SDL_category_names) == SDL_LOG_CATEGORY_RESERVED2);
104
105#ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
106#pragma GCC diagnostic pop
107#endif
108
109#ifdef SDL_PLATFORM_ANDROID
110static int SDL_android_priority[] = {
111 ANDROID_LOG_UNKNOWN,
112 ANDROID_LOG_VERBOSE,
113 ANDROID_LOG_VERBOSE,
114 ANDROID_LOG_DEBUG,
115 ANDROID_LOG_INFO,
116 ANDROID_LOG_WARN,
117 ANDROID_LOG_ERROR,
118 ANDROID_LOG_FATAL
119};
120SDL_COMPILE_TIME_ASSERT(android_priority, SDL_arraysize(SDL_android_priority) == SDL_LOG_PRIORITY_COUNT);
121#endif // SDL_PLATFORM_ANDROID
122
123static void SDLCALL SDL_LoggingChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
124{
125 SDL_ResetLogPriorities();
126}
127
128void SDL_InitLog(void)
129{
130 if (!SDL_ShouldInit(&SDL_log_init)) {
131 return;
132 }
133
134 // If these fail we'll continue without them.
135 SDL_log_lock = SDL_CreateMutex();
136 SDL_log_function_lock = SDL_CreateMutex();
137
138 SDL_AddHintCallback(SDL_HINT_LOGGING, SDL_LoggingChanged, NULL);
139
140 SDL_SetInitialized(&SDL_log_init, true);
141}
142
143void SDL_QuitLog(void)
144{
145 if (!SDL_ShouldQuit(&SDL_log_init)) {
146 return;
147 }
148
149 SDL_RemoveHintCallback(SDL_HINT_LOGGING, SDL_LoggingChanged, NULL);
150
151 CleanupLogPriorities();
152 CleanupLogPrefixes();
153
154 if (SDL_log_lock) {
155 SDL_DestroyMutex(SDL_log_lock);
156 SDL_log_lock = NULL;
157 }
158 if (SDL_log_function_lock) {
159 SDL_DestroyMutex(SDL_log_function_lock);
160 SDL_log_function_lock = NULL;
161 }
162
163 SDL_SetInitialized(&SDL_log_init, false);
164}
165
166static void SDL_CheckInitLog(void)
167{
168 int status = SDL_GetAtomicInt(&SDL_log_init.status);
169 if (status == SDL_INIT_STATUS_INITIALIZED ||
170 (status == SDL_INIT_STATUS_INITIALIZING && SDL_log_init.thread == SDL_GetCurrentThreadID())) {
171 return;
172 }
173
174 SDL_InitLog();
175}
176
177static void CleanupLogPriorities(void)
178{
179 while (SDL_loglevels) {
180 SDL_LogLevel *entry = SDL_loglevels;
181 SDL_loglevels = entry->next;
182 SDL_free(entry);
183 }
184}
185
186void SDL_SetLogPriorities(SDL_LogPriority priority)
187{
188 SDL_CheckInitLog();
189
190 SDL_LockMutex(SDL_log_lock);
191 {
192 CleanupLogPriorities();
193
194 SDL_log_default_priority = priority;
195 for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
196 SDL_log_priorities[i] = priority;
197 }
198 }
199 SDL_UnlockMutex(SDL_log_lock);
200}
201
202void SDL_SetLogPriority(int category, SDL_LogPriority priority)
203{
204 SDL_LogLevel *entry;
205
206 SDL_CheckInitLog();
207
208 SDL_LockMutex(SDL_log_lock);
209 {
210 if (category >= 0 && category < SDL_arraysize(SDL_log_priorities)) {
211 SDL_log_priorities[category] = priority;
212 } else {
213 for (entry = SDL_loglevels; entry; entry = entry->next) {
214 if (entry->category == category) {
215 entry->priority = priority;
216 break;
217 }
218 }
219
220 if (!entry) {
221 entry = (SDL_LogLevel *)SDL_malloc(sizeof(*entry));
222 if (entry) {
223 entry->category = category;
224 entry->priority = priority;
225 entry->next = SDL_loglevels;
226 SDL_loglevels = entry;
227 }
228 }
229 }
230 }
231 SDL_UnlockMutex(SDL_log_lock);
232}
233
234SDL_LogPriority SDL_GetLogPriority(int category)
235{
236 SDL_LogLevel *entry;
237 SDL_LogPriority priority = SDL_LOG_PRIORITY_INVALID;
238
239 SDL_CheckInitLog();
240
241 // Bypass the lock for known categories
242 // Technically if the priority was set on a different CPU the value might not
243 // be visible on this CPU for a while, but in practice it's fast enough that
244 // this performance improvement is worthwhile.
245 if (category >= 0 && category < SDL_arraysize(SDL_log_priorities)) {
246 return SDL_log_priorities[category];
247 }
248
249 SDL_LockMutex(SDL_log_lock);
250 {
251 if (category >= 0 && category < SDL_arraysize(SDL_log_priorities)) {
252 priority = SDL_log_priorities[category];
253 } else {
254 for (entry = SDL_loglevels; entry; entry = entry->next) {
255 if (entry->category == category) {
256 priority = entry->priority;
257 break;
258 }
259 }
260 if (priority == SDL_LOG_PRIORITY_INVALID) {
261 priority = SDL_log_default_priority;
262 }
263 }
264 }
265 SDL_UnlockMutex(SDL_log_lock);
266
267 return priority;
268}
269
270static bool ParseLogCategory(const char *string, size_t length, int *category)
271{
272 int i;
273
274 if (SDL_isdigit(*string)) {
275 *category = SDL_atoi(string);
276 return true;
277 }
278
279 if (*string == '*') {
280 *category = DEFAULT_CATEGORY;
281 return true;
282 }
283
284 for (i = 0; i < SDL_arraysize(SDL_category_names); ++i) {
285 if (SDL_strncasecmp(string, SDL_category_names[i], length) == 0) {
286 *category = i;
287 return true;
288 }
289 }
290 return false;
291}
292
293static bool ParseLogPriority(const char *string, size_t length, SDL_LogPriority *priority)
294{
295 int i;
296
297 if (SDL_isdigit(*string)) {
298 i = SDL_atoi(string);
299 if (i == 0) {
300 // 0 has a special meaning of "disable this category"
301 *priority = SDL_LOG_PRIORITY_COUNT;
302 return true;
303 }
304 if (i > SDL_LOG_PRIORITY_INVALID && i < SDL_LOG_PRIORITY_COUNT) {
305 *priority = (SDL_LogPriority)i;
306 return true;
307 }
308 return false;
309 }
310
311 if (SDL_strncasecmp(string, "quiet", length) == 0) {
312 *priority = SDL_LOG_PRIORITY_COUNT;
313 return true;
314 }
315
316 for (i = SDL_LOG_PRIORITY_INVALID + 1; i < SDL_LOG_PRIORITY_COUNT; ++i) {
317 if (SDL_strncasecmp(string, SDL_priority_names[i], length) == 0) {
318 *priority = (SDL_LogPriority)i;
319 return true;
320 }
321 }
322 return false;
323}
324
325static void ParseLogPriorities(const char *hint)
326{
327 const char *name, *next;
328 int category = DEFAULT_CATEGORY;
329 SDL_LogPriority priority = SDL_LOG_PRIORITY_INVALID;
330
331 if (SDL_strchr(hint, '=') == NULL) {
332 if (ParseLogPriority(hint, SDL_strlen(hint), &priority)) {
333 SDL_SetLogPriorities(priority);
334 }
335 return;
336 }
337
338 for (name = hint; name; name = next) {
339 const char *sep = SDL_strchr(name, '=');
340 if (!sep) {
341 break;
342 }
343 next = SDL_strchr(sep, ',');
344 if (next) {
345 ++next;
346 }
347
348 if (ParseLogCategory(name, (sep - name), &category)) {
349 const char *value = sep + 1;
350 size_t len;
351 if (next) {
352 len = (next - value - 1);
353 } else {
354 len = SDL_strlen(value);
355 }
356 if (ParseLogPriority(value, len, &priority)) {
357 if (category == DEFAULT_CATEGORY) {
358 for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
359 if (SDL_log_priorities[i] == SDL_LOG_PRIORITY_INVALID) {
360 SDL_log_priorities[i] = priority;
361 }
362 }
363 SDL_log_default_priority = priority;
364 } else {
365 SDL_SetLogPriority(category, priority);
366 }
367 }
368 }
369 }
370}
371
372void SDL_ResetLogPriorities(void)
373{
374 SDL_CheckInitLog();
375
376 SDL_LockMutex(SDL_log_lock);
377 {
378 CleanupLogPriorities();
379
380 SDL_log_default_priority = SDL_LOG_PRIORITY_INVALID;
381 for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
382 SDL_log_priorities[i] = SDL_LOG_PRIORITY_INVALID;
383 }
384
385 const char *hint = SDL_GetHint(SDL_HINT_LOGGING);
386 if (hint) {
387 ParseLogPriorities(hint);
388 }
389
390 if (SDL_log_default_priority == SDL_LOG_PRIORITY_INVALID) {
391 SDL_log_default_priority = SDL_LOG_PRIORITY_ERROR;
392 }
393 for (int i = 0; i < SDL_arraysize(SDL_log_priorities); ++i) {
394 if (SDL_log_priorities[i] != SDL_LOG_PRIORITY_INVALID) {
395 continue;
396 }
397
398 switch (i) {
399 case SDL_LOG_CATEGORY_APPLICATION:
400 SDL_log_priorities[i] = SDL_LOG_PRIORITY_INFO;
401 break;
402 case SDL_LOG_CATEGORY_ASSERT:
403 SDL_log_priorities[i] = SDL_LOG_PRIORITY_WARN;
404 break;
405 case SDL_LOG_CATEGORY_TEST:
406 SDL_log_priorities[i] = SDL_LOG_PRIORITY_VERBOSE;
407 break;
408 default:
409 SDL_log_priorities[i] = SDL_LOG_PRIORITY_ERROR;
410 break;
411 }
412 }
413 }
414 SDL_UnlockMutex(SDL_log_lock);
415}
416
417static void CleanupLogPrefixes(void)
418{
419 for (int i = 0; i < SDL_arraysize(SDL_priority_prefixes); ++i) {
420 if (SDL_priority_prefixes[i]) {
421 SDL_free(SDL_priority_prefixes[i]);
422 SDL_priority_prefixes[i] = NULL;
423 }
424 }
425}
426
427static const char *GetLogPriorityPrefix(SDL_LogPriority priority)
428{
429 if (priority <= SDL_LOG_PRIORITY_INVALID || priority >= SDL_LOG_PRIORITY_COUNT) {
430 return "";
431 }
432
433 if (SDL_priority_prefixes[priority]) {
434 return SDL_priority_prefixes[priority];
435 }
436
437 switch (priority) {
438 case SDL_LOG_PRIORITY_WARN:
439 return "WARNING: ";
440 case SDL_LOG_PRIORITY_ERROR:
441 return "ERROR: ";
442 case SDL_LOG_PRIORITY_CRITICAL:
443 return "ERROR: ";
444 default:
445 return "";
446 }
447}
448
449bool SDL_SetLogPriorityPrefix(SDL_LogPriority priority, const char *prefix)
450{
451 char *prefix_copy;
452
453 if (priority <= SDL_LOG_PRIORITY_INVALID || priority >= SDL_LOG_PRIORITY_COUNT) {
454 return SDL_InvalidParamError("priority");
455 }
456
457 if (!prefix || !*prefix) {
458 prefix_copy = SDL_strdup("");
459 } else {
460 prefix_copy = SDL_strdup(prefix);
461 }
462 if (!prefix_copy) {
463 return false;
464 }
465
466 SDL_LockMutex(SDL_log_function_lock);
467 {
468 if (SDL_priority_prefixes[priority]) {
469 SDL_free(SDL_priority_prefixes[priority]);
470 }
471 SDL_priority_prefixes[priority] = prefix_copy;
472 }
473 SDL_UnlockMutex(SDL_log_function_lock);
474
475 return true;
476}
477
478void SDL_Log(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
479{
480 va_list ap;
481
482 va_start(ap, fmt);
483 SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, ap);
484 va_end(ap);
485}
486
487void SDL_LogTrace(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
488{
489 va_list ap;
490
491 va_start(ap, fmt);
492 SDL_LogMessageV(category, SDL_LOG_PRIORITY_TRACE, fmt, ap);
493 va_end(ap);
494}
495
496void SDL_LogVerbose(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
497{
498 va_list ap;
499
500 va_start(ap, fmt);
501 SDL_LogMessageV(category, SDL_LOG_PRIORITY_VERBOSE, fmt, ap);
502 va_end(ap);
503}
504
505void SDL_LogDebug(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
506{
507 va_list ap;
508
509 va_start(ap, fmt);
510 SDL_LogMessageV(category, SDL_LOG_PRIORITY_DEBUG, fmt, ap);
511 va_end(ap);
512}
513
514void SDL_LogInfo(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
515{
516 va_list ap;
517
518 va_start(ap, fmt);
519 SDL_LogMessageV(category, SDL_LOG_PRIORITY_INFO, fmt, ap);
520 va_end(ap);
521}
522
523void SDL_LogWarn(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
524{
525 va_list ap;
526
527 va_start(ap, fmt);
528 SDL_LogMessageV(category, SDL_LOG_PRIORITY_WARN, fmt, ap);
529 va_end(ap);
530}
531
532void SDL_LogError(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
533{
534 va_list ap;
535
536 va_start(ap, fmt);
537 SDL_LogMessageV(category, SDL_LOG_PRIORITY_ERROR, fmt, ap);
538 va_end(ap);
539}
540
541void SDL_LogCritical(int category, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
542{
543 va_list ap;
544
545 va_start(ap, fmt);
546 SDL_LogMessageV(category, SDL_LOG_PRIORITY_CRITICAL, fmt, ap);
547 va_end(ap);
548}
549
550void SDL_LogMessage(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
551{
552 va_list ap;
553
554 va_start(ap, fmt);
555 SDL_LogMessageV(category, priority, fmt, ap);
556 va_end(ap);
557}
558
559#ifdef SDL_PLATFORM_ANDROID
560static const char *GetCategoryPrefix(int category)
561{
562 if (category < SDL_LOG_CATEGORY_RESERVED2) {
563 return SDL_category_names[category];
564 }
565 if (category < SDL_LOG_CATEGORY_CUSTOM) {
566 return "RESERVED";
567 }
568 return "CUSTOM";
569}
570#endif // SDL_PLATFORM_ANDROID
571
572void SDL_LogMessageV(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap)
573{
574 char *message = NULL;
575 char stack_buf[SDL_MAX_LOG_MESSAGE_STACK];
576 size_t len_plus_term;
577 int len;
578 va_list aq;
579
580 // Nothing to do if we don't have an output function
581 if (!SDL_log_function) {
582 return;
583 }
584
585 // See if we want to do anything with this message
586 if (priority < SDL_GetLogPriority(category)) {
587 return;
588 }
589
590 // Render into stack buffer
591 va_copy(aq, ap);
592 len = SDL_vsnprintf(stack_buf, sizeof(stack_buf), fmt, aq);
593 va_end(aq);
594
595 if (len < 0) {
596 return;
597 }
598
599 // If message truncated, allocate and re-render
600 if (len >= sizeof(stack_buf) && SDL_size_add_check_overflow(len, 1, &len_plus_term)) {
601 // Allocate exactly what we need, including the zero-terminator
602 message = (char *)SDL_malloc(len_plus_term);
603 if (!message) {
604 return;
605 }
606 va_copy(aq, ap);
607 len = SDL_vsnprintf(message, len_plus_term, fmt, aq);
608 va_end(aq);
609 } else {
610 message = stack_buf;
611 }
612
613 // Chop off final endline.
614 if ((len > 0) && (message[len - 1] == '\n')) {
615 message[--len] = '\0';
616 if ((len > 0) && (message[len - 1] == '\r')) { // catch "\r\n", too.
617 message[--len] = '\0';
618 }
619 }
620
621 SDL_LockMutex(SDL_log_function_lock);
622 {
623 SDL_log_function(SDL_log_userdata, category, priority, message);
624 }
625 SDL_UnlockMutex(SDL_log_function_lock);
626
627 // Free only if dynamically allocated
628 if (message != stack_buf) {
629 SDL_free(message);
630 }
631}
632
633#if defined(SDL_PLATFORM_WIN32) && !defined(SDL_PLATFORM_GDK)
634enum {
635 CONSOLE_UNATTACHED = 0,
636 CONSOLE_ATTACHED_CONSOLE = 1,
637 CONSOLE_ATTACHED_FILE = 2,
638 CONSOLE_ATTACHED_ERROR = -1,
639} consoleAttached = CONSOLE_UNATTACHED;
640
641// Handle to stderr output of console.
642static HANDLE stderrHandle = NULL;
643#endif
644
645static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority priority,
646 const char *message)
647{
648#if defined(SDL_PLATFORM_WINDOWS)
649 // Way too many allocations here, urgh
650 // Note: One can't call SDL_SetError here, since that function itself logs.
651 {
652 char *output;
653 size_t length;
654 LPTSTR tstr;
655 bool isstack;
656
657#if !defined(SDL_PLATFORM_GDK)
658 BOOL attachResult;
659 DWORD attachError;
660 DWORD consoleMode;
661 DWORD charsWritten;
662
663 // Maybe attach console and get stderr handle
664 if (consoleAttached == CONSOLE_UNATTACHED) {
665 attachResult = AttachConsole(ATTACH_PARENT_PROCESS);
666 if (!attachResult) {
667 attachError = GetLastError();
668 if (attachError == ERROR_INVALID_HANDLE) {
669 // This is expected when running from Visual Studio
670 // OutputDebugString(TEXT("Parent process has no console\r\n"));
671 consoleAttached = CONSOLE_ATTACHED_ERROR;
672 } else if (attachError == ERROR_GEN_FAILURE) {
673 OutputDebugString(TEXT("Could not attach to console of parent process\r\n"));
674 consoleAttached = CONSOLE_ATTACHED_ERROR;
675 } else if (attachError == ERROR_ACCESS_DENIED) {
676 // Already attached
677 consoleAttached = CONSOLE_ATTACHED_CONSOLE;
678 } else {
679 OutputDebugString(TEXT("Error attaching console\r\n"));
680 consoleAttached = CONSOLE_ATTACHED_ERROR;
681 }
682 } else {
683 // Newly attached
684 consoleAttached = CONSOLE_ATTACHED_CONSOLE;
685 }
686
687 if (consoleAttached == CONSOLE_ATTACHED_CONSOLE) {
688 stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
689
690 if (GetConsoleMode(stderrHandle, &consoleMode) == 0) {
691 // WriteConsole fails if the output is redirected to a file. Must use WriteFile instead.
692 consoleAttached = CONSOLE_ATTACHED_FILE;
693 }
694 }
695 }
696#endif // !defined(SDL_PLATFORM_GDK)
697 length = SDL_strlen(GetLogPriorityPrefix(priority)) + SDL_strlen(message) + 1 + 1 + 1;
698 output = SDL_small_alloc(char, length, &isstack);
699 if (!output) {
700 return;
701 }
702 (void)SDL_snprintf(output, length, "%s%s\r\n", GetLogPriorityPrefix(priority), message);
703 tstr = WIN_UTF8ToString(output);
704
705 // Output to debugger
706 OutputDebugString(tstr);
707
708#if !defined(SDL_PLATFORM_GDK)
709 // Screen output to stderr, if console was attached.
710 if (consoleAttached == CONSOLE_ATTACHED_CONSOLE) {
711 if (!WriteConsole(stderrHandle, tstr, (DWORD)SDL_tcslen(tstr), &charsWritten, NULL)) {
712 OutputDebugString(TEXT("Error calling WriteConsole\r\n"));
713 if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) {
714 OutputDebugString(TEXT("Insufficient heap memory to write message\r\n"));
715 }
716 }
717
718 } else if (consoleAttached == CONSOLE_ATTACHED_FILE) {
719 if (!WriteFile(stderrHandle, output, (DWORD)SDL_strlen(output), &charsWritten, NULL)) {
720 OutputDebugString(TEXT("Error calling WriteFile\r\n"));
721 }
722 }
723#endif // !defined(SDL_PLATFORM_GDK)
724
725 SDL_free(tstr);
726 SDL_small_free(output, isstack);
727 }
728#elif defined(SDL_PLATFORM_ANDROID)
729 {
730 char tag[32];
731
732 SDL_snprintf(tag, SDL_arraysize(tag), "SDL/%s", GetCategoryPrefix(category));
733 __android_log_write(SDL_android_priority[priority], tag, message);
734 }
735#elif defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))
736 /* Technically we don't need Cocoa/UIKit, but that's where this function is defined for now.
737 */
738 extern void SDL_NSLog(const char *prefix, const char *text);
739 {
740 SDL_NSLog(GetLogPriorityPrefix(priority), message);
741 return;
742 }
743#elif defined(SDL_PLATFORM_PSP) || defined(SDL_PLATFORM_PS2)
744 {
745 FILE *pFile;
746 pFile = fopen("SDL_Log.txt", "a");
747 if (pFile) {
748 (void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message);
749 (void)fclose(pFile);
750 }
751 }
752#elif defined(SDL_PLATFORM_VITA)
753 {
754 FILE *pFile;
755 pFile = fopen("ux0:/data/SDL_Log.txt", "a");
756 if (pFile) {
757 (void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message);
758 (void)fclose(pFile);
759 }
760 }
761#elif defined(SDL_PLATFORM_3DS)
762 {
763 FILE *pFile;
764 pFile = fopen("sdmc:/3ds/SDL_Log.txt", "a");
765 if (pFile) {
766 (void)fprintf(pFile, "%s%s\n", GetLogPriorityPrefix(priority), message);
767 (void)fclose(pFile);
768 }
769 }
770#endif
771#if defined(HAVE_STDIO_H) && \
772 !(defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT))) && \
773 !(defined(SDL_PLATFORM_WIN32))
774 (void)fprintf(stderr, "%s%s\n", GetLogPriorityPrefix(priority), message);
775#endif
776}
777
778SDL_LogOutputFunction SDL_GetDefaultLogOutputFunction(void)
779{
780 return SDL_LogOutput;
781}
782
783void SDL_GetLogOutputFunction(SDL_LogOutputFunction *callback, void **userdata)
784{
785 SDL_LockMutex(SDL_log_function_lock);
786 {
787 if (callback) {
788 *callback = SDL_log_function;
789 }
790 if (userdata) {
791 *userdata = SDL_log_userdata;
792 }
793 }
794 SDL_UnlockMutex(SDL_log_function_lock);
795}
796
797void SDL_SetLogOutputFunction(SDL_LogOutputFunction callback, void *userdata)
798{
799 SDL_LockMutex(SDL_log_function_lock);
800 {
801 SDL_log_function = callback;
802 SDL_log_userdata = userdata;
803 }
804 SDL_UnlockMutex(SDL_log_function_lock);
805}
806