Files
source/sgp/debug_util.h
T
f5782dccbd Upload pending crash reports as opt-in telemetry
A crash report is worth nothing sitting on the player's disk. On startup, drain
the crash_report_*.txt files the handler left behind to the endpoint named by
CRASH_TELEMETRY_URL in Ja2 Settings; an empty or absent key turns the feature off
entirely. The first launch asks the player once and remembers the answer in
telemetry.consent -- declined means the reports simply keep accumulating locally.

This lands in its own translation unit rather than in more of debug_win_util.cpp.
Everything in that file runs inside a faulting thread and may not allocate;
everything here runs at startup with a healthy heap and is ordinary code. Two
files keep the no-heap rule easy to see and easy to hold.

The draining runs on a detached thread. The uploads are synchronous WinHttp calls
with seconds-long timeouts, and this sits on the startup path, so on the main
thread an unreachable endpoint is a stall the player watches before the splash
screen. Nothing waits on the result: if the player quits first the process exits
from under the thread, which costs nothing, since an interrupted upload leaves the
file on disk and it goes out next launch. The consent prompt stays on the main
thread on purpose -- it is a question, and a question has to be asked before
anything is sent.

Which reports get deleted is chosen so that a mistake cannot destroy them. A file
goes away on 2xx, and on 400/413/415, i.e. content the server will never accept.
Everything else keeps it: no connection, 5xx, and notably the 403/404 of a
mistyped CRASH_TELEMETRY_URL, which would otherwise silently eat every player's
crash history. Bounds all round: every WinHttp phase has a timeout, a report over
256 KB is not one of ours and never goes on the wire, at most 20 uploads per
launch so a crash-looping build cannot turn startup into an upload session, and
reports older than 30 days are dropped unsent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 10:25:47 -03:00

81 lines
3.1 KiB
C++

// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This is a cross platform interface for helper functions related to debuggers.
// You should use this to test if you're running under a debugger, and if you
// would like to yield (breakpoint) into the debugger.
#ifndef BASE_DEBUG_UTIL_H_
#define BASE_DEBUG_UTIL_H_
#include <iosfwd>
#include <vector>
#include "sgp_logger.h"
// A macro to disallow the copy constructor and operator= functions
// This should be used in the private: declarations for a class
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&); \
void operator=(const TypeName&)
// An older, deprecated, politically incorrect name for the above.
#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) DISALLOW_COPY_AND_ASSIGN(TypeName)
// A stacktrace can be helpful in debugging. For example, you can include a
// stacktrace member in a object (probably around #ifndef NDEBUG) so that you
// can later see where the given object was created from.
class StackTrace {
public:
// Create a stacktrace from the current location
StackTrace();
// Get an array of instruction pointer values.
// count: (output) the number of elements in the returned array
const void *const *Addresses(size_t* count);
// Print a backtrace to stderr
void PrintBacktrace(const char* msg);
// Resolve backtrace to symbols and write to stream.
void OutputToStream(const char* msg, sgp::Logger::LogInstance* os);
private:
std::vector<void*> trace_;
int count_;
DISALLOW_EVIL_CONSTRUCTORS(StackTrace);
};
struct _EXCEPTION_POINTERS;
namespace sgp
{
void dumpStackTrace(vfs::String const& msg);
// Write a heap-free crash report (registers + a raw return-address backtrace)
// for the faulting context to a numbered crash_report file; symbolize it
// offline against the build's PDB. Call first-chance from a vectored handler.
void writeExceptionBacktrace(_EXCEPTION_POINTERS* ep);
// Record the build-id (game version string) to stamp into crash reports, so
// a report can be matched to the exact build's PDB. Call once at startup.
void setCrashBuildId(const char* id);
// What to tell the player: why we died and where the report landed. NULL if
// no crash was recorded. Built by the handler, shown from the exit path.
const wchar_t* crashReportMessage();
// Record the player's optional self-chosen handle (HANDLE in Ja2 Settings) to
// stamp into crash reports, so a report can be tied to whoever raises it with
// us on Discord. Empty or unset simply omits the field. Call once at startup.
void setCrashUserHandle(const wchar_t* handle);
// Startup crash-telemetry pass (heap is healthy here — never call from a
// crash handler). If url is empty the feature is off. On first run, asks the
// player for consent (native dialog) and remembers it; if granted, POSTs each
// pending crash_report_*.txt to url and deletes the ones that upload cleanly.
void processCrashTelemetry(const wchar_t* url);
}
#endif // BASE_DEBUG_UTIL_H_