SDL: Added SDL_SetLogPriorityPrefix()

From 6161c437c776b77d81c90a29d0106070db83cd5b Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 1 Apr 2024 20:07:51 -0700
Subject: [PATCH] Added SDL_SetLogPriorityPrefix()

SDL_Log() no longer prints a log prefix by default for SDL_LOG_PRIORITY_INFO and below. The log prefixes can be customized with SDL_SetLogPriorityPrefix().
---
 docs/README-migration.md          |  2 ++
 include/SDL3/SDL_log.h            | 17 ++++++++++
 src/SDL_log.c                     | 56 ++++++++++++++++++++++++-------
 src/dynapi/SDL_dynapi.sym         |  1 +
 src/dynapi/SDL_dynapi_overrides.h |  1 +
 src/dynapi/SDL_dynapi_procs.h     |  1 +
 src/video/cocoa/SDL_cocoavideo.m  |  4 +--
 src/video/uikit/SDL_uikitvideo.m  |  4 +--
 8 files changed, 69 insertions(+), 17 deletions(-)

diff --git a/docs/README-migration.md b/docs/README-migration.md
index 916f61f0a0c7e..529dae78a1c25 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -1058,6 +1058,8 @@ SDL_LoadFunction() now returns `SDL_FunctionPointer` instead of `void *`, and sh
 
 ## SDL_log.h
 
+SDL_Log() no longer prints a log prefix by default for SDL_LOG_PRIORITY_INFO and below. The log prefixes can be customized with SDL_SetLogPriorityPrefix().
+
 The following macros have been removed:
 * SDL_MAX_LOG_MESSAGE - there's no message length limit anymore. If you need an artificial limit, this used to be 4096 in SDL versions before 2.0.24.
 
diff --git a/include/SDL3/SDL_log.h b/include/SDL3/SDL_log.h
index eba3fac8d3166..2723b18f25b70 100644
--- a/include/SDL3/SDL_log.h
+++ b/include/SDL3/SDL_log.h
@@ -181,6 +181,23 @@ extern SDL_DECLSPEC SDL_LogPriority SDLCALL SDL_GetLogPriority(int category);
  */
 extern SDL_DECLSPEC void SDLCALL SDL_ResetLogPriorities(void);
 
+/**
+ * Set the text prepended to log messages of a given priority.
+ *
+ * By default SDL_LOG_PRIORITY_INFO and below have no prefix, and SDL_LOG_PRIORITY_WARN and higher have a prefix showing their priority, e.g. "WARNING: ".
+ *
+ * \param priority the SDL_LogPriority to modify.
+ * \param prefix the prefix to use for that log priority, or NULL to use no prefix.
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_SetLogPriorities
+ * \sa SDL_SetLogPriority
+ */
+extern SDL_DECLSPEC int SDLCALL SDL_SetLogPriorityPrefix(SDL_LogPriority priority, const char *prefix);
+
 /**
  * Log a message with SDL_LOG_CATEGORY_APPLICATION and SDL_LOG_PRIORITY_INFO.
  *
diff --git a/src/SDL_log.c b/src/SDL_log.c
index b3d6028a017e3..267ade6e6b6a6 100644
--- a/src/SDL_log.c
+++ b/src/SDL_log.c
@@ -66,7 +66,7 @@ static SDL_Mutex *log_function_mutex = NULL;
 #endif
 
 /* If this list changes, update the documentation for SDL_HINT_LOGGING */
