💿🐜 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.

223 lines
6.7 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 "config.hpp"
  20. #include "debug/console.hpp"
  21. #include "debug/log.hpp"
  22. #include "game/context.hpp"
  23. #include "game/state/boot.hpp"
  24. #include "utility/ansi.hpp"
  25. #include "utility/paths.hpp"
  26. #include <chrono>
  27. #include <fstream>
  28. #include <iostream>
  29. #include <SDL2/SDL.h>
  30. #include <set>
  31. #include <stdexcept>
  32. #include <syncstream>
  33. int main(int argc, char* argv[])
  34. {
  35. // Get time at which the application launched
  36. const auto launch_time = std::chrono::system_clock::now();
  37. // Enable VT100 sequences in console for colored text
  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. std::filesystem::path(event.location.file_name()).filename().string(),
  67. event.location.line(),
  68. event.location.column(),
  69. colors[static_cast<int>(event.severity)],
  70. severities[static_cast<int>(event.severity)],
  71. event.message,
  72. ansi::reset
  73. );
  74. }
  75. );
  76. // Determine path to log archive
  77. const std::filesystem::path log_archive_path = get_shared_config_path() / config::application_name / "logs";
  78. // Set up log archive
  79. bool log_archive_exists = false;
  80. try
  81. {
  82. // Create log archive if it doesn't exist
  83. if (std::filesystem::create_directories(log_archive_path))
  84. {
  85. debug::log::debug("Created log archive \"{}\"", log_archive_path.string());
  86. }
  87. else
  88. {
  89. // Clean pre-existing log archive
  90. try
  91. {
  92. // Detect and sort archived logs
  93. std::set<std::filesystem::path> log_archive;
  94. for (const auto& entry: std::filesystem::directory_iterator{log_archive_path})
  95. {
  96. if (entry.is_regular_file() &&
  97. entry.path().extension() == ".log")
  98. {
  99. log_archive.emplace(entry.path());
  100. }
  101. }
  102. debug::log::debug("Detected {} archived log{} at \"{}\"", log_archive.size(), log_archive.size() != 1 ? "s" : "", log_archive_path.string());
  103. // Delete expired logs
  104. if (!log_archive.empty())
  105. {
  106. for (std::size_t i = log_archive.size() + 1; i > config::debug_log_archive_capacity; --i)
  107. {
  108. std::filesystem::remove(*log_archive.begin());
  109. debug::log::debug("Deleted expired log file \"{}\"", log_archive.begin()->string());
  110. log_archive.erase(log_archive.begin());
  111. }
  112. }
  113. }
  114. catch (const std::filesystem::filesystem_error& e)
  115. {
  116. debug::log::error("An error occured while cleaning the log archive \"{}\": {}", log_archive_path.string(), e.what());
  117. }
  118. }
  119. log_archive_exists = true;
  120. }
  121. catch (const std::filesystem::filesystem_error& e)
  122. {
  123. debug::log::error("Failed to create log archive at \"{}\": {}", log_archive_path.string(), e.what());
  124. }
  125. // Set up logging to file
  126. std::shared_ptr<event::subscription> log_to_file_subscription;
  127. std::filesystem::path log_filepath;
  128. if (config::debug_log_archive_capacity && log_archive_exists)
  129. {
  130. // Determine log filename
  131. const auto time = std::chrono::floor<std::chrono::seconds>(launch_time);
  132. const std::string log_filename = std::format("{0}-{1:%Y%m%d}T{1:%H%M%S}Z.log", config::application_name, time);
  133. // Open log file
  134. log_filepath = log_archive_path / log_filename;
  135. const std::string log_filepath_string = log_filepath.string();
  136. auto log_filestream = std::make_shared<std::ofstream>(log_filepath);
  137. if (log_filestream->is_open())
  138. {
  139. debug::log::debug("Opened log file \"{}\"", log_filepath_string);
  140. // Write log file header
  141. (*log_filestream) << "time\tfile\tline\tcolumn\tseverity\tmessage";
  142. if (log_filestream->good())
  143. {
  144. // Subscribe log to file function to message logged events
  145. log_to_file_subscription = debug::log::default_logger().get_message_logged_channel().subscribe
  146. (
  147. [&launch_time, log_filestream](const auto& event)
  148. {
  149. std::osyncstream(*log_filestream) << std::format
  150. (
  151. "\n{:.03f}\t{}\t{}\t{}\t{}\t{}",
  152. std::chrono::duration<float>(event.time - launch_time).count(),
  153. std::filesystem::path(event.location.file_name()).filename().string(),
  154. event.location.line(),
  155. event.location.column(),
  156. static_cast<int>(event.severity),
  157. event.message
  158. );
  159. }
  160. );
  161. // Unsubscribe log to cout function from message logged events on release builds
  162. #if defined(NDEBUG)
  163. log_to_cout_subscription->unsubscribe();
  164. #endif
  165. }
  166. else
  167. {
  168. debug::log::error("Failed to write to log file \"{}\"", log_filepath_string);
  169. }
  170. }
  171. else
  172. {
  173. debug::log::error("Failed to open log file \"{}\"", log_filepath_string);
  174. }
  175. }
  176. // Log application name and version string, followed by launch time
  177. 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));
  178. // Start marker
  179. debug::log::debug("Hi! 🐜");
  180. try
  181. {
  182. // Allocate game context
  183. game::context ctx;
  184. // Enter boot state
  185. ctx.state_machine.emplace(new game::state::boot(ctx, argc, argv));
  186. }
  187. catch (const std::exception& e)
  188. {
  189. debug::log::fatal("Unhandled exception: {}", e.what());
  190. #if defined(NDEBUG)
  191. SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", std::format("Unhandled exception: {}", e.what()).c_str(), nullptr);
  192. #endif
  193. return EXIT_FAILURE;
  194. }
  195. // Clean exit marker
  196. debug::log::debug("Bye! 🐜");
  197. return EXIT_SUCCESS;
  198. }