From ad7cb650dce79b8155be65663e37c337d8e67a71 Mon Sep 17 00:00:00 2001
From: Vicki Pfau <vi@endrift.com>
Date: Sun, 5 Mar 2017 17:25:35 -0800
Subject: [PATCH] Core: Add logging filters

---
 include/mgba/core/log.h    | 17 +++++++++
 include/mgba/core/thread.h |  1 -
 src/core/log.c             | 70 ++++++++++++++++++++++++++++++++++++--
 src/core/thread.c          | 24 ++++++++-----
 4 files changed, 101 insertions(+), 11 deletions(-)

diff --git a/include/mgba/core/log.h b/include/mgba/core/log.h
index 3cdbe336e..5a6fc6128 100644
--- a/include/mgba/core/log.h
+++ b/include/mgba/core/log.h
@@ -10,6 +10,8 @@
 
 CXX_GUARD_START
 
+#include <mgba-util/table.h>
+
 enum mLogLevel {
 	mLOG_FATAL = 0x01,
 	mLOG_ERROR = 0x02,
@@ -22,8 +24,16 @@ enum mLogLevel {
 	mLOG_ALL = 0x7F
 };
 
+struct Table;
+struct mLogFilter {
+	int defaultLevels;
+	struct Table categories;
+	struct Table levels;
+};
+
 struct mLogger {
 	void (*log)(struct mLogger*, int category, enum mLogLevel level, const char* format, va_list args);
+	struct mLogFilter* filter;
 };
 
 struct mLogger* mLogGetContext(void);
@@ -33,6 +43,13 @@ const char* mLogCategoryName(int);
 const char* mLogCategoryId(int);
 int mLogCategoryById(const char*);
 
+struct mCoreConfig;
+void mLogFilterInit(struct mLogFilter*);
+void mLogFilterDeinit(struct mLogFilter*);
+void mLogFilterLoad(struct mLogFilter*, const struct mCoreConfig*);
+void mLogFilterSet(struct mLogFilter*, const char* category, int levels);
+bool mLogFilterTest(struct mLogFilter*, int category, enum mLogLevel level);
+
 ATTRIBUTE_FORMAT(printf, 3, 4)
 void mLog(int category, enum mLogLevel level, const char* format, ...);
 
diff --git a/include/mgba/core/thread.h b/include/mgba/core/thread.h
index b0a60ac80..f586bfd3b 100644
--- a/include/mgba/core/thread.h
+++ b/include/mgba/core/thread.h
@@ -58,7 +58,6 @@ struct mCoreThread {
 	bool frameWasOn;
 
 	struct mThreadLogger logger;
-	enum mLogLevel logLevel;
 	ThreadCallback startCallback;
 	ThreadCallback resetCallback;
 	ThreadCallback cleanCallback;
diff --git a/src/core/log.c b/src/core/log.c
index 04c6a1214..6af5724b9 100644
--- a/src/core/log.c
+++ b/src/core/log.c
@@ -5,6 +5,7 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include <mgba/core/log.h>
 
+#include <mgba/core/config.h>
 #include <mgba/core/thread.h>
 
 #define MAX_CATEGORY 64
@@ -36,7 +37,7 @@ int mLogGenerateCategory(const char* name, const char* id) {
 		_categoryIds[_category] = id;
 	}
 	++_category;
-	return _category;
+	return _category - 1;
 }
 
 const char* mLogCategoryName(int category) {
@@ -68,7 +69,9 @@ void mLog(int category, enum mLogLevel level, const char* format, ...) {
 	va_list args;
 	va_start(args, format);
 	if (context) {
-		context->log(context, category, level, format, args);
+		if (!context->filter || mLogFilterTest(context->filter, category, level)) {
+			context->log(context, category, level, format, args);
+		}
 	} else {
 		printf("%s: ", mLogCategoryName(category));
 		vprintf(format, args);
@@ -77,4 +80,67 @@ void mLog(int category, enum mLogLevel level, const char* format, ...) {
 	va_end(args);
 }
 
+void mLogFilterInit(struct mLogFilter* filter) {
+	HashTableInit(&filter->categories, 8, NULL);
+	TableInit(&filter->levels, 8, NULL);
+}
+
+void mLogFilterDeinit(struct mLogFilter* filter) {
+	HashTableDeinit(&filter->categories);
+	TableDeinit(&filter->levels);
+}
+
+static void _setFilterLevel(const char* key, const char* value, enum mCoreConfigLevel level, void* user) {
+	UNUSED(level);
+	struct mLogFilter* filter = user;
+	key = strchr(key, '.');
+	if (!key || !key[1]) {
+		return;
+	}
+	if (!value) {
+		return;
+	}
+	++key;
+	char* end;
+	int ivalue = strtol(value, &end, 10);
+	if (ivalue == 0) {
+		ivalue = INT_MIN; // Zero is reserved
+	}
+	if (!end) {
+		return;
+	}
+	mLogFilterSet(filter, key, ivalue);
+}
+
+void mLogFilterLoad(struct mLogFilter* filter, const struct mCoreConfig* config) {
+	mCoreConfigEnumerate(config, "logLevel.", _setFilterLevel, filter);
+	filter->defaultLevels = mLOG_ALL;
+	mCoreConfigGetIntValue(config, "logLevel", &filter->defaultLevels);
+}
+
+void mLogFilterSet(struct mLogFilter* filter, const char* category, int levels) {
+	HashTableInsert(&filter->categories, category, (void*)(intptr_t) levels);
+	// Can't do this eagerly because not all categories are initialized immediately
+	int cat = mLogCategoryById(category);
+	if (cat >= 0) {
+		TableInsert(&filter->levels, cat, (void*)(intptr_t) levels);
+	}
+
+}
+bool mLogFilterTest(struct mLogFilter* filter, int category, enum mLogLevel level) {
+	int value = (int) TableLookup(&filter->levels, category);
+	if (value) {
+		return value & level;
+	}
+	const char* cat = mLogCategoryId(category);
+	if (cat) {
+		value = (int) HashTableLookup(&filter->categories, cat);
+		if (value) {
+			TableInsert(&filter->levels, category, (void*)(intptr_t) value);
+			return value & level;
+		}
+	}
+	return level & filter->defaultLevels;
+}
+
 mLOG_DEFINE_CATEGORY(STATUS, "Status", "core.status")
diff --git a/src/core/thread.c b/src/core/thread.c
index 3ed5aeb1c..686330615 100644
--- a/src/core/thread.c
+++ b/src/core/thread.c
@@ -36,6 +36,8 @@ static BOOL CALLBACK _createTLS(PINIT_ONCE once, PVOID param, PVOID* context) {
 }
 #endif
 
+static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args);
+
 static void _changeState(struct mCoreThread* threadContext, enum mCoreThreadState newState, bool broadcast) {
 	MutexLock(&threadContext->stateMutex);
 	threadContext->state = newState;
@@ -147,6 +149,13 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
 	core->setSync(core, &threadContext->sync);
 	core->reset(core);
 
+	struct mLogFilter filter;
+	if (!threadContext->logger.d.filter) {
+		threadContext->logger.d.filter = &filter;
+		mLogFilterInit(threadContext->logger.d.filter);
+		mLogFilterLoad(threadContext->logger.d.filter, &core->config);
+	}
+
 	if (core->opts.rewindEnable && core->opts.rewindBufferCapacity > 0) {
 		 mCoreRewindContextInit(&threadContext->rewind, core->opts.rewindBufferCapacity);
 		 threadContext->rewind.stateFlags = core->opts.rewindSave ? SAVESTATE_SAVEDATA : 0;
@@ -225,13 +234,18 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
 	}
 	core->clearCoreCallbacks(core);
 
+	threadContext->logger.d.filter = NULL;
+
 	return 0;
 }
 
 bool mCoreThreadStart(struct mCoreThread* threadContext) {
 	threadContext->state = THREAD_INITIALIZED;
 	threadContext->logger.p = threadContext;
-	threadContext->logLevel = threadContext->core->opts.logLevel;
+	if (!threadContext->logger.d.log) {
+		threadContext->logger.d.log = _mCoreLog;
+		threadContext->logger.d.filter = NULL;
+	}
 
 	if (!threadContext->sync.fpsTarget) {
 		threadContext->sync.fpsTarget = _defaultFPSTarget;
@@ -544,10 +558,7 @@ struct mCoreThread* mCoreThreadGet(void) {
 
 static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args) {
 	UNUSED(logger);
-	struct mCoreThread* thread = mCoreThreadGet();
-	if (thread && !(thread->logLevel & level)) {
-		return;
-	}
+	UNUSED(level);
 	printf("%s: ", mLogCategoryName(category));
 	vprintf(format, args);
 	printf("\n");
@@ -556,9 +567,6 @@ static void _mCoreLog(struct mLogger* logger, int category, enum mLogLevel level
 struct mLogger* mCoreThreadLogger(void) {
 	struct mCoreThread* thread = mCoreThreadGet();
 	if (thread) {
-		if (!thread->logger.d.log) {
-			thread->logger.d.log = _mCoreLog;
-		}
 		return &thread->logger.d;
 	}
 	return NULL;