💿🐜 Antkeeper source code https://antkeeper.com
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.

220 lines
6.6 KiB

  1. /*
  2. * Copyright (C) 2023 Christopher J. Howard
  3. *
  4. * This file is part of Antkeeper source code.
  5. *
  6. * Antkeeper source code is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Antkeeper source code is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "game/game.hpp"
  20. #include <SDL2/SDL.h>
  21. #include <chrono>
  22. #include <engine/config.hpp>
  23. #include <engine/debug/console.hpp>
  24. #include <engine/debug/log.hpp>
  25. #include <engine/utility/ansi.hpp>
  26. #include <engine/utility/paths.hpp>
  27. #include <fstream>
  28. #include <iostream>
  29. #include <set>
  30. #include <stdexcept>
  31. #include <syncstream>
  32. int main(int argc, char* argv[])
  33. {
  34. // Get time at which the application launched
  35. const auto launch_time = std::chrono::system_clock::now();
  36. // Enable console UTF-8 output and VT100 sequences (for colored text)
  37. debug::console::enable_utf8();
  38. debug::console::enable_vt100();
  39. // Subscribe log to cout function to message logged events
  40. auto log_to_cout_subscription = debug::log::default_logger().get_message_logged_channel().subscribe
  41. (
  42. [&launch_time](const auto& event)
  43. {
  44. static const char* severities[] =
  45. {
  46. "trace",
  47. "debug",
  48. "info",
  49. "warning",
  50. "error",
  51. "fatal"
  52. };
  53. static const std::string colors[] =
  54. {
  55. std::format("{}", ansi::fg_white),
  56. std::format("{}", ansi::fg_bright_blue),
  57. std::format("{}", ansi::fg_bright_green),
  58. std::format("{}", ansi::fg_yellow),
  59. std::format("{}", ansi::fg_red),
  60. std::format("{}{}", ansi::fg_white, ansi::bg_bright_red)
  61. };
  62. std::osyncstream(std::cout) << std::format
  63. (
  64. "[{:8.03f}] {}{}: {}:{}:{}: {}{}\n",
  65. std::chrono::duration<float>(event.time - launch_time).count(),
  66. colors[static_cast<int>(event.severity)],
  67. //severities[static_cast<int>(event.severity)],
  68. static_cast<int>(event.severity),
  69. std::filesystem::path(event.location.file_name()).filename().string(),
  70. event.location.line(),
  71. event.location.column(),
  72. event.message,
  73. ansi::reset
  74. );
  75. }
  76. );
  77. // Determine path to log archive
  78. const std::filesystem::path log_archive_path = get_shared_config_path() / config::application_name / "logs";
  79. // Set up log archive
  80. bool log_archive_exists = false;
  81. try
  82. {
  83. // Create log archive if it doesn't exist
  84. if (std::filesystem::create_directories(log_archive_path))
  85. {
  86. debug::log::debug("Created log archive \"{}\"", log_archive_path.string());
  87. }
  88. else
  89. {
  90. // Clean pre-existing log archive
  91. try
  92. {
  93. // Detect and sort archived logs
  94. std::set<std::filesystem::path> log_archive;
  95. for (const auto& entry: std::filesystem::directory_iterator{log_archive_path})
  96. {
  97. if (entry.is_regular_file() &&
  98. entry.path().extension() == ".log")
  99. {
  100. log_archive.emplace(entry.path());
  101. }
  102. }
  103. debug::log::debug("Detected {} archived log{} at \"{}\"", log_archive.size(), log_archive.size() != 1 ? "s" : "", log_archive_path.string());
  104. // Delete expired logs
  105. if (!log_archive.empty())
  106. {
  107. for (std::size_t i = log_archive.size() + 1; i > config::debug_log_archive_capacity; --i)
  108. {
  109. std::filesystem::remove(*log_archive.begin());
  110. debug::log::debug("Deleted expired log file \"{}\"", log_archive.begin()->string());
  111. log_archive.erase(log_archive.begin());
  112. }
  113. }
  114. }
  115. catch (const std::filesystem::filesystem_error& e)
  116. {
  117. debug::log::error("An error occured while cleaning the log archive \"{}\": {}", log_archive_path.string(), e.what());
  118. }
  119. }
  120. log_archive_exists = true;
  121. }
  122. catch (const std::filesystem::filesystem_error& e)
  123. {
  124. debug::log::error("Failed to create log archive at \"{}\": {}", log_archive_path.string(), e.what());
  125. }
  126. // Set up logging to file
  127. std::shared_ptr<event::subscription> log_to_file_subscription;
  128. std::filesystem::path log_filepath;
  129. if (config::debug_log_archive_capacity && log_archive_exists)
  130. {
  131. // Determine log filename
  132. const auto time = std::chrono::floor<std::chrono::seconds>(launch_time);
  133. const std::string log_filename = std::format("{0}-{1:%Y%m%d}T{1:%H%M%S}Z.log", config::application_slug, time);
  134. // Open log file
  135. log_filepath = log_archive_path / log_filename;
  136. const std::string log_filepath_string = log_filepath.string();
  137. auto log_filestream = std::make_shared<std::ofstream>(log_filepath);
  138. if (log_filestream->is_open())
  139. {
  140. debug::log::debug("Opened log file \"{}\"", log_filepath_string);
  141. // Write log file header
  142. (*log_filestream) << "time\tfile\tline\tcolumn\tseverity\tmessage";
  143. if (log_filestream->good())
  144. {
  145. // Subscribe log to file function to message logged events
  146. log_to_file_subscription = debug::log::default_logger().get_message_logged_channel().subscribe
  147. (
  148. [&launch_time, log_filestream](const auto& event)
  149. {
  150. std::osyncstream(*log_filestream) << std::format
  151. (
  152. "\n{:.03f}\t{}\t{}\t{}\t{}\t{}",
  153. std::chrono::duration<float>(event.time - launch_time).count(),
  154. std::filesystem::path(event.location.file_name()).filename().string(),
  155. event.location.line(),
  156. event.location.column(),
  157. static_cast<int>(event.severity),
  158. event.message
  159. );
  160. }
  161. );
  162. // Unsubscribe log to cout function from message logged events on release builds
  163. #if defined(NDEBUG)
  164. log_to_cout_subscription->unsubscribe();
  165. #endif
  166. }
  167. else
  168. {
  169. debug::log::error("Failed to write to log file \"{}\"", log_filepath_string);
  170. }
  171. }
  172. else
  173. {
  174. debug::log::error("Failed to open log file \"{}\"", log_filepath_string);
  175. }
  176. }
  177. // Log application name and version string, followed by launch time
  178. debug::log::info("{0} {1}; {2:%Y%m%d}T{2:%H%M%S}Z", config::application_name, config::application_version_string, std::chrono::floor<std::chrono::milliseconds>(launch_time));
  179. // Start marker
  180. debug::log::debug("Hi! 🐜");
  181. try
  182. {
  183. game(argc, argv).execute();
  184. }
  185. catch (const std::exception& e)
  186. {
  187. debug::log::fatal("Unhandled exception: {}", e.what());
  188. #if defined(NDEBUG)
  189. SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", std::format("Unhandled exception: {}", e.what()).c_str(), nullptr);
  190. #endif
  191. return EXIT_FAILURE;
  192. }
  193. // Clean exit marker
  194. debug::log::debug("Bye! 🐜");
  195. return EXIT_SUCCESS;
  196. }