You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

128 lines
2.9KB

  1. #include <mutex>
  2. #include <common.hpp>
  3. #include <asset.hpp>
  4. #include <system.hpp>
  5. #include <settings.hpp>
  6. // #include <unistd.h> // for dup2
  7. namespace rack {
  8. namespace logger {
  9. std::string logPath;
  10. static FILE* outputFile = NULL;
  11. static std::mutex mutex;
  12. static bool truncated = false;
  13. static bool fileEndsWith(FILE* file, std::string str) {
  14. // Seek to last `len` characters
  15. size_t len = str.size();
  16. std::fseek(file, -long(len), SEEK_END);
  17. char actual[len];
  18. if (std::fread(actual, 1, len, file) != len)
  19. return false;
  20. return std::string(actual, len) == str;
  21. }
  22. static bool isTruncated() {
  23. if (logPath.empty())
  24. return false;
  25. // Open existing log file
  26. FILE* file = std::fopen(logPath.c_str(), "r");
  27. if (!file)
  28. return false;
  29. DEFER({std::fclose(file);});
  30. if (fileEndsWith(file, "END"))
  31. return false;
  32. // legacy <=v1
  33. if (fileEndsWith(file, "Destroying logger\n"))
  34. return false;
  35. return true;
  36. }
  37. void init() {
  38. assert(!outputFile);
  39. std::lock_guard<std::mutex> lock(mutex);
  40. truncated = false;
  41. // Don't open a file in development mode.
  42. if (logPath.empty()) {
  43. outputFile = stderr;
  44. }
  45. else {
  46. truncated = isTruncated();
  47. outputFile = std::fopen(logPath.c_str(), "w");
  48. if (!outputFile) {
  49. std::fprintf(stderr, "Could not open log at %s\n", logPath.c_str());
  50. }
  51. }
  52. // Redirect stdout and stderr to the file
  53. // Actually, disable this because we don't want to steal stdout/stderr from the DAW in Rack for DAWs.
  54. // dup2(fileno(outputFile), fileno(stdout));
  55. // dup2(fileno(outputFile), fileno(stderr));
  56. }
  57. void destroy() {
  58. std::lock_guard<std::mutex> lock(mutex);
  59. if (outputFile && outputFile != stderr) {
  60. // Print end token so we know if the logger exited cleanly.
  61. std::fprintf(outputFile, "END");
  62. std::fclose(outputFile);
  63. }
  64. outputFile = NULL;
  65. }
  66. static const char* const levelLabels[] = {
  67. "debug",
  68. "info",
  69. "warn",
  70. "fatal",
  71. };
  72. static const int levelColors[] = {
  73. 35,
  74. 34,
  75. 33,
  76. 31,
  77. };
  78. static void logVa(Level level, const char* filename, int line, const char* func, const char* format, va_list args) {
  79. if (!outputFile)
  80. return;
  81. double nowTime = system::getTime();
  82. std::lock_guard<std::mutex> lock(mutex);
  83. if (outputFile == stderr)
  84. std::fprintf(outputFile, "\x1B[%dm", levelColors[level]);
  85. std::fprintf(outputFile, "[%.03f %s %s:%d %s] ", nowTime, levelLabels[level], filename, line, func);
  86. if (outputFile == stderr)
  87. std::fprintf(outputFile, "\x1B[0m");
  88. std::vfprintf(outputFile, format, args);
  89. std::fprintf(outputFile, "\n");
  90. // Note: This adds around 10us, but it's important for logging to finish writing the file, and logging is not used in performance critical code.
  91. std::fflush(outputFile);
  92. }
  93. void log(Level level, const char* filename, int line, const char* func, const char* format, ...) {
  94. va_list args;
  95. va_start(args, format);
  96. logVa(level, filename, line, func, format, args);
  97. va_end(args);
  98. }
  99. bool wasTruncated() {
  100. return truncated;
  101. }
  102. } // namespace logger
  103. } // namespace rack