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.

122 lines
2.7KB

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