17 #include "opthelpers.h"
22 #define WIN32_LEAN_AND_MEAN
24 #elif defined(__ANDROID__)
25 #include <android/log.h>
29 FILE *gLogFile
{stderr
};
31 LogLevel gLogLevel
{LogLevel::Warning
};
33 LogLevel gLogLevel
{LogLevel::Error
};
39 enum class LogState
: uint8_t {
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>
55 case LogLevel::Disable
: break;
56 case LogLevel::Error
: return 'E';
57 case LogLevel::Warning
: return 'W';
58 case LogLevel::Trace
: return 'I';
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
;
76 gLogState
= LogState::Disable
;
80 void al_print(LogLevel level
, const char *fmt
, ...) noexcept
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
86 auto prefix
= al::span
{"[ALSOFT] (--) "}.first
<14>();
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
;
105 va_copy(args2
, args
);
106 const int msglen
{std::vsnprintf(msg
.data(), msg
.size(), fmt
, args
)};
109 if(static_cast<size_t>(msglen
) >= msg
.size()) UNLIKELY
111 dynmsg
.resize(static_cast<size_t>(msglen
)+prefix
.size() + 1u);
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
));
122 msg
= {msg
.data(), std::strlen(msg
.data())};
125 /* NOLINTEND(*-array-to-pointer-decay) */
127 if(gLogLevel
>= level
)
129 auto logfile
= gLogFile
;
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
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
:
152 return ANDROID_LOG_ERROR
;
154 __android_log_print(android_severity(level
), "openal", "%s", str
);
157 auto cblock
= std::lock_guard
{LogCallbackMutex
};
158 if(gLogState
!= LogState::Disable
)
160 while(!msg
.empty() && std::isspace(msg
.back()))
163 msg
= msg
.first(msg
.size()-1);
165 if(auto logcode
= GetLevelCode(level
); logcode
&& !msg
.empty())
168 gLogCallback(gLogCallbackPtr
, *logcode
, msg
.data(), static_cast<int>(msg
.size()));
169 else if(gLogState
== LogState::FirstRun
)
170 gLogState
= LogState::Disable
;
175 /* Swallow any exceptions */