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.

139 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. const static long maxSize = 10 * 1000 * 1000; // 10 MB
  14. static bool fileEndsWith(FILE* file, std::string str) {
  15. // Seek to last `len` characters
  16. size_t len = str.size();
  17. std::fseek(file, -long(len), SEEK_END);
  18. char actual[len];
  19. if (std::fread(actual, 1, len, file) != len)
  20. return false;
  21. return std::string(actual, len) == str;
  22. }
  23. static bool isTruncated() {
  24. if (logPath.empty())
  25. return false;
  26. // Open existing log file
  27. FILE* file = std::fopen(logPath.c_str(), "r");
  28. if (!file)
  29. return false;
  30. DEFER({std::fclose(file);});
  31. if (fileEndsWith(file, "END"))
  32. return false;
  33. // legacy <=v1
  34. if (fileEndsWith(file, "Destroying logger\n"))
  35. return false;
  36. return true;
  37. }
  38. bool init() {
  39. if (outputFile)
  40. return true;
  41. std::lock_guard<std::mutex> lock(mutex);
  42. truncated = false;
  43. // Don't open a file in development mode.
  44. if (logPath.empty()) {
  45. outputFile = stderr;
  46. }
  47. else {
  48. truncated = isTruncated();
  49. outputFile = std::fopen(logPath.c_str(), "w");
  50. if (!outputFile) {
  51. std::fprintf(stderr, "Could not open log at %s\n", logPath.c_str());
  52. return false;
  53. }
  54. }
  55. return true;
  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. // Record logging time before calling OS functions
  82. double nowTime = system::getTime();
  83. // Check if log size is full
  84. if (outputFile != stderr) {
  85. long pos = std::ftell(outputFile);
  86. if (pos >= maxSize)
  87. return;
  88. }
  89. std::lock_guard<std::mutex> lock(mutex);
  90. if (outputFile == stderr)
  91. std::fprintf(outputFile, "\x1B[%dm", levelColors[level]);
  92. std::fprintf(outputFile, "[%.03f %s %s:%d %s] ", nowTime, levelLabels[level], filename, line, func);
  93. if (outputFile == stderr)
  94. std::fprintf(outputFile, "\x1B[0m");
  95. std::vfprintf(outputFile, format, args);
  96. std::fprintf(outputFile, "\n");
  97. // 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.
  98. std::fflush(outputFile);
  99. }
  100. void log(Level level, const char* filename, int line, const char* func, const char* format, ...) {
  101. va_list args;
  102. va_start(args, format);
  103. logVa(level, filename, line, func, format, args);
  104. va_end(args);
  105. }
  106. bool wasTruncated() {
  107. return truncated;
  108. }
  109. } // namespace logger
  110. } // namespace rack