Separate ALCdevice from the implementation
[openal-soft.git] / core / logging.cpp
blobc0ff45c0241d921fa59a49075dd6b1415f57717b
2 #include "config.h"
4 #include "logging.h"
6 #include <array>
7 #include <cctype>
8 #include <cstdarg>
9 #include <cstdio>
10 #include <cstring>
11 #include <mutex>
12 #include <optional>
13 #include <string>
14 #include <vector>
16 #include "alspan.h"
17 #include "opthelpers.h"
18 #include "strutils.h"
21 #if defined(_WIN32)
22 #define WIN32_LEAN_AND_MEAN
23 #include <windows.h>
24 #elif defined(__ANDROID__)
25 #include <android/log.h>
26 #endif
29 FILE *gLogFile{stderr};
30 #ifdef _DEBUG
31 LogLevel gLogLevel{LogLevel::Warning};
32 #else
33 LogLevel gLogLevel{LogLevel::Error};
34 #endif
37 namespace {
39 enum class LogState : uint8_t {
40 FirstRun,
41 Ready,
42 Disable
45 std::mutex LogCallbackMutex;
46 LogState gLogState{LogState::FirstRun};
48 LogCallbackFunc gLogCallback{};
49 void *gLogCallbackPtr{};
51 constexpr auto GetLevelCode(LogLevel level) noexcept -> std::optional<char>
53 switch(level)
55 case LogLevel::Disable: break;
56 case LogLevel::Error: return 'E';
57 case LogLevel::Warning: return 'W';
58 case LogLevel::Trace: return 'I';
60 return std::nullopt;
63 } // namespace
65 void al_set_log_callback(LogCallbackFunc callback, void *userptr)
67 auto cblock = std::lock_guard{LogCallbackMutex};
68 gLogCallback = callback;
69 gLogCallbackPtr = callback ? userptr : nullptr;
70 if(gLogState == LogState::FirstRun)
72 auto extlogopt = al::getenv("ALSOFT_DISABLE_LOG_CALLBACK");
73 if(!extlogopt || *extlogopt != "1")
74 gLogState = LogState::Ready;
75 else
76 gLogState = LogState::Disable;
80 void al_print(LogLevel level, const char *fmt, ...) noexcept
81 try {
82 /* Kind of ugly since string literals are const char arrays with a size
83 * that includes the null terminator, which we want to exclude from the
84 * span.
86 auto prefix = al::span{"[ALSOFT] (--) "}.first<14>();
87 switch(level)
89 case LogLevel::Disable: break;
90 case LogLevel::Error: prefix = al::span{"[ALSOFT] (EE) "}.first<14>(); break;
91 case LogLevel::Warning: prefix = al::span{"[ALSOFT] (WW) "}.first<14>(); break;
92 case LogLevel::Trace: prefix = al::span{"[ALSOFT] (II) "}.first<14>(); break;
95 std::vector<char> dynmsg;
96 std::array<char,256> stcmsg{};
98 char *str{stcmsg.data()};
99 auto prefend1 = std::copy_n(prefix.begin(), prefix.size(), stcmsg.begin());
100 al::span<char> msg{prefend1, stcmsg.end()};
102 /* NOLINTBEGIN(*-array-to-pointer-decay) */
103 std::va_list args, args2;
104 va_start(args, fmt);
105 va_copy(args2, args);
106 const int msglen{std::vsnprintf(msg.data(), msg.size(), fmt, args)};
107 if(msglen >= 0)
109 if(static_cast<size_t>(msglen) >= msg.size()) UNLIKELY
111 dynmsg.resize(static_cast<size_t>(msglen)+prefix.size() + 1u);
113 str = dynmsg.data();
114 auto prefend2 = std::copy_n(prefix.begin(), prefix.size(), dynmsg.begin());
115 msg = {prefend2, dynmsg.end()};
117 std::vsnprintf(msg.data(), msg.size(), fmt, args2);
119 msg = msg.first(static_cast<size_t>(msglen));
121 else
122 msg = {msg.data(), std::strlen(msg.data())};
123 va_end(args2);
124 va_end(args);
125 /* NOLINTEND(*-array-to-pointer-decay) */
127 if(gLogLevel >= level)
129 auto logfile = gLogFile;
130 fputs(str, logfile);
131 fflush(logfile);
133 #if defined(_WIN32) && !defined(NDEBUG)
134 /* OutputDebugStringW has no 'level' property to distinguish between
135 * informational, warning, or error debug messages. So only print them for
136 * non-Release builds.
138 std::wstring wstr{utf8_to_wstr(str)};
139 OutputDebugStringW(wstr.c_str());
140 #elif defined(__ANDROID__)
141 auto android_severity = [](LogLevel l) noexcept
143 switch(l)
145 case LogLevel::Trace: return ANDROID_LOG_DEBUG;
146 case LogLevel::Warning: return ANDROID_LOG_WARN;
147 case LogLevel::Error: return ANDROID_LOG_ERROR;
148 /* Should not happen. */
149 case LogLevel::Disable:
150 break;
152 return ANDROID_LOG_ERROR;
154 __android_log_print(android_severity(level), "openal", "%s", str);
155 #endif
157 auto cblock = std::lock_guard{LogCallbackMutex};
158 if(gLogState != LogState::Disable)
160 while(!msg.empty() && std::isspace(msg.back()))
162 msg.back() = '\0';
163 msg = msg.first(msg.size()-1);
165 if(auto logcode = GetLevelCode(level); logcode && !msg.empty())
167 if(gLogCallback)
168 gLogCallback(gLogCallbackPtr, *logcode, msg.data(), static_cast<int>(msg.size()));
169 else if(gLogState == LogState::FirstRun)
170 gLogState = LogState::Disable;
174 catch(...) {
175 /* Swallow any exceptions */