-static const char *SDL_priority_prefixes[] = {
+static const char * const SDL_priority_names[] = {
     NULL,
     "VERBOSE",
     "DEBUG",
@@ -75,10 +75,22 @@ static const char *SDL_priority_prefixes[] = {
     "ERROR",
     "CRITICAL"
 };
+SDL_COMPILE_TIME_ASSERT(priority_names, SDL_arraysize(SDL_priority_names) == SDL_NUM_LOG_PRIORITIES);
+
+/* If this list changes, update the documentation for SDL_HINT_LOGGING */
+static const char *SDL_priority_prefixes[] = {
+    NULL,
+    "",
+    "",
+    "",
+    "WARNING: ",
+    "ERROR: ",
+    "CRITICAL: "
+};
 SDL_COMPILE_TIME_ASSERT(priority_prefixes, SDL_arraysize(SDL_priority_prefixes) == SDL_NUM_LOG_PRIORITIES);
 
 /* If this list changes, update the documentation for SDL_HINT_LOGGING */
-static const char *SDL_category_prefixes[] = {
+static const char * const SDL_category_names[] = {
     "APP",
     "ERROR",
     "ASSERT",
@@ -89,7 +101,7 @@ static const char *SDL_category_prefixes[] = {
     "INPUT",
     "TEST"
 };
-SDL_COMPILE_TIME_ASSERT(category_prefixes, SDL_arraysize(SDL_category_prefixes) == SDL_LOG_CATEGORY_RESERVED1);
+SDL_COMPILE_TIME_ASSERT(category_names, SDL_arraysize(SDL_category_names) == SDL_LOG_CATEGORY_RESERVED1);
 
 #ifdef HAVE_GCC_DIAGNOSTIC_PRAGMA
 #pragma GCC diagnostic pop
@@ -172,8 +184,8 @@ static SDL_bool SDL_ParseLogCategory(const char *string, size_t length, int *cat
         return SDL_TRUE;
     }
 
-    for (i = 0; i < SDL_arraysize(SDL_category_prefixes); ++i) {
-        if (SDL_strncasecmp(string, SDL_category_prefixes[i], length) == 0) {
+    for (i = 0; i < SDL_arraysize(SDL_category_names); ++i) {
+        if (SDL_strncasecmp(string, SDL_category_names[i], length) == 0) {
             *category = i;
             return SDL_TRUE;
         }
@@ -205,7 +217,7 @@ static SDL_bool SDL_ParseLogPriority(const char *string, size_t length, SDL_LogP
     }
 
     for (i = SDL_LOG_PRIORITY_VERBOSE; i < SDL_NUM_LOG_PRIORITIES; ++i) {
-        if (SDL_strncasecmp(string, SDL_priority_prefixes[i], length) == 0) {
+        if (SDL_strncasecmp(string, SDL_priority_names[i], length) == 0) {
             *priority = (SDL_LogPriority)i;
             return SDL_TRUE;
         }
@@ -303,6 +315,24 @@ void SDL_ResetLogPriorities(void)
     SDL_forced_priority = SDL_FALSE;
 }
 
+int SDL_SetLogPriorityPrefix(SDL_LogPriority priority, const char *prefix)
+{
+    if (priority < SDL_LOG_PRIORITY_VERBOSE || priority >= SDL_NUM_LOG_PRIORITIES) {
+        return SDL_InvalidParamError("priority");
+    }
+
+    if (!prefix) {
+        prefix = "";
+    } else {
+        prefix = SDL_GetPersistentString(prefix);
+        if (!prefix) {
+            return -1;
+        }
+    }
+    SDL_priority_prefixes[priority] = prefix;
+    return 0;
+}
+
 void SDL_Log(SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
 {
     va_list ap;
@@ -379,7 +409,7 @@ void SDL_LogMessage(int category, SDL_LogPriority priority, SDL_PRINTF_FORMAT_ST
 static const char *GetCategoryPrefix(int category)
 {
     if (category < SDL_LOG_CATEGORY_RESERVED1) {
-        return SDL_category_prefixes[category];
+        return SDL_category_names[category];
     }
     if (category < SDL_LOG_CATEGORY_CUSTOM) {
         return "RESERVED";
@@ -518,9 +548,9 @@ static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority
         }
 #endif /* !defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINRT) && !defined(SDL_PLATFORM_GDK) */
 
-        length = SDL_strlen(SDL_priority_prefixes[priority]) + 2 + SDL_strlen(message) + 1 + 1 + 1;
+        length = SDL_strlen(SDL_priority_prefixes[priority]) + SDL_strlen(message) + 1 + 1 + 1;
         output = SDL_small_alloc(char, length, &isstack);
-        (void)SDL_snprintf(output, length, "%s: %s\r\n", SDL_priority_prefixes[priority], message);
+        (void)SDL_snprintf(output, length, "%s%s\r\n", SDL_priority_prefixes[priority], message);
         tstr = WIN_UTF8ToString(output);
 
         /* Output to debugger */
@@ -566,7 +596,7 @@ static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority
         FILE *pFile;
         pFile = fopen("SDL_Log.txt", "a");
         if (pFile) {
-            (void)fprintf(pFile, "%s: %s\n", SDL_priority_prefixes[priority], message);
+            (void)fprintf(pFile, "%s%s\n", SDL_priority_prefixes[priority], message);
             (void)fclose(pFile);
         }
     }
@@ -575,7 +605,7 @@ static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority
         FILE *pFile;
         pFile = fopen("ux0:/data/SDL_Log.txt", "a");
         if (pFile) {
-            (void)fprintf(pFile, "%s: %s\n", SDL_priority_prefixes[priority], message);
+            (void)fprintf(pFile, "%s%s\n", SDL_priority_prefixes[priority], message);
             (void)fclose(pFile);
         }
     }
@@ -584,14 +614,14 @@ static void SDLCALL SDL_LogOutput(void *userdata, int category, SDL_LogPriority
         FILE *pFile;
         pFile = fopen("sdmc:/3ds/SDL_Log.txt", "a");
         if (pFile) {
-            (void)fprintf(pFile, "%s: %s\n", SDL_priority_prefixes[priority], message);
+            (void)fprintf(pFile, "%s%s\n", SDL_priority_prefixes[priority], message);
             (void)fclose(pFile);
         }
     }
 #endif
 #if defined(HAVE_STDIO_H) && \
     !(defined(SDL_PLATFORM_APPLE) && (defined(SDL_VIDEO_DRIVER_COCOA) || defined(SDL_VIDEO_DRIVER_UIKIT)))
-    (void)fprintf(stderr, "%s: %s\n", SDL_priority_prefixes[priority], message);
+    (void)fprintf(stderr, "%s%s\n", SDL_priority_prefixes[priority], message);
 #endif
 }
 
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index b81e437b0a2d4..c671942e8f3f2 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -742,6 +742,7 @@ SDL3_0.0.0 {
     SDL_SetLogOutputFunction;
     SDL_SetLogPriorities;
     SDL_SetLogPriority;
+    SDL_SetLogPriorityPrefix;
     SDL_SetMainReady;
     SDL_SetMemoryFunctions;
     SDL_SetModState;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index e096d67202cc9..8e2a066b02df3 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -767,6 +767,7 @@
 #define SDL_SetLogOutputFunction SDL_SetLogOutputFunction_REAL
 #define SDL_SetLogPriorities SDL_SetLogPriorities_REAL
 #define SDL_SetLogPriority SDL_SetLogPriority_REAL
+#define SDL_SetLogPriorityPrefix SDL_SetLogPriorityPrefix_REAL
 #define SDL_SetMainReady SDL_SetMainReady_REAL
 #define SDL_SetMemoryFunctions SDL_SetMemoryFunctions_REAL
 #define SDL_SetModState SDL_SetModState_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index faf99fa0f58e1..cb9163bc98da8 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -777,6 +777,7 @@ SDL_DYNAPI_PROC(int,SDL_SetLinuxThreadPriorityAndPolicy,(Sint64 a, int b, int c)
 SDL_DYNAPI_PROC(void,SDL_SetLogOutputFunction,(SDL_LogOutputFunction a, void *b),(a,b),)
 SDL_DYNAPI_PROC(void,SDL_SetLogPriorities,(SDL_LogPriority a),(a),)
 SDL_DYNAPI_PROC(void,SDL_SetLogPriority,(int a, SDL_LogPriority b),(a,b),)
+SDL_DYNAPI_PROC(int,SDL_SetLogPriorityPrefix,(SDL_LogPriority a, const char *b),(a,b),return)
 SDL_DYNAPI_PROC(void,SDL_SetMainReady,(void),(),)
 SDL_DYNAPI_PROC(int,SDL_SetMemoryFunctions,(SDL_malloc_func a, SDL_calloc_func b, SDL_realloc_func c, SDL_free_func d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(void,SDL_SetModState,(SDL_Keymod a),(a),)
diff --git a/src/video/cocoa/SDL_cocoavideo.m b/src/video/cocoa/SDL_cocoavideo.m
index 7b1aecc2f413d..7bc315805a83c 100644
--- a/src/video/cocoa/SDL_cocoavideo.m
+++ b/src/video/cocoa/SDL_cocoavideo.m
@@ -315,9 +315,9 @@ void SDL_NSLog(const char *prefix, const char *text)
 {
     @autoreleasepool {
         NSString *nsText = [NSString stringWithUTF8String:text];
-        if (prefix) {
+        if (prefix && *prefix) {
             NSString *nsPrefix = [NSString stringWithUTF8String:prefix];
-            NSLog(@"%@: %@", nsPrefix, nsText);
+            NSLog(@"%@%@", nsPrefix, nsText);
         } else {
             NSLog(@"%@", nsText);
         }
diff --git a/src/video/uikit/SDL_uikitvideo.m b/src/video/uikit/SDL_uikitvideo.m
index 90866df1192f0..827a7f2163fee 100644
--- a/src/video/uikit/SDL_uikitvideo.m
+++ b/src/video/uikit/SDL_uikitvideo.m
@@ -286,9 +286,9 @@ void SDL_NSLog(const char *prefix, const char *text)
 {
     @autoreleasepool {
         NSString *nsText = [NSString stringWithUTF8String:text];
-        if (prefix) {
+        if (prefix && *prefix) {
             NSString *nsPrefix = [NSString stringWithUTF8String:prefix];
-            NSLog(@"%@: %@", nsPrefix, nsText);
+            NSLog(@"%@%@", nsPrefix, nsText);
         } else {
             NSLog(@"%@", nsText);
         }