14#include <system_error>
17#include "absl/debugging/failure_signal_handler.h"
18#include "absl/debugging/symbolize.h"
25#define STDERR_FILENO _fileno(stderr)
39using PathChar = std::filesystem::path::value_type;
44constexpr std::string_view
kHeaderBanner =
"=== YAZE Crash Report ===\n";
45constexpr std::string_view
kHeaderEnd =
"========================\n\n";
63 g_crash_log_path[0] == PathChar{0}) {
66 g_crash_log_open_attempted = 1;
69 _wopen(g_crash_log_path, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY,
70 _S_IREAD | _S_IWRITE);
73 open(g_crash_log_path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
74 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
77 write(
crash_log_fd_, g_crash_log_header, g_crash_log_header_len);
83 static volatile sig_atomic_t in_crash_handler = 0;
84 if (in_crash_handler) {
90 if (data ==
nullptr) {
97 while (len < 4096 && data[len] !=
'\0') {
108 write(STDERR_FILENO, data, len);
110 in_crash_handler = 0;
114 const std::filesystem::path& directory) {
117 auto now = std::chrono::system_clock::now();
118 auto time_t_now = std::chrono::system_clock::to_time_t(now);
119 std::tm* tm_now = std::localtime(&time_t_now);
121 std::ostringstream filename;
122 filename <<
"crash_" << std::put_time(tm_now,
"%Y%m%d_%H%M%S") <<
".log";
126 std::filesystem::create_directories(directory, ec);
129 if (native_path.size() < kMaxCrashLogPathChars) {
130 std::copy(native_path.begin(), native_path.end(), g_crash_log_path);
131 g_crash_log_path[native_path.size()] = PathChar{0};
134 g_crash_log_path[0] = PathChar{0};
137 std::ostringstream header;
138 header << kHeaderBanner;
139 header <<
"Version: " <<
version_ <<
"\n";
140 header <<
"Session start: " << std::put_time(tm_now,
"%Y-%m-%d %H:%M:%S")
142 header <<
"Platform: ";
145#elif defined(__APPLE__)
147#elif defined(__linux__)
153 header << kHeaderEnd;
155 const std::string header_str = header.str();
156 g_crash_log_header_len =
157 std::min(header_str.size(),
sizeof(g_crash_log_header));
158 std::memcpy(g_crash_log_header, header_str.data(), g_crash_log_header_len);
159 g_crash_log_open_attempted = 0;
166 absl::FailureSignalHandlerOptions options;
167 options.symbolize_stacktrace =
true;
168 options.use_alternate_stack =
true;
169 options.call_previous_handler =
true;
174 absl::InstallFailureSignalHandler(options);
178 const std::string& version,
const std::filesystem::path& directory) {
196 g_crash_log_path[0] = PathChar{0};
197 g_crash_log_header_len = 0;
198 g_crash_log_open_attempted = 0;
204 const auto size = std::filesystem::file_size(path, ec);
205 if (ec || size > kMaxCrashLogHeaderBytes * 2) {
208 std::ifstream in(path, std::ios::binary);
212 std::string contents((std::istreambuf_iterator<char>(in)),
213 std::istreambuf_iterator<char>());
215 contents.erase(std::remove(contents.begin(), contents.end(),
'\r'),
217 const std::string_view view(contents);
218 return view.starts_with(kHeaderBanner) && view.ends_with(kHeaderEnd);
223 if (app_data_result.ok()) {
224 return *app_data_result;
229 if (temp_result.ok()) {
230 return *temp_result /
"crash_logs";
234 return std::filesystem::current_path() /
"crash_logs";
240 if (!std::filesystem::exists(crash_dir)) {
244 std::vector<std::filesystem::path> logs;
245 for (
const auto& entry : std::filesystem::directory_iterator(crash_dir)) {
246 if (entry.path().extension() ==
".log" &&
247 entry.path().filename().string().starts_with(
"crash_") &&
249 logs.push_back(entry.path());
258 std::sort(logs.begin(), logs.end(),
259 [](
const std::filesystem::path& a,
const std::filesystem::path& b) {
260 return std::filesystem::last_write_time(a) >
261 std::filesystem::last_write_time(b);
269 auto ack_file = crash_dir /
".acknowledged";
272 if (most_recent.empty()) {
277 if (std::filesystem::exists(ack_file)) {
278 auto ack_time = std::filesystem::last_write_time(ack_file);
279 auto crash_time = std::filesystem::last_write_time(most_recent);
280 return crash_time > ack_time;
288 auto ack_file = crash_dir /
".acknowledged";
291 std::ofstream ack(ack_file);
292 ack <<
"acknowledged";
298 if (!std::filesystem::exists(crash_dir)) {
302 std::vector<std::filesystem::path> logs;
303 std::vector<std::filesystem::path> header_only_logs;
304 for (
const auto& entry : std::filesystem::directory_iterator(crash_dir)) {
305 if (entry.path().extension() ==
".log" &&
306 entry.path().filename().string().starts_with(
"crash_")) {
308 header_only_logs.push_back(entry.path());
310 logs.push_back(entry.path());
315 for (
const auto& path : header_only_logs) {
317 std::filesystem::remove(path, ec);
320 if (
static_cast<int>(logs.size()) <= keep_count) {
325 std::sort(logs.begin(), logs.end(),
326 [](
const std::filesystem::path& a,
const std::filesystem::path& b) {
327 return std::filesystem::last_write_time(a) >
328 std::filesystem::last_write_time(b);
332 for (
size_t i = keep_count; i < logs.size(); ++i) {
334 std::filesystem::remove(logs[i], ec);
static void CleanupOldLogs(int keep_count=5)
Clean up old crash logs, keeping only the most recent N logs.
static bool IsHeaderOnlyCrashLog(const std::filesystem::path &path)
True if the file holds only the report header, with nothing written by the signal handler after it.
static void OpenCrashLogFromSignalHandler()
static void CrashLogWriter(const char *data)
static void Initialize(const std::string &version)
Initialize the crash handler for the application.
static std::filesystem::path GetCrashLogDirectory()
Get the path where crash logs are stored.
static bool HasUnacknowledgedCrashLog()
Check if there's a crash log from a previous session.
static std::filesystem::path PendingCrashLogPathForTesting()
static void AcknowledgeCrashLog()
Mark the current crash log as acknowledged.
static std::string version_
static void PrepareCrashLog(const std::string &version, const std::filesystem::path &directory)
static std::filesystem::path GetMostRecentCrashLog()
Get the path to the most recent crash log, if any.
static std::filesystem::path crash_log_path_
static void WriteCrashLogForTesting(const char *data)
static void ResetForTesting()
static void PrepareCrashLogForTesting(const std::string &version, const std::filesystem::path &directory)
size_t g_crash_log_header_len
constexpr size_t kMaxCrashLogHeaderBytes
char g_crash_log_header[kMaxCrashLogHeaderBytes]
constexpr std::string_view kHeaderEnd
volatile sig_atomic_t g_crash_log_open_attempted
PathChar g_crash_log_path[kMaxCrashLogPathChars]
constexpr std::string_view kHeaderBanner
std::filesystem::path::value_type PathChar
constexpr size_t kMaxCrashLogPathChars