summaryrefslogtreecommitdiffhomepage
path: root/ovr_sdk_win_23.0.0/Logging/src/Logging_Library.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ovr_sdk_win_23.0.0/Logging/src/Logging_Library.cpp')
-rw-r--r--ovr_sdk_win_23.0.0/Logging/src/Logging_Library.cpp1537
1 files changed, 1537 insertions, 0 deletions
diff --git a/ovr_sdk_win_23.0.0/Logging/src/Logging_Library.cpp b/ovr_sdk_win_23.0.0/Logging/src/Logging_Library.cpp
new file mode 100644
index 0000000..c1f56f1
--- /dev/null
+++ b/ovr_sdk_win_23.0.0/Logging/src/Logging_Library.cpp
@@ -0,0 +1,1537 @@
+/************************************************************************************
+
+Filename : Logging_Library.cpp
+Content : Logging system
+Created : Oct 26, 2015
+Authors : Chris Taylor
+
+Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
+
+Licensed under the Oculus Master SDK License Version 1.0 (the "License");
+you may not use the Oculus VR Rift SDK except in compliance with the License,
+which is provided at the time of installation or download, or which
+otherwise accompanies this software in either electronic or hard copy form.
+
+You may obtain a copy of the License at
+
+https://developer.oculus.com/licenses/oculusmastersdk-1.0
+
+Unless required by applicable law or agreed to in writing, the Oculus VR SDK
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+************************************************************************************/
+
+#include "Logging/Logging_Library.h"
+#include "Logging/Logging_OutputPlugins.h"
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4530) // C++ exception handler used, but unwind semantics are not enabled
+#endif // _MSC_VER
+
+#include <assert.h>
+#include <inttypes.h>
+#include <string.h>
+#include <time.h>
+#include <algorithm>
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#endif // _MSC_VER
+
+#ifndef _WIN32
+#include <codecvt>
+#include <ctime>
+#include <locale>
+#endif // !_WIN32
+
+namespace ovrlog {
+
+//--------------------------------------------------------------------------------------------------
+// LogTime
+//--------------------------------------------------------------------------------------------------
+
+LogTime GetCurrentLogTime() {
+#if defined(_WIN32)
+ SYSTEMTIME t;
+ ::GetLocalTime(&t);
+ return t;
+#else
+ return std::chrono::system_clock::now();
+#endif
+}
+
+//--------------------------------------------------------------------------------------------------
+// RepeatedMessageManager
+//--------------------------------------------------------------------------------------------------
+
+RepeatedMessageManager::RepeatedMessageManager()
+ : Mutex(),
+ BusyInWrite(false),
+ RecentMessageMap(),
+ RepeatedMessageMap(),
+ RepeatedMessageExceptionSet() {}
+
+void RepeatedMessageManager::PrintDeferredAggregateMessage(
+ OutputWorker* outputWorker,
+ RepeatedMessage& repeatedMessage) {
+ // Don't lock Mutex, as it's expected to already be locked.
+
+ // Add the prefix to repeatedMessage->stream instead of writing it separately because a race
+ // condition could otherwise cause another message to be printed between the two.
+ char prefixMessage[64]; // Impossible to overflow below.
+ size_t prefixLength = snprintf(
+ prefixMessage,
+ sizeof(prefixMessage),
+ "[Aggregated %d times] ",
+ repeatedMessage.aggregatedCount);
+ repeatedMessage.stream.insert(0, prefixMessage, prefixLength);
+
+ // We use WriteOption::DangerouslyIgnoreQueueLimit because it is very unlikely that these
+ // messages could be generated in a runaway fashion. But they are an aggregate and so are more
+ // important that their non-aggregated versions would be.
+ BusyInWrite = true;
+ outputWorker->Write(
+ repeatedMessage.subsystemName.c_str(),
+ repeatedMessage.messageLogLevel,
+ repeatedMessage.stream.c_str(),
+ false,
+ WriteOption::DangerouslyIgnoreQueueLimit);
+ BusyInWrite = false;
+}
+
+RepeatedMessageManager::LogTimeMs RepeatedMessageManager::GetCurrentLogMillisecondTime() {
+ const LogTime currentLogTime = GetCurrentLogTime();
+ const LogTimeMs currentLogTimeMs = LogTimeToMillisecondTime(currentLogTime);
+ return currentLogTimeMs;
+}
+
+RepeatedMessageManager::LogTimeMs RepeatedMessageManager::LogTimeToMillisecondTime(
+ const LogTime& logTime) {
+#if defined(_WIN32)
+ // Time is represented by SYSTEMTIME, which is a calendar time, with 1ms granularity.
+ // We need to quickly subtract two SYSTEMTIMEs, which is hard to do because it contains
+ // year, month, day, hour, minute, second, millisecond components. We could use the Windows
+ // SystemTimeToFileTime function to convert SYSTEMTIME to FILETIME, which is an absolute
+ // time with a single value, but that's an expensive conversion.
+ LogTimeMs logTimeMs = (logTime.wHour * 3600000) + (logTime.wMinute * 60000) +
+ (logTime.wSecond * 1000) + logTime.wMilliseconds;
+ return logTimeMs;
+#else
+ return std::chrono::duration_cast<std::chrono::milliseconds>(logTime.time_since_epoch()).count();
+#endif
+}
+
+int64_t RepeatedMessageManager::GetLogMillisecondTimeDifference(LogTimeMs begin, LogTimeMs end) {
+#if defined(_WIN32)
+ if (end >= begin) // If the day didn't roll over between begin and end...
+ return (end - begin);
+ return (86400000 + (end - begin)); // Else assume exactly one day rolled over.
+#else
+ return (end - begin);
+#endif
+}
+
+RepeatedMessageManager::PrefixHashType RepeatedMessageManager::GetHash(const char* p) {
+ // Fowler / Noll / Vo (FNV) Hash
+ // FNV is a great string hash for reduction of collisions, but the cost benefit is high.
+ // We can get away with a fairly poor string hash here which is very fast.
+ //
+ PrefixHashType hash(2166136261U);
+ const char* pEnd = (p + messagePrefixLength);
+ while (*p && (p < pEnd)) {
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
+ hash ^= (uint8_t)*p++;
+ }
+ return hash;
+
+ // uint32_t hash(0); // To do: See if this hash is too poor.
+ // const char* pEnd = (p + messagePrefixLength);
+ // while(*p && (p < pEnd))
+ // hash += (uint8_t)*p++;
+ // return hash;
+}
+
+RepeatedMessageManager::HandleResult RepeatedMessageManager::HandleMessage(
+ const char* subsystemName,
+ Level messageLogLevel,
+ const char* stream) {
+ std::lock_guard<std::recursive_mutex> lock(Mutex);
+
+ if (BusyInWrite) // If we are here due to our own call of OutputWorker::Write from our Poll func..
+ return HandleResult::Passed;
+
+ PrefixHashType prefixHash = GetHash(stream);
+
+ // Check to see if we have this particular message in an exception list.
+ if (RepeatedMessageExceptionSet.find(prefixHash) != RepeatedMessageExceptionSet.end()) {
+ return HandleResult::Passed;
+ }
+
+ PrefixHashType subsystemNameHash = GetHash(subsystemName);
+ if (RepeatedMessageSubsystemExceptionSet.find(subsystemNameHash) !=
+ RepeatedMessageSubsystemExceptionSet.end()) {
+ return HandleResult::Passed;
+ }
+
+ // We will need the current time below for all pathways.
+ const LogTimeMs currentLogTimeMs = GetCurrentLogMillisecondTime();
+
+ // First look at our repeated messages. This is a container of known repeating messages.
+ auto itRepeated = RepeatedMessageMap.find(prefixHash);
+
+ if (itRepeated != RepeatedMessageMap.end()) { // If this is a message that's already repeating...
+ RepeatedMessage& repeatedMessage = itRepeated->second;
+
+ // Assume subsystemName == repeatedMessage->subsystemName, though theoretically it's possible
+ // that two subsystems generate the same prefix string. Let's worry about that if we see it.
+ //
+ // Assume messageLogLevel == repeatedMessage->messageLogLevel for the purposes of handling
+ // repeated messages. It's possible that a subsystem may generate the same message string
+ // but with different log levels, but we've never seen that, and it may not be significant
+ // with respect to handling repeating anyway. Let's worry about that if we see it.
+ const LogTimeMs logTimeDifferenceMs =
+ GetLogMillisecondTimeDifference(repeatedMessage.lastTimeMs, currentLogTimeMs);
+
+ if (logTimeDifferenceMs < maxDeferrableDetectionTimeMs) { // If this message was soon after
+ repeatedMessage.lastTimeMs = currentLogTimeMs; // the last one...
+
+ // We actually print the first few seemingly repeated messages before deferring them.
+ if (repeatedMessage.printedCount < printedRepeatCount) {
+ repeatedMessage.printedCount++;
+ return HandleResult::Passed;
+ }
+
+ // Else we aggregate it, and won't print it until later with a summary printing.
+ // If repeatedMessage.aggregatedCount >= maxDeferredMessages, then copy stream to
+ // repeatedMessage.stream, in order to print the most recent variation of this repeat when
+ // the aggregated print is done.
+ if (++repeatedMessage.aggregatedCount >= maxDeferredMessages)
+ repeatedMessage.stream = stream;
+
+ return HandleResult::Aggregated;
+ }
+ // Else the repeated message was old and we don't don't consider this a repeat.
+ // Don't erase the entry from RepeatedMessageMap here, as we still need to do a final
+ // print of the aggregated message before removing it. We'll handle that in the Poll function.
+ } else {
+ // Else this message wasn't known to be previously repeating, but maybe it's the first repeat
+ // we are encountering. Check the RecentMessageMap for this.
+ auto itRecent = RecentMessageMap.find(prefixHash);
+
+ if (itRecent != RecentMessageMap.end()) { // If it looks like a repeat of something recent...
+ RepeatedMessageMap[prefixHash] = RepeatedMessage(
+ subsystemName, messageLogLevel, stream, currentLogTimeMs, currentLogTimeMs, 0);
+
+ // No need to keep it in the RecentMessageMap any more, since it's now classified as repeat.
+ RecentMessageMap.erase(itRecent);
+ } else {
+ // Else add it to RecentMessageMap. Old RecentMessageMap entries will be removed by Poll().
+ RecentMessageMap[prefixHash] = RecentMessage{currentLogTimeMs};
+ }
+ }
+
+ return HandleResult::Passed;
+}
+
+void RepeatedMessageManager::Poll(OutputWorker* outputWorker) {
+ std::vector<RepeatedMessage> messagesToPrint;
+
+ {
+ std::lock_guard<std::recursive_mutex> lock(Mutex);
+
+ if (RecentMessageMap.size() > (recentMessageCount * 2)) {
+ // Prune the oldest messages out of RecentMessageMap until
+ // RecentMessageMap.size() == recentMessageCount. Unfortunately, RecentMessageMap is an
+ // unsorted hash container, so we can't quickly find the oldest N messages. We can solve
+ // this by doing a copy of iterators to an array, sort, erase first N iterators. A faster
+ // solution would be to keep a std::queue of iterators that were added to the map, though
+ // that results in some complicated code. Maybe we'll do that, but let's do the sort
+ // solution first.
+ const size_t arrayCapacity = (recentMessageCount * 3);
+ RecentMessageMapType::iterator itArray[arrayCapacity]; // Avoid memory allocation.
+ size_t itArraySize = 0;
+
+ for (RecentMessageMapType::iterator it = RecentMessageMap.begin();
+ (it != RecentMessageMap.end()) && (itArraySize < arrayCapacity);
+ ++it) {
+ itArray[itArraySize++] = it;
+ }
+
+ std::sort(
+ itArray,
+ itArray + itArraySize, // Put the oldest at the end of the array.
+ [](const RecentMessageMapType::iterator& it1,
+ const RecentMessageMapType::iterator& it2) -> bool {
+ return (it2->second.timeMs < it1->second.timeMs); // Sort newest to oldest.
+ });
+
+ for (size_t i = 0; i < itArraySize; ++i)
+ RecentMessageMap.erase(itArray[i]);
+ }
+
+ // Currently we go through the entire RepeatedMessageMap every time we are here, though we
+ // have a purgeDeferredMessageTimeMs constant which we have to make this more granular, for
+ // efficiency purposed. To do.
+ const LogTimeMs currentLogTimeMs = GetCurrentLogMillisecondTime();
+
+ for (auto it = RepeatedMessageMap.begin(); it != RepeatedMessageMap.end();) {
+ RepeatedMessage& repeatedMessage = it->second;
+ LogTimeMs logTimeDifferenceMs =
+ GetLogMillisecondTimeDifference(repeatedMessage.lastTimeMs, currentLogTimeMs);
+
+ // If this message hasn't repeated in a while...
+ if (logTimeDifferenceMs > maxDeferrableDetectionTimeMs) {
+ // Print an aggregate result for this entry before removing it.
+ // Since we have already printed the first <printedRepeatCount> of a given repeated message,
+ // we do an aggregate print only if further printings were called for.
+ if (repeatedMessage.aggregatedCount) { // If there are any aggregated messages deferred...
+ // This will print the first variation of the string that was encountered.
+ // We can move the message instead of copy because we won't need it any more after this.
+ messagesToPrint.emplace_back(std::move(repeatedMessage));
+ }
+
+ it = RepeatedMessageMap.erase(it);
+ continue;
+ } else if (repeatedMessage.aggregatedCount >= maxDeferredMessages) {
+ messagesToPrint.emplace_back(repeatedMessage);
+ repeatedMessage.printedCount += repeatedMessage.aggregatedCount;
+ repeatedMessage.aggregatedCount = 0; // Reset this for a new round of aggregation.
+ }
+ ++it;
+ }
+ } // lock
+
+ // We need to print these messages outside our locked Mutex because printing of these is
+ // calling out to external code which itself has mutexes and thus we need to avoid deadlock.
+ for (auto& repeatedMessage : messagesToPrint)
+ PrintDeferredAggregateMessage(outputWorker, repeatedMessage);
+}
+
+void RepeatedMessageManager::AddRepeatedMessageException(const char* messagePrefix) {
+ std::lock_guard<std::recursive_mutex> lock(Mutex);
+
+ PrefixHashType prefixHash = GetHash(messagePrefix);
+ RepeatedMessageExceptionSet.insert(prefixHash);
+}
+
+void RepeatedMessageManager::RemoveRepeatedMessageException(const char* messagePrefix) {
+ std::lock_guard<std::recursive_mutex> lock(Mutex);
+
+ PrefixHashType prefixHash = GetHash(messagePrefix);
+ auto it = RepeatedMessageExceptionSet.find(prefixHash);
+ if (it != RepeatedMessageExceptionSet.end()) {
+ RepeatedMessageExceptionSet.erase(it);
+ }
+}
+
+void RepeatedMessageManager::AddRepeatedMessageSubsystemException(const char* subsystemName) {
+ std::lock_guard<std::recursive_mutex> lock(Mutex);
+
+ PrefixHashType subsystemNameHash = GetHash(subsystemName);
+ RepeatedMessageSubsystemExceptionSet.insert(subsystemNameHash);
+}
+
+void RepeatedMessageManager::RemoveRepeatedMessageSubsytemException(const char* subsystemName) {
+ std::lock_guard<std::recursive_mutex> lock(Mutex);
+
+ PrefixHashType subsystemNameHash = GetHash(subsystemName);
+ auto it = RepeatedMessageSubsystemExceptionSet.find(subsystemNameHash);
+ if (it != RepeatedMessageExceptionSet.end()) {
+ RepeatedMessageExceptionSet.erase(it);
+ }
+}
+
+// Don't use locks or register channels until OutputWorker::Start has been called
+// Once called the first time, register all known channels and start using locks
+static volatile bool OutputWorkerInstValid = false;
+static ChannelNode* ChannelNodeHead = nullptr;
+void ChannelRegisterNoLock(ChannelNode* channelNode) {
+ if (ChannelNodeHead) {
+ channelNode->Next = ChannelNodeHead;
+ ChannelNodeHead = channelNode;
+ } else {
+ ChannelNodeHead = channelNode;
+ channelNode->Next = nullptr;
+ }
+}
+
+void ChannelRegister(ChannelNode* channelNode) {
+ Locker locker(OutputWorker::GetInstance()->GetChannelsLock());
+ ChannelRegisterNoLock(channelNode);
+ Configurator::GetInstance()->RestoreChannelLogLevel(channelNode);
+}
+
+void ChannelUnregisterNoLock(ChannelNode* channelNode) {
+ if (channelNode == ChannelNodeHead) {
+ ChannelNodeHead = channelNode->Next;
+ return;
+ } else {
+ if (ChannelNodeHead != nullptr) {
+ for (ChannelNode *prev = ChannelNodeHead, *cur = prev->Next; cur != nullptr;
+ prev = cur, cur = cur->Next) {
+ if (cur == channelNode) {
+ prev->Next = channelNode->Next;
+ return;
+ }
+ }
+ }
+ // did not find channel to unregister
+ assert(false);
+ }
+}
+
+void ChannelUnregister(ChannelNode* channelNode) {
+ Locker locker(OutputWorker::GetInstance()->GetChannelsLock());
+
+ ChannelUnregisterNoLock(channelNode);
+}
+
+//-----------------------------------------------------------------------------
+// Export Write() as C API so we can use it across DLL boundaries. Not marking it
+// as DLL visibile / DLL export. Expect it to be passed to DLLs after they are loaded.
+void OutputWorkerOutputFunctionC(
+ const char* subsystemName,
+ Log_Level_t messageLogLevel,
+ const char* stream,
+ bool relogged,
+ Write_Option_t option) {
+ OutputWorker::GetInstance()->Write(
+ subsystemName, (Level)messageLogLevel, stream, relogged, (WriteOption)option);
+}
+
+//-----------------------------------------------------------------------------
+// Channel
+
+OutputWorkerOutputFunctionType Channel::OutputWorkerOutputFunction = OutputWorkerOutputFunctionC;
+
+void Channel::ConfiguratorOnChannelLevelChange(
+ const char* channelName,
+ Log_Level_t minimumOutputLevel) {
+ Configurator::GetInstance()->OnChannelLevelChange(channelName, minimumOutputLevel);
+}
+void Channel::ConfiguratorRegister(ChannelNode* channelNode) {
+ if (OutputWorkerInstValid == false)
+ ChannelRegisterNoLock(channelNode);
+ else
+ ChannelRegister(channelNode);
+}
+void Channel::ConfiguratorUnregister(ChannelNode* channelNode) {
+ if (OutputWorkerInstValid == false)
+ ChannelUnregisterNoLock(channelNode);
+ else
+ ChannelUnregister(channelNode);
+}
+
+//-----------------------------------------------------------------------------
+// Shutdown the logging system and release memory
+void ShutdownLogging() {
+ // This function needs to be robust to multiple calls in a row
+ if (OutputWorkerInstValid) {
+ ovrlog::OutputWorker::GetInstance()->Stop();
+ }
+}
+
+void RestartLogging() {
+ if (OutputWorkerInstValid) {
+ ovrlog::OutputWorker::GetInstance()->Start();
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Log Output Worker Thread
+
+OutputWorker* OutputWorker::GetInstance() {
+ static OutputWorker worker;
+ return &worker;
+}
+
+void OutputWorkerAtExit() {
+ // This function needs to be robust to multiple calls in a row
+ ShutdownLogging();
+}
+
+OutputWorker::OutputWorker()
+ : IsInDebugger(false),
+ DefaultPluginsDisabled(false),
+ PluginsLock(),
+ Plugins(),
+ OutputPluginChannelFiltering(),
+ ChannelOutputPluginFiltering(),
+ WorkQueueLock(),
+ WorkQueueOverrun(0),
+ StartStopLock()
+#if defined(_WIN32)
+ ,
+ WorkerWakeEvent(),
+ WorkerTerminator(),
+ LoggingThread()
+#else
+ ,
+ Terminated(true)
+#endif // defined(_WIN32)
+{
+#if defined(_WIN32)
+ // Create a worker wake event
+ WorkerWakeEvent = ::CreateEventW(nullptr, FALSE, FALSE, nullptr);
+#endif // defined(_WIN32)
+
+ IsInDebugger = IsDebuggerAttached();
+
+ InstallDefaultOutputPlugins();
+
+ OutputWorkerInstValid = true;
+
+ Start();
+}
+
+OutputWorker::~OutputWorker() {
+ Stop();
+ OutputWorkerInstValid = false;
+}
+
+void OutputWorker::InstallDefaultOutputPlugins() {
+ // These are the default outputs for ALL applications:
+
+ // If debugger is *not* attached,
+ if (!IsInDebugger) {
+ // Enable event log output. This logger is fairly slow, taking about 1 millisecond per log,
+ // and this is very expensive to flush after each log message for debugging. Since we almost
+ // never use the Event Log when debugging apps it is better to simply leave it out.
+#ifdef OVR_ENABLE_OS_EVENT_LOG
+ AddPlugin(std::make_shared<OutputEventLog>());
+#endif
+
+ // Do not log to the DbgView output from the worker thread. When a debugger is attached we
+ // instead flush directly to the DbgView log so that the messages are available at breakpoints.
+ AddPlugin(std::make_shared<OutputDbgView>());
+ }
+
+#if defined(_WIN32)
+ // If there is a console window,
+ if (::GetConsoleWindow() != NULL) {
+ bool outputToStdout = false;
+ if (GetEnvironmentVariableW(L"OvrOutputToStdout", NULL, 0) > 0) {
+ outputToStdout = true;
+ }
+
+ // Enable the console. This logger takes 3 milliseconds per message, so it is fairly
+ // slow and should be avoided if it is not needed (ie. console is not shown).
+ AddPlugin(std::make_shared<OutputConsole>(outputToStdout));
+ }
+#endif
+}
+
+void OutputWorker::AddPlugin(std::shared_ptr<OutputPlugin> plugin) {
+ if (!plugin) {
+ return;
+ }
+
+ Locker locker(PluginsLock);
+
+ RemovePlugin(plugin);
+
+ Plugins.insert(plugin);
+}
+
+void OutputWorker::RemovePlugin(std::shared_ptr<OutputPlugin> pluginToRemove) {
+ if (!pluginToRemove) {
+ return;
+ }
+
+ const char* nameOfPluginToRemove = pluginToRemove->GetUniquePluginName();
+
+ Locker locker(PluginsLock);
+
+ for (auto& existingPlugin : Plugins) {
+ const char* existingPluginName = existingPlugin->GetUniquePluginName();
+
+ // If the names match exactly,
+ if (0 == strcmp(nameOfPluginToRemove, existingPluginName)) {
+ Plugins.erase(existingPlugin);
+ break;
+ }
+ }
+}
+
+std::shared_ptr<OutputPlugin> OutputWorker::GetPlugin(const char* const pluginName) {
+ Locker locker(PluginsLock);
+
+ for (auto& existingPlugin : Plugins) {
+ const char* const existingPluginName = existingPlugin->GetUniquePluginName();
+
+ // If the names match exactly,
+ if (0 == strcmp(pluginName, existingPluginName)) {
+ return existingPlugin;
+ }
+ }
+
+ return nullptr;
+}
+
+void OutputWorker::DisableAllPlugins() {
+ Locker locker(PluginsLock);
+
+ DefaultPluginsDisabled = true;
+ Plugins.clear();
+}
+
+Lock* OutputWorker::GetChannelsLock() {
+ return &ChannelsLock;
+}
+
+void OutputWorker::AddRepeatedMessageSubsystemException(const char* subsystemName) {
+ return RepeatedMessageManagerInstance.AddRepeatedMessageSubsystemException(subsystemName);
+}
+
+void OutputWorker::RemoveRepeatedMessageSubsystemException(const char* subsystemName) {
+ return RepeatedMessageManagerInstance.RemoveRepeatedMessageSubsytemException(subsystemName);
+}
+
+void OutputWorker::SetOutputPluginChannels(
+ const char* outputPluginName,
+ const std::vector<std::string>& channelNames) {
+ Locker locker(PluginsLock);
+
+ if (channelNames.empty())
+ OutputPluginChannelFiltering.erase(outputPluginName);
+ else {
+ OutputPluginChannelFiltering[outputPluginName].clear();
+ OutputPluginChannelFiltering[outputPluginName].insert(channelNames.begin(), channelNames.end());
+ }
+}
+
+void OutputWorker::SetChannelOutputPlugins(
+ const char* channelName,
+ const std::vector<std::string>& outputPluginNames) {
+ Locker locker(PluginsLock);
+
+ if (outputPluginNames.empty())
+ OutputPluginChannelFiltering.erase(channelName);
+ else {
+ ChannelOutputPluginFiltering[channelName].clear();
+ ChannelOutputPluginFiltering[channelName].insert(
+ outputPluginNames.begin(), outputPluginNames.end());
+ }
+}
+
+void OutputWorker::SetChannelSingleOutput(const char* channelName, const char* outputPluginName) {
+ // We assume a recursive mutex, becasue the called functions also lock this mutex. If the
+ // mutex wasn't recursive then we would immediately find out because this function would
+ // deadlock itself.
+ Locker locker(PluginsLock);
+
+ SetChannelOutputPlugins(channelName, {outputPluginName});
+ SetOutputPluginChannels(outputPluginName, {channelName});
+}
+
+void OutputWorker::Start() {
+ // Hold start-stop lock to prevent Start() and Stop() from being called at the same time.
+ Locker startStopLocker(StartStopLock);
+
+ // If already started,
+#if defined(_WIN32)
+ if (LoggingThread.IsValid())
+#else
+ if (LoggingThread.joinable())
+#endif // defined(_WIN32)
+ {
+ return; // Nothing to do!
+ }
+
+ // RestoreAllChannelLogLevelsNoLock is used to address
+ // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm Section 6.7
+ // RestoreAllChannelLogLevelsNoLock otherwise invokes OutputWorker::GetInstance() whose
+ // constructor hasn't finished yet
+ Configurator::GetInstance()->RestoreAllChannelLogLevelsNoLock();
+
+#if defined(_WIN32)
+ if (!WorkerTerminator.Initialize()) {
+ // Unable to create worker terminator event?
+ LOGGING_DEBUG_BREAK();
+ return;
+ }
+
+ LoggingThread = ::CreateThread(
+ nullptr, // No thread security attributes
+ 0, // Default stack size
+ &OutputWorker::WorkerThreadEntrypoint_, // Thread entrypoint
+ this, // This parameter
+ 0, // No creation flags, start immediately
+ nullptr); // Do not request thread id
+
+ if (!LoggingThread.IsValid()) {
+ // Unable to create worker thread?
+ LOGGING_DEBUG_BREAK();
+ return;
+ }
+#else
+ {
+ std::unique_lock<std::mutex> lock(WorkerCvMutex);
+
+ Terminated = false;
+ LoggingThread = std::thread(&OutputWorker::WorkerThreadEntrypoint_, this);
+
+ // make sure our thread has started
+ WorkerCv.wait(lock);
+ }
+#endif // defined(_WIN32) && !defined(OVR_LOGGING_USE_CPP11)
+
+ // Note this may queue more than one OutputWorkerAtExit() call.
+ // This function needs to be robust to multiple calls in a row
+ std::atexit(&OutputWorkerAtExit);
+}
+
+void OutputWorker::Stop() {
+ // This function needs to be robust to multiple calls in a row
+
+ // Hold start-stop lock to prevent Start() and Stop() from being called at the same time.
+ Locker startStopLocker(StartStopLock);
+
+#if defined(_WIN32)
+ if (LoggingThread.IsValid()) {
+ // Flag termination
+ WorkerTerminator.Terminate();
+
+ // Wait for thread to end
+ ::WaitForSingleObject(
+ LoggingThread.Get(), // Thread handle
+ INFINITE); // Wait forever for thread to terminate
+
+ LoggingThread.Clear();
+ }
+#else
+ if (LoggingThread.joinable()) {
+ // Flag termination
+ Terminated = true;
+ {
+ std::unique_lock<std::mutex> lock(WorkerCvMutex);
+ WorkerCv.notify_one();
+ }
+
+ LoggingThread.join();
+ }
+#endif // defined(_WIN32)
+
+ // Hold scoped work queue lock:
+ {
+ // This ensures that logs are not printed out of order on Stop(), and that Flush()
+ // can use the flag to check if a flush has already occurred.
+ Locker workQueueLock(WorkQueueLock);
+
+ // Finish the last set of queued messages to avoid losing any before Stop() returns.
+ ProcessQueuedMessages();
+ }
+}
+
+static int GetTimestamp(char* buffer, int bufferBytes, const LogTime& logTime) {
+#if defined(_WIN32)
+ // GetDateFormat and GetTimeFormat returns the number of characters written to the
+ // buffer if successful, including the trailing '\0'; and return 0 on failure.
+ char dateBuffer[16];
+ int dateBufferLength;
+ int writtenChars =
+ ::GetDateFormatA(LOCALE_USER_DEFAULT, 0, &logTime, "dd/MM ", dateBuffer, sizeof(dateBuffer));
+
+ if (writtenChars <= 0) {
+ // Failure
+ buffer[0] = '\0';
+ return 0;
+ }
+ dateBufferLength = (writtenChars - 1);
+
+ char timeBuffer[32];
+ int timeBufferLength;
+ writtenChars = ::GetTimeFormatA( // Intentionally using 'A' version.
+ LOCALE_USER_DEFAULT, // User locale
+ 0, // Default flags
+ &logTime, // Time
+ "HH:mm:ss",
+ timeBuffer, // Output buffer
+ sizeof(timeBuffer)); // Size of buffer in tchars
+
+ if (writtenChars <= 0) {
+ // Failure
+ buffer[0] = '\0';
+ return 0;
+ }
+ timeBufferLength = (writtenChars - 1);
+
+ // Append milliseconds
+ const char msBuffer[5] = {(char)('.'),
+ (char)(((logTime.wMilliseconds / 100) % 10) + '0'),
+ (char)(((logTime.wMilliseconds / 10) % 10) + '0'),
+ (char)((logTime.wMilliseconds % 10) + '0'),
+ (char)('\0')};
+
+ const int writeSum = (dateBufferLength + timeBufferLength + sizeof(msBuffer));
+
+ if (bufferBytes < writeSum) {
+ buffer[0] = '\0';
+ return 0;
+ }
+
+#if defined(_MSC_VER)
+#pragma warning(push) // We are guaranteed that strcpy is safe.
+#pragma warning(disable : 4996) //'strcpy': This function or variable may be unsafe.
+#endif // defined(_MSC_VER)
+ strcpy(buffer, dateBuffer);
+ strcpy(buffer + dateBufferLength, timeBuffer);
+ strcpy(buffer + dateBufferLength + timeBufferLength, msBuffer);
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif // defined(_MSC_VER)
+
+ return (
+ writeSum -
+ 1); // -1 because we return the strlen of buffer, and don't include the trailing '\0'.
+#else
+ using namespace std::chrono;
+ size_t ms = duration_cast<milliseconds>(logTime.time_since_epoch()).count() % 1000;
+ time_t seconds = system_clock::to_time_t(logTime);
+
+ const char msBuffer[5] = {(char)('.'),
+ (char)(((ms / 100) % 10) + '0'),
+ (char)(((ms / 10) % 10) + '0'),
+ (char)((ms % 10) + '0'),
+ (char)('\0')};
+
+#if defined(_MSC_VER)
+#pragma warning(push) // We are guaranteed that strcpy is safe.
+#pragma warning(disable : 4996) //'strcpy': This function or variable may be unsafe.
+#endif // defined(_MSC_VER)
+ size_t dateTimeBufferLength =
+ std::strftime(buffer, bufferBytes, "%d/%m %H:%M:%S", std::localtime(&seconds));
+ strcpy(buffer + dateTimeBufferLength, msBuffer);
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif // defined(_MSC_VER)
+
+ return (int)(dateTimeBufferLength + sizeof(msBuffer) - 1);
+#endif // defined(_WIN32)
+}
+
+// Returns number of bytes written to buffer
+// Precondition: Buffer is large enough to hold everything,
+// so don't bother complaining there isn't enough length checking.
+static int GetTimestamp(char* buffer, int bufferBytes) {
+ LogTime time = GetCurrentLogTime();
+ return GetTimestamp(buffer, bufferBytes, time);
+}
+
+void OutputWorker::Flush() {
+#if defined(_WIN32)
+ if (!LoggingThread.IsValid())
+#else
+ if (!LoggingThread.joinable())
+#endif // defined(_WIN32)
+ {
+ LOGGING_DEBUG_BREAK(); // Must be called between Start() and Stop()
+ return;
+ }
+
+#if defined(_WIN32)
+ AutoHandle flushEvent;
+#else
+ std::unique_lock<std::mutex> lock(WorkerCvMutex);
+#endif // !defined(_WIN32)
+
+ // Scoped work queue lock:
+ {
+ Locker workQueueLock(WorkQueueLock);
+
+ LogStringBuffer buffer("Logging", ovrlog::Level::Info);
+ LogTime time = GetCurrentLogTime();
+ QueuedLogMessage* queuedBuffer = new QueuedLogMessage("Logging", ovrlog::Level::Info, "", time);
+#if defined(_WIN32)
+ // Generate a flush event
+ flushEvent = ::CreateEventW(nullptr, FALSE, FALSE, nullptr);
+ queuedBuffer->FlushEvent = flushEvent.Get();
+#else
+ queuedBuffer->FlushEvent = true;
+#endif // defined(_WIN32)
+
+ // Add queued buffer to the end of the work queue
+ WorkQueueAdd(queuedBuffer);
+
+#if defined(_WIN32)
+ // Wake the worker thread
+ ::SetEvent(WorkerWakeEvent.Get());
+#else
+ WorkerCv.notify_one();
+#endif // defined(_WIN32)
+ }
+
+ // Wait until the event signals.
+ // Since we are guaranteed to never lose log messages, as late as Stop() being called,
+ // this cannot cause a hang.
+#if defined(_WIN32)
+ ::WaitForSingleObject(flushEvent.Get(), INFINITE);
+#else
+ WorkerCv.wait(lock);
+#endif // !defined(_WIN32)
+}
+
+static void WriteAdvanceStrCpy(char*& buffer, size_t& bufferBytes, const char* str) {
+ // Get length of string to copy into buffer
+ size_t slen = strlen(str);
+
+ // If the resulting buffer cannot accommodate the string and a null terminator,
+ if (bufferBytes < slen + 1) {
+ // Do nothing
+ return;
+ }
+
+ // Copy string to buffer
+ memcpy(buffer, str, slen);
+
+ // Advance buffer by number of bytes copied
+ buffer += slen;
+ bufferBytes -= slen;
+}
+
+void OutputWorker::AppendHeader(
+ char* buffer,
+ size_t bufferBytes,
+ Level level,
+ const char* subsystemName) {
+ // Writes <L> [SubSystem] to the provided buffer.
+
+ // Based on message log level,
+ const char* initial = "";
+ switch (level) {
+ case Level::Disabled:
+ initial = " {DISABLED}[";
+ break; // This typically should not occur, but we have here for consistency.
+ case Level::Trace:
+ initial = " {TRACE} [";
+ break;
+ case Level::Debug:
+ initial = " {DEBUG} [";
+ break;
+ case Level::Info:
+ initial = " {INFO} [";
+ break;
+ case Level::Warning:
+ initial = " {WARNING} [";
+ break;
+ case Level::Error:
+ initial = " {!ERROR!} [";
+ break;
+ default:
+ initial = " {???} [";
+ break;
+ }
+ static_assert(Level::Count == static_cast<Level>(6), "Needs updating");
+
+ WriteAdvanceStrCpy(buffer, bufferBytes, initial);
+ WriteAdvanceStrCpy(buffer, bufferBytes, subsystemName);
+ WriteAdvanceStrCpy(buffer, bufferBytes, "] ");
+ buffer[0] = '\0';
+}
+
+OVR_THREAD_FUNCTION_TYPE OutputWorker::WorkerThreadEntrypoint_(void* vworker) {
+ // Invoke thread entry-point
+ OutputWorker* worker = reinterpret_cast<OutputWorker*>(vworker);
+ if (worker) {
+ worker->WorkerThreadEntrypoint();
+ }
+ return 0;
+}
+
+void OutputWorker::ProcessQueuedMessages() {
+ // Potentially trigger aggregated repeating messages.
+ RepeatedMessageManagerInstance.Poll(this);
+
+ static const int TempBufferBytes = 1024; // 1 KiB
+ char HeaderBuffer[TempBufferBytes];
+
+ QueuedLogMessage* message = nullptr;
+
+ // Pull messages off the queue
+ int lostCount = 0;
+ {
+ Locker locker(WorkQueueLock);
+ message = WorkQueueHead;
+ WorkQueueHead = WorkQueueTail = nullptr;
+ lostCount = WorkQueueOverrun;
+ WorkQueueOverrun = 0;
+ WorkQueueSize = 0;
+ }
+
+ if (message == nullptr) {
+ // No data to process
+ return;
+ }
+
+ // Log output format:
+ // TIMESTAMP <L> [SubSystem] Message
+
+ // If some messages were lost,
+ if (lostCount > 0) {
+ char str[255];
+ snprintf(
+ str,
+ sizeof(str),
+ "Lost %i log messages due to queue overrun; try to reduce the amount of logging",
+ lostCount);
+
+ // Insert at the front of the list
+ LogTime t = GetCurrentLogTime();
+ QueuedLogMessage* queuedMsg = new QueuedLogMessage("Logging", Level::Error, str, t);
+ queuedMsg->Next = message;
+ message = queuedMsg;
+ }
+
+ {
+ Locker locker(PluginsLock);
+
+ // For each log message,
+ for (QueuedLogMessage* next; message; message = next) {
+ // If the message is a flush event,
+#if defined(_WIN32)
+ if (message->FlushEvent != nullptr)
+#else
+ if (message->FlushEvent)
+#endif // defined(_WIN32)
+ {
+ // Signal it to wake up the waiting Flush() call.
+#if defined(_WIN32)
+ ::SetEvent(message->FlushEvent);
+#else
+ // we are already locked here...
+ WorkerCv.notify_one();
+#endif // defined(_WIN32)
+ } else {
+ std::size_t timestampLength =
+ GetTimestamp(HeaderBuffer, sizeof(HeaderBuffer), message->Time);
+ const char* subsystemName = message->SubsystemName.Get(); // a.k.a. channel name.
+ const char* messageBuffer = message->Buffer.c_str();
+ const Level level = message->MessageLogLevel;
+
+ // Construct header on top of timestamp buffer
+ AppendHeader(
+ HeaderBuffer + timestampLength,
+ sizeof(HeaderBuffer) - timestampLength,
+ level,
+ subsystemName);
+
+ // Write the output to potentially each output plugin. There's some logic below to check
+ // for channel writability to output plugins. The large majority of the time the cost of
+ // the checks will amount to two hash-table lookups of keys that aren't present. For the
+ // rare cases that they are present, a second lookup into the hash table for a short
+ // string occurs.
+ auto itC = ChannelOutputPluginFiltering.find(subsystemName);
+
+ for (auto& pluginIt : Plugins) {
+ const char* pluginName = pluginIt->GetUniquePluginName();
+ bool channelWritesToPlugin =
+ (itC == ChannelOutputPluginFiltering.end()); // Typically true.
+
+ if (!channelWritesToPlugin) { // 99% of the time we won't need to execute this block.
+ // In this case the channel filtering map has a customized set of output
+ // plugins to write to, which are a subset of the entire set.
+ channelWritesToPlugin = (itC->second.find(pluginName) != itC->second.end());
+ }
+
+ // If the channel is set to write to the output plugin, then let's check to see
+ // if the output plugin enables writes from the channel.
+ if (channelWritesToPlugin) { // Typically true.
+ auto itO = OutputPluginChannelFiltering.find(pluginName);
+ bool pluginAllowsChannel =
+ (itO == OutputPluginChannelFiltering.end()); // Typically true.
+
+ if (!pluginAllowsChannel) { // 99% of the time we won't need to execute this block.
+ // In this case the output filtering map has a customized set of channels
+ // it allows writing from.
+ pluginAllowsChannel = (itO->second.find(subsystemName) != itO->second.end());
+ }
+
+ if (pluginAllowsChannel)
+ pluginIt->Write(level, subsystemName, HeaderBuffer, messageBuffer);
+ }
+ }
+ }
+
+ next = message->Next;
+ delete message;
+ }
+ }
+}
+
+void OutputWorker::FlushDbgViewLogImmediately(
+ const char* subsystemName,
+ Level messageLogLevel,
+ const char* stream) {
+ static const int TempBufferBytes = 1024; // 1 KiB
+ char HeaderBuffer[TempBufferBytes];
+
+ // Get timestamp string
+ int timestampLength = GetTimestamp(HeaderBuffer, TempBufferBytes);
+ if (timestampLength <= 0) {
+ LOGGING_DEBUG_BREAK();
+ return; // Maybe bug in timestamp code?
+ }
+
+ // Construct log header on top of timestamp buffer
+ AppendHeader(
+ HeaderBuffer + timestampLength,
+ sizeof(HeaderBuffer) - timestampLength,
+ messageLogLevel,
+ subsystemName);
+
+ // Build up a single string to send to OutputDebugStringA so it
+ // all appears on the same line in DbgView.
+ std::stringstream ss;
+ ss << HeaderBuffer << stream << "\n";
+
+#if defined(_WIN32)
+ ::OutputDebugStringA(ss.str().c_str());
+#else
+ fputs(ss.str().c_str(), stderr);
+#endif
+}
+
+static void SetThreadName(const char* name) {
+#if defined(_WIN32)
+ DWORD threadId = ::GetCurrentThreadId();
+
+// http://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx
+#pragma pack(push, 8)
+ struct THREADNAME_INFO {
+ DWORD dwType; // Must be 0x1000
+ LPCSTR szName; // Pointer to name (in user address space)
+ DWORD dwThreadID; // Thread ID (-1 for caller thread)
+ DWORD dwFlags; // Reserved for future use; must be zero
+ };
+ union TNIUnion {
+ THREADNAME_INFO tni;
+ ULONG_PTR upArray[4];
+ };
+#pragma pack(pop)
+
+ TNIUnion tniUnion = {{0x1000, name, threadId, 0}};
+
+ __try {
+ RaiseException(0x406D1388, 0, ARRAYSIZE(tniUnion.upArray), tniUnion.upArray);
+ } __except (
+ GetExceptionCode() == 0x406D1388 ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER) {
+ return;
+ }
+#elif defined(__APPLE__)
+ pthread_setname_np(name);
+#else
+ pthread_setname_np(pthread_self(), name);
+#endif
+}
+
+void OutputWorker::WorkerThreadEntrypoint() {
+#if !defined(_WIN32)
+ std::unique_lock<std::mutex> lock(WorkerCvMutex);
+ WorkerCv.notify_one();
+#endif // !defined(_WIN32)
+ SetThreadName("LoggingOutputWorker");
+
+#if defined(_WIN32)
+ // Lower the priority for logging.
+ ::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_LOWEST);
+#else
+ // Other desktop platforms (e.g. Linux, OSX) don't let you set thread priorities.
+#endif // defined(_WIN32)
+
+#if defined(_WIN32)
+ while (!WorkerTerminator.IsTerminated()) {
+ if (WorkerTerminator.WaitOn(WorkerWakeEvent.Get())) {
+ ProcessQueuedMessages();
+ }
+ }
+#else
+ while (!Terminated.load()) {
+ WorkerCv.wait(lock);
+ ProcessQueuedMessages();
+ }
+#endif // defined(_WIN32)
+}
+
+void OutputWorker::Write(
+ const char* subsystemName,
+ Level messageLogLevel,
+ const char* stream,
+ bool relogged,
+ WriteOption option) {
+ bool dropped = false; // Flag indicates if the message was dropped due to queue overrun
+ bool needToWakeWorkerThread = false; // Flag indicates if we need to wake the worker thread
+
+ // Add work to queue.
+ {
+ Locker locker(WorkQueueLock);
+
+ // Check to see if this message looks like it's repeat message which we want to aggregate
+ // in order to avoid log spam of the same similar message repeatedly.
+ if (RepeatedMessageManagerInstance.HandleMessage(subsystemName, messageLogLevel, stream) ==
+ RepeatedMessageManager::HandleResult::Aggregated) {
+ return;
+ }
+
+ if (option != WriteOption::DangerouslyIgnoreQueueLimit && WorkQueueSize >= WorkQueueLimit) {
+ // Record drop
+ WorkQueueOverrun++;
+ dropped = true;
+ } else {
+ // Add queued buffer to the end of the work queue
+ LogTime time = GetCurrentLogTime();
+ WorkQueueAdd(new QueuedLogMessage(subsystemName, messageLogLevel, stream, time));
+
+#if defined(_WIN32)
+ // Only need to wake the worker thread on the first message
+ // The SetEvent() call takes 6 microseconds or so
+ if (WorkQueueSize <= 1) {
+ needToWakeWorkerThread = true;
+ }
+#else
+ // WorkerCv.notify_one() will only wake the worker if it is waiting
+ needToWakeWorkerThread = true;
+#endif // defined(_WIN32)
+ }
+ }
+
+ if (!dropped && needToWakeWorkerThread) {
+ // Wake the worker thread
+#if defined(_WIN32)
+ ::SetEvent(WorkerWakeEvent.Get());
+#else
+ WorkerCv.notify_one();
+#endif
+ }
+
+ // If this is the first time logging this message,
+ if (!relogged) {
+ // If we are in a debugger,
+ if (IsInDebugger) {
+ FlushDbgViewLogImmediately(subsystemName, messageLogLevel, stream);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// QueuedLogMessage
+
+OutputWorker::QueuedLogMessage::QueuedLogMessage(
+ const char* subsystemName,
+ Level messageLogLevel,
+ const char* stream,
+ const LogTime& time)
+ : SubsystemName(subsystemName),
+ MessageLogLevel(messageLogLevel),
+ Buffer(stream),
+ Time(time),
+ Next(nullptr),
+#if defined(_WIN32)
+ FlushEvent(nullptr)
+#else
+ FlushEvent(false)
+#endif // defined(_WIN32)
+{
+}
+
+void Channel::registerNode() {
+ Node.SubsystemName = SubsystemName.Get();
+ Node.Level = &MinimumOutputLevel;
+ Node.UserOverrodeMinimumOutputLevel = &UserOverrodeMinimumOutputLevel;
+
+ ConfiguratorRegister(&Node);
+}
+
+Channel::Channel(const char* nameString)
+ : SubsystemName(nameString),
+ MinimumOutputLevel((Log_Level_t)DefaultMinimumOutputLevel),
+ UserOverrodeMinimumOutputLevel(false) {
+ registerNode();
+}
+
+Channel::Channel(const Channel& other)
+ : SubsystemName(other.SubsystemName),
+ MinimumOutputLevel(other.MinimumOutputLevel),
+ Prefix(other.GetPrefix()),
+ UserOverrodeMinimumOutputLevel(other.UserOverrodeMinimumOutputLevel) {
+ registerNode();
+}
+
+Channel::~Channel() {
+ // ...We can get modified from other threads here.
+ ConfiguratorUnregister(&Node);
+}
+
+std::string Channel::GetPrefix() const {
+ Locker locker(PrefixLock);
+ return Prefix;
+}
+
+void Channel::SetPrefix(const std::string& prefix) {
+ Locker locker(PrefixLock);
+ Prefix = prefix;
+}
+
+void Channel::SetMinimumOutputLevel(Level newLevel) {
+ SetMinimumOutputLevelNoSave(newLevel);
+
+ ConfiguratorOnChannelLevelChange(SubsystemName.Get(), MinimumOutputLevel);
+}
+
+void Channel::SetMinimumOutputLevelNoSave(Level newLevel) {
+ MinimumOutputLevel = (Log_Level_t)newLevel;
+ UserOverrodeMinimumOutputLevel = true;
+}
+
+Level Channel::GetMinimumOutputLevel() const {
+ return (Level)MinimumOutputLevel;
+}
+
+int Channel::GetPrintfLength(const char* format, ...) {
+ va_list argList;
+ va_start(argList, format);
+ int size = GetPrintfLengthV(format, argList);
+ va_end(argList);
+ return size;
+}
+
+//-----------------------------------------------------------------------------
+// Conversion functions
+
+template <>
+void LogStringize(LogStringBuffer& buffer, const wchar_t* const& first) {
+#ifdef _WIN32
+
+ // Use Windows' optimized multi-byte UTF8 conversion function for performance.
+ // Returns the number of bytes used by the conversion, including the null terminator
+ // since -1 is passed in for the input string length.
+ // Returns 0 on failure.
+ int bytesUsed = ::WideCharToMultiByte(
+ CP_ACP, // Default code page
+ 0, // Default flags
+ first, // String to convert
+ -1, // Unknown string length
+ nullptr, // Null while checking length of buffer
+ 0, // 0 to request the buffer size required
+ nullptr, // Default default character
+ nullptr); // Ignore whether or not default character was used
+ // Setting the last two arguments to null is documented to execute faster via MSDN.
+
+ // If the function succeeded,
+ if (bytesUsed > 0) {
+ // Avoid allocating memory if the string is fairly small.
+ char stackBuffer[128];
+ char* dynamicBuffer = nullptr;
+ char* convertedString = stackBuffer;
+ if (bytesUsed > (int)sizeof(stackBuffer)) {
+ // (defensive coding) Add 8 bytes of slop in case of API bugs.
+ dynamicBuffer = new char[bytesUsed + 8];
+ convertedString = dynamicBuffer;
+ }
+
+ int charsWritten = ::WideCharToMultiByte(
+ CP_ACP, // Default code page
+ 0, // Default flags
+ first, // String to convert
+ -1, // Unknown string length
+ convertedString, // Output buffer
+ bytesUsed, // Request the same number of bytes
+ nullptr, // Default default character
+ nullptr); // Ignore whether or not default character was used
+ // Setting the last two arguments to null is documented to execute faster via MSDN.
+
+ if (charsWritten > 0) {
+ // Append the converted string.
+ buffer.Stream << convertedString;
+ }
+
+ delete[] dynamicBuffer;
+ }
+
+#else
+
+ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
+ buffer.Stream << converter.to_bytes(first);
+
+#endif // _WIN32
+}
+
+//-----------------------------------------------------------------------------
+// ConfiguratorPlugin
+
+ConfiguratorPlugin::ConfiguratorPlugin() {}
+
+ConfiguratorPlugin::~ConfiguratorPlugin() {}
+
+//-----------------------------------------------------------------------------
+// Log Configurator
+
+Configurator::Configurator() : GlobalMinimumLogLevel((Log_Level_t)Level::Debug), Plugin(nullptr) {}
+
+Configurator* Configurator::GetInstance() {
+ static Configurator configurator;
+ return &configurator;
+}
+
+Configurator::~Configurator() {}
+
+void Configurator::SetGlobalMinimumLogLevel(Level level) {
+ Locker locker(OutputWorker::GetInstance()->GetChannelsLock());
+
+ GlobalMinimumLogLevel = (Log_Level_t)level;
+
+ for (ChannelNode* channelNode = ChannelNodeHead; channelNode; channelNode = channelNode->Next) {
+ *(channelNode->Level) = (ovrlog::Log_Level_t)level;
+ }
+}
+
+// Should be locked already when calling this function!
+void Configurator::RestoreChannelLogLevel(const char* channelName) {
+ Level level = (Level)GlobalMinimumLogLevel;
+
+ // Look up the log level for this channel if we can
+ if (Plugin) {
+ Plugin->RestoreChannelLevel(channelName, level);
+ }
+
+ const std::string stdChannelName(channelName);
+
+ SetChannelLevelNoLock(stdChannelName, level, false);
+}
+
+void Configurator::RestoreChannelLogLevel(ChannelNode* channelNode) {
+ Level level = (Level)GlobalMinimumLogLevel;
+
+ // Look up the log level for this channel if we can
+ if (Plugin) {
+ Plugin->RestoreChannelLevel(channelNode->SubsystemName, level);
+ }
+
+ // Don't undo user calls to SetMinimumOutputLevelNoSave()
+ if (*(channelNode->UserOverrodeMinimumOutputLevel) == false) {
+ *(channelNode->Level) = (Log_Level_t)level;
+ }
+}
+
+void Configurator::RestoreAllChannelLogLevels() {
+ Locker locker(OutputWorker::GetInstance()->GetChannelsLock());
+ RestoreAllChannelLogLevelsNoLock();
+}
+
+void Configurator::RestoreAllChannelLogLevelsNoLock() {
+ for (ChannelNode* channelNode = ChannelNodeHead; channelNode; channelNode = channelNode->Next) {
+ RestoreChannelLogLevel(channelNode->SubsystemName);
+ }
+}
+
+void Configurator::SetPlugin(std::shared_ptr<ConfiguratorPlugin> plugin) {
+ Locker locker(OutputWorker::GetInstance()->GetChannelsLock());
+
+ Plugin = plugin;
+
+ for (ChannelNode* channelNode = ChannelNodeHead; channelNode; channelNode = channelNode->Next) {
+ RestoreChannelLogLevel(channelNode->SubsystemName);
+ }
+}
+void Configurator::GetChannels(std::vector<std::pair<std::string, Level>>& channels) {
+ channels.clear();
+
+ Locker locker(OutputWorker::GetInstance()->GetChannelsLock());
+
+ for (ChannelNode* channelNode = ChannelNodeHead; channelNode; channelNode = channelNode->Next) {
+ channels.push_back(
+ std::make_pair(std::string(channelNode->SubsystemName), (Level) * (channelNode->Level)));
+ }
+}
+void Configurator::SetChannelLevel(const std::string& channelName, Level level) {
+ Locker locker(OutputWorker::GetInstance()->GetChannelsLock());
+ SetChannelLevelNoLock(channelName, level, true);
+}
+
+// Should be locked already when calling this function!
+void Configurator::SetChannelLevelNoLock(
+ const std::string& channelName,
+ Level level,
+ bool overrideUser) {
+ for (ChannelNode* channelNode = ChannelNodeHead; channelNode; channelNode = channelNode->Next) {
+ if (std::string(channelNode->SubsystemName) == channelName) {
+ if (*(channelNode->UserOverrodeMinimumOutputLevel) == false || overrideUser) {
+ *(channelNode->Level) = (Log_Level_t)level;
+
+ // Purposely no break, channels may have duplicate names
+ }
+ }
+ }
+}
+
+void Configurator::SetOutputPluginChannels(
+ const char* outputPluginName,
+ const std::vector<std::string>& channelNames) {
+ OutputWorker::GetInstance()->SetOutputPluginChannels(outputPluginName, channelNames);
+}
+
+void Configurator::SetChannelOutputPlugins(
+ const char* channelName,
+ const std::vector<std::string>& outputPluginNames) {
+ OutputWorker::GetInstance()->SetChannelOutputPlugins(channelName, outputPluginNames);
+}
+
+void Configurator::SetChannelSingleOutput(const char* channelName, const char* outputPluginName) {
+ OutputWorker::GetInstance()->SetChannelSingleOutput(channelName, outputPluginName);
+}
+
+void Configurator::OnChannelLevelChange(const char* channelName, Log_Level_t minimumOutputLevel) {
+ Locker locker(OutputWorker::GetInstance()->GetChannelsLock());
+
+ if (Plugin) {
+ // Save channel level
+ Plugin->SaveChannelLevel(channelName, (Level)minimumOutputLevel);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// ErrorSilencer
+#if defined(_MSC_VER)
+#if (_MSC_VER < 1300)
+__declspec(thread) int ThreadErrorSilencedOptions = 0;
+#else
+#pragma data_seg(".tls$")
+__declspec(thread) int ThreadErrorSilencedOptions = 0;
+#pragma data_seg(".rwdata")
+#endif
+#else
+thread_local int ThreadErrorSilencedOptions = 0;
+#endif
+
+int ErrorSilencer::GetSilenceOptions() {
+ return ThreadErrorSilencedOptions;
+}
+
+ErrorSilencer::ErrorSilencer(int options) : Options(options) {
+ Silence();
+}
+
+ErrorSilencer::~ErrorSilencer() {
+ Unsilence();
+}
+
+void ErrorSilencer::Silence() {
+ // We do not currently support recursive silencers
+ assert(!GetSilenceOptions());
+ ThreadErrorSilencedOptions = Options;
+}
+
+void ErrorSilencer::Unsilence() {
+ // We do not currently support recursive silencers
+ assert(GetSilenceOptions());
+ ThreadErrorSilencedOptions = 0;
+}
+
+} // namespace ovrlog
+
+#ifdef OVR_STRINGIZE
+#error "This code must remain independent of LibOVR"
+#endif