logging.cpp 4.6 KB

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