summaryrefslogtreecommitdiff
path: root/source/logging
diff options
context:
space:
mode:
Diffstat (limited to 'source/logging')
-rw-r--r--source/logging/ErrorReporter.c286
-rw-r--r--source/logging/ErrorReporter.h111
-rw-r--r--source/logging/EventLogger.c398
-rw-r--r--source/logging/EventLogger.h219
-rw-r--r--source/logging/LogPrinter.c144
-rw-r--r--source/logging/LogPrinter.h133
6 files changed, 1291 insertions, 0 deletions
diff --git a/source/logging/ErrorReporter.c b/source/logging/ErrorReporter.c
new file mode 100644
index 0000000..29f6ac7
--- /dev/null
+++ b/source/logging/ErrorReporter.c
@@ -0,0 +1,286 @@
+//
+// ErrorReport.c - MrsWatson
+// Created by Nik Reiman on 9/22/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "app/BuildInfo.h"
+#include "base/File.h"
+#include "base/PlatformInfo.h"
+#include "logging/ErrorReporter.h"
+#include "logging/EventLogger.h"
+
+#if WINDOWS
+#include <Shlobj.h>
+#endif
+
+static const char *kErrorReportInfoText = "MrsWatson is now running \
+in error report mode, which will generate a report on your desktop with \
+any input/output sources and error logs. This also enables some extra \
+arguments and will disable console logging.\n";
+static const char *kErrorReportCopyPluginsPromptText = "Would you like to copy the \
+plugin to the report? All data sent to the official support address is kept \
+strictly confidential. If the plugin in question has copy protection (or is \
+cracked), or depends on external resources, this probably won't work. But if \
+the plugin can be copied, it greatly helps in fixing bugs.\n\
+Copy the plugin? (y/n) ";
+
+
+ErrorReporter newErrorReporter(void)
+{
+ ErrorReporter errorReporter = (ErrorReporter)malloc(sizeof(ErrorReporterMembers));
+
+ errorReporter->started = false;
+ errorReporter->completed = false;
+ errorReporter->reportName = newCharString();
+
+ errorReporter->desktopPath = newCharString();
+ errorReporter->reportDirPath = newCharString();
+
+ return errorReporter;
+}
+
+void errorReporterInitialize(ErrorReporter self)
+{
+ CharString infoText = newCharStringWithCString(kErrorReportInfoText);
+ CharString wrappedInfoText;
+ time_t now;
+ size_t length;
+ size_t i;
+
+ printf("=== Starting error report ===\n");
+ wrappedInfoText = charStringWrap(infoText, 0);
+ // The second newline here is intentional
+ printf("%s\n", wrappedInfoText->data);
+
+ time(&now);
+ self->started = true;
+
+ snprintf(self->reportName->data, self->reportName->capacity,
+ "MrsWatson Report %s", ctime(&now));
+ // Trim the final newline character from this string if it exists
+ length = strlen(self->reportName->data);
+
+ if (self->reportName->data[length - 1] == '\n') {
+ self->reportName->data[length - 1] = '\0';
+ length--;
+ }
+
+ for (i = 0; i < length; i++) {
+ if (!(charStringIsLetter(self->reportName, i) ||
+ charStringIsNumber(self->reportName, i))) {
+ self->reportName->data[i] = '-';
+ }
+ }
+
+#if UNIX
+ snprintf(self->desktopPath->data, self->desktopPath->capacity,
+ "%s/Desktop", getenv("HOME"));
+#elif WINDOWS
+ SHGetFolderPathA(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, self->desktopPath->data);
+#endif
+
+ // Try to place the report on the user's desktop. However, if we cannot find
+ // the desktop (which may occur with localized Linux installations, for instance),
+ // then just dump it in the current directory instead.
+ File desktopPath = newFileWithPath(self->desktopPath);
+ File reportPath;
+
+ if (!fileExists(desktopPath)) {
+ logWarn("Could not find desktop location, placing error report in current directory instead");
+ CharString currentDirString = fileGetCurrentDirectory();
+ File currentDir = newFileWithPath(currentDirString);
+ reportPath = newFileWithParent(currentDir, self->reportName);
+ freeFile(currentDir);
+ freeCharString(currentDirString);
+ } else {
+ reportPath = newFileWithParent(desktopPath, self->reportName);
+ freeFile(desktopPath);
+ }
+
+ if (fileExists(reportPath)) {
+ logCritical("The path '%s' already contains a previous error report. Please remove the report data and try again.");
+ } else {
+ fileCreate(reportPath, kFileTypeDirectory);
+ }
+
+ // Now we should have a real error report path
+ charStringCopy(self->reportDirPath, reportPath->absolutePath);
+
+ freeFile(reportPath);
+ freeCharString(wrappedInfoText);
+ freeCharString(infoText);
+}
+
+void errorReporterCreateLauncher(ErrorReporter self, int argc, char *argv[])
+{
+ CharString outScriptName = newCharString();
+ FILE *scriptFilePointer;
+ int i;
+
+ charStringCopyCString(outScriptName, "run.sh");
+ errorReporterRemapPath(self, outScriptName);
+ scriptFilePointer = fopen(outScriptName->data, "w");
+ fprintf(scriptFilePointer, "#!/bin/sh\n");
+ fprintf(scriptFilePointer, "mrswatson");
+
+ for (i = 1; i < argc; i++) {
+ // Don't run with the error report option again
+ if (strcmp(argv[i], "--error-report")) {
+ fprintf(scriptFilePointer, " %s", argv[i]);
+ }
+ }
+
+ fprintf(scriptFilePointer, "\n");
+ fclose(scriptFilePointer);
+}
+
+void errorReporterRemapPath(ErrorReporter self, CharString path)
+{
+ File pathAsFile = newFileWithPath(path);
+ CharString basename = fileGetBasename(pathAsFile);
+ File parent = newFileWithPath(self->reportDirPath);
+ File remappedPath = newFileWithParent(parent, basename);
+
+ charStringCopy(path, remappedPath->absolutePath);
+
+ freeCharString(basename);
+ freeFile(parent);
+ freeFile(pathAsFile);
+ freeFile(remappedPath);
+}
+
+boolByte errorReportCopyFileToReport(ErrorReporter self, CharString path)
+{
+ boolByte success;
+
+ // Copy the destination path so that the original is not modified
+ CharString destination = newCharString();
+ charStringCopy(destination, path);
+ errorReporterRemapPath(self, destination);
+
+ File reportDirPath = newFileWithPath(self->reportDirPath);
+ File distinationPath = newFileWithPath(path);
+ File result = fileCopyTo(distinationPath, reportDirPath);
+ success = fileExists(result);
+
+ freeCharString(destination);
+ freeFile(reportDirPath);
+ freeFile(distinationPath);
+ freeFile(result);
+ return success;
+}
+
+// This could live in File, however this is currently the only place it is being used
+// and also it's a rather cheap hack, so I would prefer to keep it as a static function
+// here until another use-case presents itself. If that should happen, then we should
+// refactor this code properly and move it to File.
+static boolByte _copyDirectoryToErrorReportDir(ErrorReporter self, CharString path)
+{
+ boolByte success;
+
+#if UNIX
+ int result;
+ CharString copyCommand = newCharString();
+ // TODO: This is the lazy way of doing this...
+ snprintf(copyCommand->data, copyCommand->capacity, "/bin/cp -r \"%s\" \"%s\"",
+ path->data, self->reportDirPath->data);
+ result = system(copyCommand->data);
+ success = (boolByte)(WEXITSTATUS(result) == 0);
+
+ if (!success) {
+ logError("Could not copy '%s' to '%s'\n", path->data, self->reportDirPath->data);
+ }
+
+#else
+ logUnsupportedFeature("Copy directory recursively");
+ success = false;
+#endif
+
+ return success;
+}
+
+boolByte errorReporterShouldCopyPlugins(void)
+{
+ CharString promptText = newCharStringWithCString(kErrorReportCopyPluginsPromptText);
+ CharString wrappedPromptText;
+ char response;
+
+ wrappedPromptText = charStringWrap(promptText, 0);
+ printf("%s", wrappedPromptText->data);
+ freeCharString(wrappedPromptText);
+ freeCharString(promptText);
+
+ response = (char)getchar();
+ return (boolByte)(response == 'y' || response == 'Y');
+}
+
+boolByte errorReporterCopyPlugins(ErrorReporter self, PluginChain pluginChain)
+{
+ CharString pluginAbsolutePath = NULL;
+ Plugin currentPlugin = NULL;
+ boolByte failed = false;
+ unsigned int i;
+ PlatformInfo platform = newPlatformInfo();
+
+ for (i = 0; i < pluginChain->numPlugins; i++) {
+ currentPlugin = pluginChain->plugins[i];
+ pluginAbsolutePath = currentPlugin->pluginAbsolutePath;
+
+ if (charStringIsEmpty(pluginAbsolutePath)) {
+ logInfo("Plugin '%s' does not have an absolute path and could not be copied", currentPlugin->pluginName->data);
+ } else if (platform->type == PLATFORM_MACOSX) {
+ failed |= !_copyDirectoryToErrorReportDir(self, pluginAbsolutePath);
+ } else {
+ failed |= !errorReportCopyFileToReport(self, pluginAbsolutePath);
+ }
+ }
+
+ freePlatformInfo(platform);
+ return (boolByte)!failed;
+}
+
+void errorReporterClose(ErrorReporter self)
+{
+ // Always do this, just in case
+ flushErrorLog();
+
+ printf("\n=== Error report complete ===\n");
+ printf("Created error report at %s\n", self->reportDirPath->data);
+ printf("Please compress and email the report to: %s\n", SUPPORT_EMAIL);
+ printf("Thanks!\n");
+}
+
+void freeErrorReporter(ErrorReporter errorReporter)
+{
+ freeCharString(errorReporter->reportName);
+ freeCharString(errorReporter->reportDirPath);
+ freeCharString(errorReporter->desktopPath);
+ free(errorReporter);
+}
diff --git a/source/logging/ErrorReporter.h b/source/logging/ErrorReporter.h
new file mode 100644
index 0000000..74b4012
--- /dev/null
+++ b/source/logging/ErrorReporter.h
@@ -0,0 +1,111 @@
+//
+// ErrorReporter.h - MrsWatson
+// Created by Nik Reiman on 9/22/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MrsWatson_ErrorReporter_h
+#define MrsWatson_ErrorReporter_h
+
+#include "base/CharString.h"
+#include "plugin/PluginChain.h"
+
+typedef struct {
+ CharString reportName;
+ CharString reportDirPath;
+ CharString desktopPath;
+ boolByte started;
+ boolByte completed;
+} ErrorReporterMembers;
+typedef ErrorReporterMembers *ErrorReporter;
+
+/**
+ * Create a new ErrorReporter
+ * @return ErrorReporter object
+ */
+ErrorReporter newErrorReporter(void);
+
+/**
+ * Create the necessary resources used by the ErrorReporter. This is not done
+ * during object construction because large files can be copied and interactive
+ * input requested from the user.
+ * @param self
+ */
+void errorReporterInitialize(ErrorReporter self);
+
+/**
+ * Create a shell script that can be used to launch MrsWatson with the same
+ * arguments which caused the error to happen
+ * @param self
+ * @param argc Number of arguments, as taken from main()
+ * @param argv Argument array, as taken from main()
+ */
+void errorReporterCreateLauncher(ErrorReporter self, int argc, char *argv[]);
+
+/**
+ * Remap a resource to point to the ErrorReporter's directory. This ensures all
+ * resources are contained within the same folder, and can be easily compressed
+ * for sending after the program finishes executing.
+ * @param self
+ * @param path Input path to be remapped
+ */
+void errorReporterRemapPath(ErrorReporter self, CharString path);
+
+/**
+ * Copy a file to the ErrorReporter's directory
+ * @param self
+ * @param path File to copy
+ * @return True if the file successfully copied
+ */
+boolByte errorReportCopyFileToReport(ErrorReporter self, CharString path);
+
+/**
+ * Ask the user if plugins should be copied to the report directory in addition
+ * to other files. Because there can be copy protection issues, not to mention
+ * file sizes, it is better to ask the user before copying the plugins.
+ * @return True if the user wishes to copy all plugins
+ */
+boolByte errorReporterShouldCopyPlugins(void);
+
+/**
+ * Copy all plug-ins to the ErrorReporter directory
+ * @param self
+ * @param pluginChain Initialized plugin chain
+ * @return True if all plugins were copied
+ */
+boolByte errorReporterCopyPlugins(ErrorReporter self, PluginChain pluginChain);
+
+/**
+ * Close any resources associated with the ErrorReporter
+ * @param self
+ */
+void errorReporterClose(ErrorReporter self);
+
+/**
+ * Free all memory associated with an ErrorReporter instance
+ * @param self
+ */
+void freeErrorReporter(ErrorReporter self);
+
+#endif
diff --git a/source/logging/EventLogger.c b/source/logging/EventLogger.c
new file mode 100644
index 0000000..dc9b6a9
--- /dev/null
+++ b/source/logging/EventLogger.c
@@ -0,0 +1,398 @@
+//
+// EventLogger.c - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "app/BuildInfo.h"
+#include "audio/AudioSettings.h"
+#include "logging/EventLogger.h"
+#include "logging/LogPrinter.h"
+#include "time/AudioClock.h"
+
+#include "MrsWatson.h"
+
+#if WINDOWS
+#include <Windows.h>
+#include <io.h>
+#elif UNIX
+#include <unistd.h>
+#endif
+
+EventLogger eventLoggerInstance = NULL;
+
+void initEventLogger(void)
+{
+#if WINDOWS
+ ULONGLONG currentTime;
+#else
+ struct timeval currentTime;
+#endif
+
+ eventLoggerInstance = (EventLogger)malloc(sizeof(EventLoggerMembers));
+ eventLoggerInstance->logLevel = LOG_INFO;
+ eventLoggerInstance->logFile = NULL;
+ eventLoggerInstance->useColor = false;
+ eventLoggerInstance->zebraStripeSize = (unsigned long)DEFAULT_SAMPLE_RATE;
+ eventLoggerInstance->systemErrorMessage = NULL;
+
+#if WINDOWS
+ currentTime = GetTickCount();
+ eventLoggerInstance->startTimeInSec = (unsigned long)(currentTime / 1000);
+ eventLoggerInstance->startTimeInMs = (unsigned long)currentTime;
+#else
+ gettimeofday(&currentTime, NULL);
+ eventLoggerInstance->startTimeInSec = currentTime.tv_sec;
+ eventLoggerInstance->startTimeInMs = currentTime.tv_usec / 1000;
+#endif
+
+ if (isatty(1)) {
+ eventLoggerInstance->useColor = true;
+ }
+}
+
+static EventLogger _getEventLoggerInstance(void)
+{
+ return eventLoggerInstance;
+}
+
+char *stringForLastError(int errorNumber)
+{
+ EventLogger eventLogger = _getEventLoggerInstance();
+
+ if (eventLogger->systemErrorMessage == NULL) {
+ eventLogger->systemErrorMessage = newCharString();
+ }
+
+#if UNIX
+ return strerror(errorNumber);
+#elif WINDOWS
+ FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, errorNumber, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ eventLogger->systemErrorMessage->data, eventLogger->systemErrorMessage->capacity - 1, NULL);
+ return eventLogger->systemErrorMessage->data;
+#endif
+
+}
+
+boolByte isLogLevelAtLeast(LogLevel logLevel)
+{
+ EventLogger eventLogger = _getEventLoggerInstance();
+ return (boolByte)(logLevel >= eventLogger->logLevel);
+}
+
+void setLogLevel(LogLevel logLevel)
+{
+ EventLogger eventLogger = _getEventLoggerInstance();
+ eventLogger->logLevel = logLevel;
+}
+
+void setLogLevelFromString(const CharString logLevelString)
+{
+ if (charStringIsEqualToCString(logLevelString, "debug", true)) {
+ setLogLevel(LOG_DEBUG);
+ } else if (charStringIsEqualToCString(logLevelString, "info", true)) {
+ setLogLevel(LOG_INFO);
+ } else if (charStringIsEqualToCString(logLevelString, "warn", true)) {
+ setLogLevel(LOG_WARN);
+ } else if (charStringIsEqualToCString(logLevelString, "error", true)) {
+ setLogLevel(LOG_ERROR);
+ } else {
+ logCritical("Invalid log level '%s', see '--help full' for valid arguments", logLevelString->data);
+ }
+}
+
+void setLogFile(const CharString logFileName)
+{
+ EventLogger eventLogger = _getEventLoggerInstance();
+ eventLogger->logFile = fopen(logFileName->data, "a");
+
+ if (eventLogger->logFile == NULL) {
+ logCritical("Could not open file '%s' for logging", logFileName->data);
+ } else {
+ eventLogger->useColor = false;
+ }
+}
+
+void setLoggingColorEnabled(boolByte useColor)
+{
+ EventLogger eventLogger = _getEventLoggerInstance();
+ eventLogger->useColor = useColor;
+}
+
+void setLoggingColorEnabledWithString(const CharString colorSchemeName)
+{
+ if (charStringIsEmpty(colorSchemeName)) {
+ setLoggingColorEnabled(false);
+ } else if (charStringIsEqualToCString(colorSchemeName, "none", false)) {
+ setLoggingColorEnabled(false);
+ } else if (charStringIsEqualToCString(colorSchemeName, "auto", false)) {
+ setLoggingColorEnabled((boolByte)isatty(1));
+ } else if (charStringIsEqualToCString(colorSchemeName, "force", false)) {
+ setLoggingColorEnabled(true);
+ } else {
+ // Use critical log level to avoid colors
+ logCritical("Unknown color scheme '%s'", colorSchemeName->data);
+ }
+}
+
+void setLoggingZebraSize(const unsigned long zebraStripeSize)
+{
+ EventLogger eventLogger = _getEventLoggerInstance();
+ eventLogger->zebraStripeSize = zebraStripeSize;
+}
+
+static char _logLevelStatusChar(const LogLevel logLevel)
+{
+ switch (logLevel) {
+ case LOG_DEBUG:
+ return 'D';
+
+ case LOG_INFO:
+ return '-';
+
+ case LOG_WARN:
+ return 'W';
+
+ case LOG_ERROR:
+ return 'E';
+
+ default:
+ return '!';
+ }
+}
+
+static LogColor _logLevelStatusColor(const LogLevel logLevel)
+{
+ switch (logLevel) {
+ case LOG_DEBUG:
+ return COLOR_FG_DKGRAY;
+
+ case LOG_INFO:
+ return COLOR_RESET;
+
+ case LOG_WARN:
+ return COLOR_FG_YELLOW;
+
+ case LOG_ERROR:
+ return COLOR_FG_RED;
+
+ default:
+ return COLOR_RESET;
+ }
+}
+
+static LogColor _logTimeColor(void)
+{
+ return COLOR_FG_CYAN;
+}
+
+static LogColor _logTimeZebraStripeColor(const long elapsedTime, const unsigned long zebraSizeInMs)
+{
+ boolByte zebraState = (boolByte)((elapsedTime / zebraSizeInMs) % 2);
+ return zebraState ? COLOR_FG_OLIVE : COLOR_FG_GREEN;
+}
+
+static void _printMessage(const LogLevel logLevel, const long elapsedTimeInMs, const long numFramesProcessed, const char *message, const EventLogger eventLogger)
+{
+ char *logString = (char *)malloc(sizeof(char) * kCharStringLengthLong);
+
+ if (eventLogger->useColor) {
+ snprintf(logString, kCharStringLengthLong, "%c ", _logLevelStatusChar(logLevel));
+ printToLog(_logLevelStatusColor(logLevel), eventLogger->logFile, logString);
+ snprintf(logString, kCharStringLengthLong, "%08ld ", numFramesProcessed);
+ printToLog(_logTimeZebraStripeColor(numFramesProcessed, eventLogger->zebraStripeSize),
+ eventLogger->logFile, logString);
+ snprintf(logString, kCharStringLengthLong, "%06ld ", elapsedTimeInMs);
+ printToLog(_logTimeColor(), eventLogger->logFile, logString);
+ printToLog(_logLevelStatusColor(logLevel), eventLogger->logFile, message);
+ } else {
+ snprintf(logString, kCharStringLengthLong, "%c %08ld %06ld %s", _logLevelStatusChar(logLevel), numFramesProcessed, elapsedTimeInMs, message);
+ printToLog(COLOR_NONE, eventLogger->logFile, logString);
+ }
+
+ flushLog(eventLogger->logFile);
+ free(logString);
+}
+
+static void _logMessage(const LogLevel logLevel, const char *message, va_list arguments)
+{
+ long elapsedTimeInMs;
+ EventLogger eventLogger = _getEventLoggerInstance();
+#if WINDOWS
+ ULONGLONG currentTime;
+#else
+ struct timeval currentTime;
+#endif
+
+ if (eventLogger != NULL && logLevel >= eventLogger->logLevel) {
+ CharString formattedMessage = newCharStringWithCapacity(kCharStringLengthDefault * 2);
+ vsnprintf(formattedMessage->data, formattedMessage->capacity, message, arguments);
+#if WINDOWS
+ currentTime = GetTickCount();
+ elapsedTimeInMs = (unsigned long)(currentTime - eventLogger->startTimeInMs);
+#else
+ gettimeofday(&currentTime, NULL);
+ elapsedTimeInMs = ((currentTime.tv_sec - (eventLogger->startTimeInSec + 1)) * 1000) +
+ (currentTime.tv_usec / 1000) + (1000 - eventLogger->startTimeInMs);
+#endif
+ _printMessage(logLevel, elapsedTimeInMs, getAudioClock()->currentFrame, formattedMessage->data, eventLogger);
+ freeCharString(formattedMessage);
+ }
+}
+
+void logDebug(const char *message, ...)
+{
+ va_list arguments;
+ va_start(arguments, message);
+ _logMessage(LOG_DEBUG, message, arguments);
+ va_end(arguments);
+}
+
+void logInfo(const char *message, ...)
+{
+ va_list arguments;
+ va_start(arguments, message);
+ _logMessage(LOG_INFO, message, arguments);
+ va_end(arguments);
+}
+
+void logWarn(const char *message, ...)
+{
+ va_list arguments;
+ va_start(arguments, message);
+ _logMessage(LOG_WARN, message, arguments);
+ va_end(arguments);
+}
+
+void logError(const char *message, ...)
+{
+ va_list arguments;
+ va_start(arguments, message);
+ _logMessage(LOG_ERROR, message, arguments);
+ va_end(arguments);
+}
+
+void logCritical(const char *message, ...)
+{
+ va_list arguments;
+ CharString formattedMessage = newCharString();
+ CharString wrappedMessage;
+
+ va_start(arguments, message);
+ // Instead of going through the common logging method, we always dump critical
+ // messages to stderr
+ vsnprintf(formattedMessage->data, formattedMessage->capacity, message, arguments);
+ wrappedMessage = charStringWrap(formattedMessage, 0);
+ fprintf(stderr, "ERROR: %s\n", wrappedMessage->data);
+
+ if (eventLoggerInstance != NULL && eventLoggerInstance->logFile != NULL) {
+ fprintf(eventLoggerInstance->logFile, "ERROR: %s\n", wrappedMessage->data);
+ }
+
+ freeCharString(formattedMessage);
+ freeCharString(wrappedMessage);
+ va_end(arguments);
+}
+
+void logInternalError(const char *message, ...)
+{
+ va_list arguments;
+ CharString formattedMessage = newCharString();
+
+ va_start(arguments, message);
+ // Instead of going through the common logging method, we always dump critical messages to stderr
+ vsnprintf(formattedMessage->data, formattedMessage->capacity, message, arguments);
+ fprintf(stderr, "INTERNAL ERROR: %s\n", formattedMessage->data);
+
+ if (eventLoggerInstance != NULL && eventLoggerInstance->logFile != NULL) {
+ fprintf(eventLoggerInstance->logFile, "INTERNAL ERROR: %s\n", formattedMessage->data);
+ }
+
+ freeCharString(formattedMessage);
+ va_end(arguments);
+
+ fprintf(stderr, " This should not have happened. Please take a minute to report a bug.\n");
+ fprintf(stderr, " Support website: %s\n", SUPPORT_WEBSITE);
+ fprintf(stderr, " Support email: %s\n", SUPPORT_EMAIL);
+}
+
+void logUnsupportedFeature(const char *featureName)
+{
+ fprintf(stderr, "UNSUPPORTED FEATURE: %s\n", featureName);
+ fprintf(stderr, " This feature is not yet supported. Please help us out and submit a patch! :)\n");
+ fprintf(stderr, " Project website: %s\n", PROJECT_WEBSITE);
+ fprintf(stderr, " Support email: %s\n", SUPPORT_EMAIL);
+}
+
+void logDeprecated(const char *functionName, const char *plugin)
+{
+ logWarn("Call to deprecated function '%s' made by plugin '%s'", functionName, plugin);
+}
+
+void logFileError(const char *filename, const char *message)
+{
+ logCritical("Could not parse file '%s'", filename);
+ logCritical("Got error message message: %s", message);
+ logPossibleBug("This file is either corrupt or was parsed incorrectly");
+}
+
+void logPossibleBug(const char *cause)
+{
+ CharString extraText = newCharStringWithCString("If you believe this to be a \
+bug in MrsWatson, please re-run the program with the --error-report option to \
+generate a diagnostic report to send to support.");
+ CharString wrappedCause;
+ CharString wrappedExtraText;
+
+ wrappedCause = charStringWrap(newCharStringWithCString(cause), 0);
+ wrappedExtraText = charStringWrap(extraText, 0);
+ fprintf(stderr, "%s\n", wrappedCause->data);
+ fprintf(stderr, "%s\n", wrappedExtraText->data);
+
+ freeCharString(wrappedCause);
+ freeCharString(wrappedExtraText);
+}
+
+void flushErrorLog(void)
+{
+ if (eventLoggerInstance != NULL && eventLoggerInstance->logFile != NULL) {
+ fflush(eventLoggerInstance->logFile);
+ }
+}
+
+void freeEventLogger(void)
+{
+ if (eventLoggerInstance->logFile != NULL) {
+ fclose(eventLoggerInstance->logFile);
+ }
+
+ freeCharString(eventLoggerInstance->systemErrorMessage);
+ free(eventLoggerInstance);
+ eventLoggerInstance = NULL;
+}
diff --git a/source/logging/EventLogger.h b/source/logging/EventLogger.h
new file mode 100644
index 0000000..ebc294a
--- /dev/null
+++ b/source/logging/EventLogger.h
@@ -0,0 +1,219 @@
+//
+// EventLogger.h - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MrsWatson_EventLogger_h
+#define MrsWatson_EventLogger_h
+
+#include <sys/types.h>
+#include <stdio.h>
+
+#include "base/CharString.h"
+#include "base/Types.h"
+
+typedef enum {
+ LOG_DEBUG,
+ LOG_INFO,
+ LOG_WARN,
+ LOG_ERROR,
+ NUM_LOG_LEVELS
+} LogLevel;
+
+typedef struct {
+ LogLevel logLevel;
+ long startTimeInSec;
+ long startTimeInMs;
+ boolByte useColor;
+ unsigned long zebraStripeSize;
+ FILE *logFile;
+ CharString systemErrorMessage;
+} EventLoggerMembers;
+typedef EventLoggerMembers *EventLogger;
+extern EventLogger eventLoggerInstance;
+
+/**
+ * Initialize the global event logger instance. Unlike other classes, the event
+ * logger exists as a global singleton, since it is called from numerous places
+ * throughout the code base.
+ */
+void initEventLogger(void);
+
+/**
+ * Get a descriptive error message from the operating system.
+ * @param errorNumber Error code from the operating system
+ * @return String description of the error code
+ */
+char *stringForLastError(int errorNumber);
+
+/**
+ * Check if the current log level is at a given level or higher. This is useful
+ * to avoid doing work to generate logging messages which would not actually
+ * be seen by the user.
+ * @param logLevel Target log level
+ * @return True if the logging level is at this level or higher
+ */
+boolByte isLogLevelAtLeast(LogLevel logLevel);
+
+/**
+ * Setup the logging level to be used
+ * @param logLevel Log level
+ */
+void setLogLevel(LogLevel logLevel);
+
+/**
+ * Set the logging level from a human readable string
+ * @param logLevelString Log level as a string. If they given string does not
+ * match any log level, no change is made.
+ */
+void setLogLevelFromString(const CharString logLevelString);
+
+/**
+ * Send all logging outputs tree files instead of standard error
+ * @param logFileName File name to log to. The file will be opened for appending
+ * text, it will not be overwritten.
+ */
+void setLogFile(const CharString logFileName);
+
+/**
+ * Enable or disable the use of color for console logging. By default, the
+ * EventLogger will not use colors if outputting to a non-interactive terminal.
+ * @param useColor True if color should be used
+ */
+void setLoggingColorEnabled(boolByte useColor);
+
+/**
+ * Set the color scheme to be used with a string name. If no valid name is
+ * passed to this method, no action will be taken.
+ * @param colorSchemeName Color scheme string
+ */
+void setLoggingColorEnabledWithString(const CharString colorSchemeName);
+
+/**
+ * When colored logging is enabled, MrsWatson uses different color shades for
+ * the current sample number in the log output. By default, the colors alternate
+ * every one second of processed audio. This makes it much easier to follow the
+ * logging output, and to find a given event in a very large stream of text.
+ * This method allows one to set how often the colors should alternate, instead
+ * of the default rate of one second.
+ * @param zebraStripeSize How long each color should be displayed, in sample
+ * frames
+ */
+void setLoggingZebraSize(const unsigned long zebraStripeSize);
+
+/**
+ * Log a debug message.
+ * @param message Format string, like printf
+ * @param ... Arguments
+ */
+void logDebug(const char *message, ...);
+
+/**
+ * Log a message with regular priority
+ * @param message Format string, like printf
+ * @param ... Arguments
+ */
+void logInfo(const char *message, ...);
+
+/**
+ * Log a warning message
+ * @param message Format string, like printf
+ * @param ... Arguments
+ */
+void logWarn(const char *message, ...);
+
+/**
+ * Log an error message
+ * @param message Format string, like printf
+ * @param ... Arguments
+ */
+void logError(const char *message, ...);
+
+/**
+ * Log a severe error message. Unlike logError, this method does not use colors
+ * or check the log level. It is reserved for messages which must be shown to
+ * the user at all costs.
+ * @param message Format string, like printf
+ * @param ... Arguments
+ */
+void logCritical(const char *message, ...);
+
+/**
+ * Display a message to the user indicating that something terribly wrong has
+ * occurred within the program. This call should be reserved for errors which
+ * are not expected under normal circumstances.
+ * @param message Format string, like printf
+ * @param ... Arguments
+ */
+void logInternalError(const char *message, ...);
+
+/**
+ * Display a message to the user indicating that they have tried to use part of
+ * the program which is incomplete. For example, plugin to host callbacks which
+ * are known to exist but not yet not fully supported. In these cases, we should
+ * let the user know that they are not encountering a bug, and also encourage
+ * contribution to the project by indicating that this is a planned future.
+ * @param featureName Missing feature name
+ */
+void logUnsupportedFeature(const char *featureName);
+
+/**
+ * Used when a plug-in has attempted to utilize a deprecated host feature. In
+ * such cases, undefined behavior in the plugin may result, so it is useful for
+ * the user to know about this, either to tell the plugin's developer or simply
+ * to know that an incompatibility may exist.
+ * @param functionName Deprecated feature name
+ * @param plugin
+ */
+void logDeprecated(const char *functionName, const char *plugin);
+
+/**
+ * Used to indicate that a file may be corrupt or incorrectly parsed. This is
+ * similar to a critical log message, except that extra information about the
+ * file and situation is shown.
+ * @param filename File name
+ * @param message Description of what went wrong
+ */
+void logFileError(const char *filename, const char *message);
+
+/**
+ * Used when a serious error has occurred, and its source is not entirely known.
+ * This occurs usually with segmentation faults or other errors which result in
+ * the program crashing.
+ * @param cause Description of error cause
+ */
+void logPossibleBug(const char *cause);
+
+/**
+ * Flush the contents of the ErrorLogger
+ */
+void flushErrorLog(void);
+
+/**
+ * Free all memory and associated resources from the global EventLogger instance
+ */
+void freeEventLogger(void);
+
+#endif
diff --git a/source/logging/LogPrinter.c b/source/logging/LogPrinter.c
new file mode 100644
index 0000000..87d4bff
--- /dev/null
+++ b/source/logging/LogPrinter.c
@@ -0,0 +1,144 @@
+//
+// LogPrinter.c - MrsWatson
+// Created by Nik Reiman on 10/23/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <stdlib.h>
+
+#include "logging/LogPrinter.h"
+
+#if UNIX
+void printToLog(const char *color, FILE *logFile, const char *message)
+{
+ if (logFile == NULL) {
+ if (color == NULL) {
+ fprintf(stderr, "%s", message);
+ } else {
+ fprintf(stderr, "%s%s%s", color, message, COLOR_RESET);
+ }
+ } else {
+ fprintf(logFile, "%s", message);
+ }
+}
+#elif WINDOWS
+void printToLog(const LogColor color, FILE *logFile, const char *message)
+{
+ static HANDLE consoleHandle = NULL;
+ CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo;
+
+ if (logFile == NULL) {
+ if (color != 0) {
+ if (consoleHandle == NULL) {
+ consoleHandle = GetStdHandle(STD_ERROR_HANDLE);
+ }
+
+ GetConsoleScreenBufferInfo(consoleHandle, &screenBufferInfo);
+ SetConsoleTextAttribute(consoleHandle, color);
+ fprintf(stderr, "%s", message);
+ SetConsoleTextAttribute(consoleHandle, screenBufferInfo.wAttributes);
+ } else {
+ fprintf(stderr, "%s", message);
+ }
+ } else {
+ fprintf(logFile, "%s", message);
+ }
+}
+#endif
+
+void flushLog(FILE *logFile)
+{
+ if (logFile == NULL) {
+ fprintf(stderr, "\n");
+ fflush(stderr);
+ } else {
+ fprintf(logFile, "\n");
+ fflush(logFile);
+ }
+}
+
+void printTestPattern(void)
+{
+ printToLog(COLOR_FG_BLACK, NULL, "COLOR_FG_BLACK");
+ flushLog(NULL);
+ printToLog(COLOR_FG_MAROON, NULL, "COLOR_FG_MAROON");
+ flushLog(NULL);
+ printToLog(COLOR_FG_GREEN, NULL, "COLOR_FG_GREEN");
+ flushLog(NULL);
+ printToLog(COLOR_FG_OLIVE, NULL, "COLOR_FG_OLIVE");
+ flushLog(NULL);
+ printToLog(COLOR_FG_NAVY, NULL, "COLOR_FG_NAVY");
+ flushLog(NULL);
+ printToLog(COLOR_FG_PURPLE, NULL, "COLOR_FG_PURPLE");
+ flushLog(NULL);
+ printToLog(COLOR_FG_TEAL, NULL, "COLOR_FG_TEAL");
+ flushLog(NULL);
+ printToLog(COLOR_FG_GRAY, NULL, "COLOR_FG_GRAY");
+ flushLog(NULL);
+ printToLog(COLOR_FG_DKGRAY, NULL, "COLOR_FG_DKGRAY");
+ flushLog(NULL);
+ printToLog(COLOR_FG_RED, NULL, "COLOR_FG_RED");
+ flushLog(NULL);
+ printToLog(COLOR_FG_YELLOW, NULL, "COLOR_FG_YELLOW");
+ flushLog(NULL);
+ printToLog(COLOR_FG_BLUE, NULL, "COLOR_FG_BLUE");
+ flushLog(NULL);
+ printToLog(COLOR_FG_FUCHSIA, NULL, "COLOR_FG_FUCHSIA");
+ flushLog(NULL);
+ printToLog(COLOR_FG_CYAN, NULL, "COLOR_FG_CYAN");
+ flushLog(NULL);
+
+ printToLog(COLOR_FG_WHITE, NULL, "COLOR_FG_WHITE");
+ flushLog(NULL);
+ printToLog(COLOR_BG_BLACK, NULL, "COLOR_BG_BLACK");
+ flushLog(NULL);
+ printToLog(COLOR_BG_MAROON, NULL, "COLOR_BG_MAROON");
+ flushLog(NULL);
+ printToLog(COLOR_BG_GREEN, NULL, "COLOR_BG_GREEN");
+ flushLog(NULL);
+ printToLog(COLOR_BG_OLIVE, NULL, "COLOR_BG_OLIVE");
+ flushLog(NULL);
+ printToLog(COLOR_BG_NAVY, NULL, "COLOR_BG_NAVY");
+ flushLog(NULL);
+ printToLog(COLOR_BG_PURPLE, NULL, "COLOR_BG_PURPLE");
+ flushLog(NULL);
+ printToLog(COLOR_BG_TEAL, NULL, "COLOR_BG_TEAL");
+ flushLog(NULL);
+ printToLog(COLOR_BG_GRAY, NULL, "COLOR_BG_GRAY");
+ flushLog(NULL);
+ printToLog(COLOR_BG_DKGRAY, NULL, "COLOR_BG_DKGRAY");
+ flushLog(NULL);
+ printToLog(COLOR_BG_RED, NULL, "COLOR_BG_RED");
+ flushLog(NULL);
+ printToLog(COLOR_BG_YELLOW, NULL, "COLOR_BG_YELLOW");
+ flushLog(NULL);
+ printToLog(COLOR_BG_BLUE, NULL, "COLOR_BG_BLUE");
+ flushLog(NULL);
+ printToLog(COLOR_BG_FUCHSIA, NULL, "COLOR_BG_FUCHSIA");
+ flushLog(NULL);
+ printToLog(COLOR_BG_CYAN, NULL, "COLOR_BG_CYAN");
+ flushLog(NULL);
+ printToLog(COLOR_BG_WHITE, NULL, "COLOR_BG_WHITE");
+ flushLog(NULL);
+}
diff --git a/source/logging/LogPrinter.h b/source/logging/LogPrinter.h
new file mode 100644
index 0000000..bed457c
--- /dev/null
+++ b/source/logging/LogPrinter.h
@@ -0,0 +1,133 @@
+//
+// LogPrinter.h - MrsWatson
+// Created by Nik Reiman on 10/23/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#ifndef MrsWatson_LogPrinter_h
+#define MrsWatson_LogPrinter_h
+
+#include <stdio.h>
+
+#if WINDOWS
+#include <Windows.h>
+#endif
+
+#if UNIX
+typedef const char *LogColor;
+
+#define COLOR_RESET "\x1b[0m"
+#define COLOR_NONE NULL
+
+#define COLOR_FG_BLACK "\x1b[30m"
+#define COLOR_FG_MAROON "\x1b[31m"
+#define COLOR_FG_GREEN "\x1b[92m"
+#define COLOR_FG_OLIVE "\x1b[32m"
+#define COLOR_FG_NAVY "\x1b[34m"
+#define COLOR_FG_PURPLE "\x1b[35m"
+#define COLOR_FG_TEAL "\x1b[36m"
+#define COLOR_FG_GRAY "\x1b[37m"
+#define COLOR_FG_DKGRAY "\x1b[90m"
+#define COLOR_FG_RED "\x1b[31m"
+#define COLOR_FG_YELLOW "\x1b[33m"
+#define COLOR_FG_BLUE "\x1b[94m"
+#define COLOR_FG_FUCHSIA "\x1b[95m"
+#define COLOR_FG_CYAN "\x1b[96m"
+#define COLOR_FG_WHITE "\x1b[37m"
+
+#define COLOR_BG_BLACK "\x1b[40m\x1b[37m"
+#define COLOR_BG_MAROON "\x1b[41m\x1b[37m"
+#define COLOR_BG_GREEN "\x1b[102m\x1b[30m"
+#define COLOR_BG_OLIVE "\x1b[42m\x1b[30m"
+#define COLOR_BG_NAVY "\x1b[44m\x1b[37m"
+#define COLOR_BG_PURPLE "\x1b[45m\x1b[37m"
+#define COLOR_BG_TEAL "\x1b[46m\x1b[30m"
+#define COLOR_BG_GRAY "\x1b[47m\x1b[30m"
+#define COLOR_BG_DKGRAY "\x1b[100m\x1b[37m"
+#define COLOR_BG_RED "\x1b[101m\x1b[37m"
+#define COLOR_BG_YELLOW "\x1b[43m\x1b[30m"
+#define COLOR_BG_BLUE "\x1b[104m\x1b[37m"
+#define COLOR_BG_FUCHSIA "\x1b[105m\x1b[30m"
+#define COLOR_BG_CYAN "\x1b[46m\x1b[30m"
+#define COLOR_BG_WHITE "\x1b[47m\x1b[30m"
+
+#elif WINDOWS
+typedef WORD LogColor;
+
+#define COLOR_RESET 0
+#define COLOR_NONE 0
+
+#define COLOR_FG_BLACK 0
+#define COLOR_FG_MAROON 0x05
+#define COLOR_FG_GREEN FOREGROUND_GREEN | FOREGROUND_INTENSITY
+#define COLOR_FG_OLIVE FOREGROUND_GREEN
+#define COLOR_FG_NAVY FOREGROUND_BLUE
+#define COLOR_FG_PURPLE 0x0c
+#define COLOR_FG_TEAL 0x03
+#define COLOR_FG_GRAY 0x07
+#define COLOR_FG_DKGRAY 0x08
+#define COLOR_FG_RED FOREGROUND_RED | FOREGROUND_INTENSITY
+#define COLOR_FG_YELLOW 0x0e
+#define COLOR_FG_BLUE FOREGROUND_BLUE | FOREGROUND_INTENSITY
+#define COLOR_FG_FUCHSIA 0x0b
+#define COLOR_FG_CYAN 0x03 | FOREGROUND_INTENSITY
+#define COLOR_FG_WHITE 0x0f
+
+#define COLOR_BG_BLACK 0
+#define COLOR_BG_MAROON BACKGROUND_RED
+#define COLOR_BG_GREEN BACKGROUND_GREEN | BACKGROUND_INTENSITY
+#define COLOR_BG_OLIVE BACKGROUND_GREEN
+#define COLOR_BG_NAVY BACKGROUND_BLUE
+#define COLOR_BG_PURPLE 0xc0
+#define COLOR_BG_TEAL 0x30
+#define COLOR_BG_GRAY 0x70
+#define COLOR_BG_DKGRAY 0x80
+#define COLOR_BG_RED BACKGROUND_RED | BACKGROUND_INTENSITY
+#define COLOR_BG_YELLOW 0xe0
+#define COLOR_BG_BLUE BACKGROUND_BLUE | BACKGROUND_INTENSITY
+#define COLOR_BG_FUCHSIA 0xb0
+#define COLOR_BG_CYAN 0x30
+#define COLOR_BG_WHITE 0xf0
+#endif
+
+/**
+ * Print a message with colors to the log
+ * @param color Color to use
+ * @param logFile Log file to write to
+ * @param message Message contents
+ */
+void printToLog(const LogColor color, FILE *logFile, const char *message);
+
+/**
+ * Flush the log file to disk
+ * @param logFile Log file
+ */
+void flushLog(FILE *logFile);
+
+/**
+ * Print a test pattern of all known color combinations.
+ */
+void printTestPattern(void);
+
+#endif