OpenTTD Source 20250312-master-gcdcc6b491d
crashlog.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
10#include "stdafx.h"
11#include "crashlog.h"
12#include "survey.h"
13#include "gamelog.h"
14#include "map_func.h"
18#include "saveload/saveload.h"
19#include "screenshot.h"
21#include "news_func.h"
22#include "news_gui.h"
23#include "fileio_func.h"
24#include "fileio_type.h"
25
26#include "company_func.h"
27#include "3rdparty/fmt/chrono.h"
28#include "3rdparty/fmt/std.h"
29#include "core/format.hpp"
30
31#include "safeguards.h"
32
33/* static */ std::string CrashLog::message{};
34
36constexpr uint8_t CRASHLOG_SURVEY_VERSION = 1;
37
42static void SurveyGamelog(nlohmann::json &json)
43{
44 json = nlohmann::json::array();
45
46 _gamelog.Print([&json](const std::string &s) {
47 json.push_back(s);
48 });
49}
50
55static void SurveyRecentNews(nlohmann::json &json)
56{
57 json = nlohmann::json::array();
58
59 int i = 0;
60 for (const auto &news : GetNews()) {
61 TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(news.date);
62 json.push_back(fmt::format("({}-{:02}-{:02}) String: {}, Type: {}, Ref1: {}, {}, Ref2: {}, {}",
63 ymd.year, ymd.month + 1, ymd.day, news.GetStatusText(), news.type,
64 news.ref1.index(), SerialiseNewsReference(news.ref1),
65 news.ref2.index(), SerialiseNewsReference(news.ref2)));
66 if (++i > 32) break;
67 }
68}
69
76std::string CrashLog::CreateFileName(const char *ext, bool with_dir) const
77{
78 static std::string crashname;
79
80 if (crashname.empty()) {
81 crashname = fmt::format("crash{:%Y%m%d%H%M%S}", fmt::gmtime(time(nullptr)));
82 }
83 return fmt::format("{}{}{}", with_dir ? _personal_dir : std::string{}, crashname, ext);
84}
85
90{
91 /* Reminder: this JSON is read in an automated fashion.
92 * If any structural changes are applied, please bump the version. */
93 this->survey["schema"] = CRASHLOG_SURVEY_VERSION;
94 this->survey["date"] = fmt::format("{:%Y-%m-%d %H:%M:%S} (UTC)", fmt::gmtime(time(nullptr)));
95
96 /* If no internal reason was logged, it must be a crash. */
97 if (CrashLog::message.empty()) {
98 this->SurveyCrash(this->survey["crash"]);
99 } else {
100 this->survey["crash"]["reason"] = CrashLog::message;
101 CrashLog::message.clear();
102 }
103
104 if (!this->TryExecute("stacktrace", [this]() { this->SurveyStacktrace(this->survey["stacktrace"]); return true; })) {
105 this->survey["stacktrace"] = "crashed while gathering information";
106 }
107
108 if (!this->TryExecute("session", [this]() { SurveyGameSession(this->survey["session"]); return true; })) {
109 this->survey["session"] = "crashed while gathering information";
110 }
111
112 {
113 auto &info = this->survey["info"];
114 if (!this->TryExecute("os", [&info]() { SurveyOS(info["os"]); return true; })) {
115 info["os"] = "crashed while gathering information";
116 }
117 if (!this->TryExecute("openttd", [&info]() { SurveyOpenTTD(info["openttd"]); return true; })) {
118 info["openttd"] = "crashed while gathering information";
119 }
120 if (!this->TryExecute("configuration", [&info]() { SurveyConfiguration(info["configuration"]); return true; })) {
121 info["configuration"] = "crashed while gathering information";
122 }
123 if (!this->TryExecute("font", [&info]() { SurveyFont(info["font"]); return true; })) {
124 info["font"] = "crashed while gathering information";
125 }
126 if (!this->TryExecute("compiler", [&info]() { SurveyCompiler(info["compiler"]); return true; })) {
127 info["compiler"] = "crashed while gathering information";
128 }
129 if (!this->TryExecute("libraries", [&info]() { SurveyLibraries(info["libraries"]); return true; })) {
130 info["libraries"] = "crashed while gathering information";
131 }
132 if (!this->TryExecute("plugins", [&info]() { SurveyPlugins(info["plugins"]); return true; })) {
133 info["plugins"] = "crashed while gathering information";
134 }
135 }
136
137 {
138 auto &game = this->survey["game"];
139 game["local_company"] = _local_company.base();
140 game["current_company"] = _current_company.base();
141
142 if (!this->TryExecute("timers", [&game]() { SurveyTimers(game["timers"]); return true; })) {
143 game["libraries"] = "crashed while gathering information";
144 }
145 if (!this->TryExecute("companies", [&game]() { SurveyCompanies(game["companies"]); return true; })) {
146 game["companies"] = "crashed while gathering information";
147 }
148 if (!this->TryExecute("settings", [&game]() { SurveySettings(game["settings_changed"], true); return true; })) {
149 game["settings"] = "crashed while gathering information";
150 }
151 if (!this->TryExecute("grfs", [&game]() { SurveyGrfs(game["grfs"]); return true; })) {
152 game["grfs"] = "crashed while gathering information";
153 }
154 if (!this->TryExecute("game_script", [&game]() { SurveyGameScript(game["game_script"]); return true; })) {
155 game["game_script"] = "crashed while gathering information";
156 }
157 if (!this->TryExecute("gamelog", [&game]() { SurveyGamelog(game["gamelog"]); return true; })) {
158 game["gamelog"] = "crashed while gathering information";
159 }
160 if (!this->TryExecute("news", [&game]() { SurveyRecentNews(game["news"]); return true; })) {
161 game["news"] = "crashed while gathering information";
162 }
163 }
164}
165
166void CrashLog::PrintCrashLog() const
167{
168 fmt::print(" OpenTTD version:\n");
169 fmt::print(" Version: {}\n", this->survey["info"]["openttd"]["version"]["revision"].get<std::string>());
170 fmt::print(" Hash: {}\n", this->survey["info"]["openttd"]["version"]["hash"].get<std::string>());
171 fmt::print(" NewGRF ver: {}\n", this->survey["info"]["openttd"]["version"]["newgrf"].get<std::string>());
172 fmt::print(" Content ver: {}\n", this->survey["info"]["openttd"]["version"]["content"].get<std::string>());
173 fmt::print("\n");
174
175 fmt::print(" Crash:\n");
176 fmt::print(" Reason: {}\n", this->survey["crash"]["reason"].get<std::string>());
177 fmt::print("\n");
178
179 fmt::print(" Stacktrace:\n");
180 for (const auto &line : this->survey["stacktrace"]) {
181 fmt::print(" {}\n", line.get<std::string>());
182 }
183 fmt::print("\n");
184}
185
192{
193 this->crashlog_filename = this->CreateFileName(".json.log");
194
195 auto file = FioFOpenFile(this->crashlog_filename, "w", NO_DIRECTORY);
196 if (!file.has_value()) return false;
197
198 std::string survey_json = this->survey.dump(4);
199
200 size_t len = survey_json.size();
201 size_t written = fwrite(survey_json.data(), 1, len, *file);
202
203 return len == written;
204}
205
212/* virtual */ bool CrashLog::WriteCrashDump()
213{
214 fmt::print("No method to create a crash.dmp available.\n");
215 return false;
216}
217
224{
225 /* If the map doesn't exist, saving will fail too. If the map got
226 * initialised, there is a big chance the rest is initialised too. */
227 if (!Map::IsInitialized()) return false;
228
229 try {
231
232 this->savegame_filename = this->CreateFileName(".sav");
233
234 /* Don't do a threaded saveload. */
235 return SaveOrLoad(this->savegame_filename, SLO_SAVE, DFT_GAME_FILE, NO_DIRECTORY, false) == SL_OK;
236 } catch (...) {
237 return false;
238 }
239}
240
247{
248 /* Don't draw when we have invalid screen size */
249 if (_screen.width < 1 || _screen.height < 1 || _screen.dst_ptr == nullptr) return false;
250
251 std::string filename = this->CreateFileName("", false);
252 bool res = MakeScreenshot(SC_CRASHLOG, filename);
253 if (res) this->screenshot_filename = _full_screenshot_path;
254 return res;
255}
256
261{
262 if (_game_mode == GM_NORMAL) {
264 }
265}
266
273{
274 /* Don't keep looping logging crashes. */
275 static bool crashlogged = false;
276 if (crashlogged) return;
277 crashlogged = true;
278
279 fmt::print("Crash encountered, generating crash log...\n");
280 this->FillCrashLog();
281 fmt::print("Crash log generated.\n\n");
282
283 fmt::print("Crash in summary:\n");
284 this->TryExecute("crashlog", [this]() { this->PrintCrashLog(); return true; });
285
286 fmt::print("Writing crash log to disk...\n");
287 bool ret = this->TryExecute("crashlog", [this]() { return this->WriteCrashLog(); });
288 if (ret) {
289 fmt::print("Crash log written to {}. Please add this file to any bug reports.\n\n", this->crashlog_filename);
290 } else {
291 fmt::print("Writing crash log failed. Please attach the output above to any bug reports.\n\n");
292 this->crashlog_filename = "(failed to write crash log)";
293 }
294
295 fmt::print("Writing crash dump to disk...\n");
296 ret = this->TryExecute("crashdump", [this]() { return this->WriteCrashDump(); });
297 if (ret) {
298 fmt::print("Crash dump written to {}. Please add this file to any bug reports.\n\n", this->crashdump_filename);
299 } else {
300 fmt::print("Writing crash dump failed.\n\n");
301 this->crashdump_filename = "(failed to write crash dump)";
302 }
303
304 fmt::print("Writing crash savegame...\n");
305 ret = this->TryExecute("savegame", [this]() { return this->WriteSavegame(); });
306 if (ret) {
307 fmt::print("Crash savegame written to {}. Please add this file and the last (auto)save to any bug reports.\n\n", this->savegame_filename);
308 } else {
309 fmt::print("Writing crash savegame failed. Please attach the last (auto)save to any bug reports.\n\n");
310 this->savegame_filename = "(failed to write crash savegame)";
311 }
312
313 fmt::print("Writing crash screenshot...\n");
314 ret = this->TryExecute("screenshot", [this]() { return this->WriteScreenshot(); });
315 if (ret) {
316 fmt::print("Crash screenshot written to {}. Please add this file to any bug reports.\n\n", this->screenshot_filename);
317 } else {
318 fmt::print("Writing crash screenshot failed.\n\n");
319 this->screenshot_filename = "(failed to write crash screenshot)";
320 }
321
322 this->TryExecute("survey", [this]() { this->SendSurvey(); return true; });
323}
324
329/* static */ void CrashLog::SetErrorMessage(const std::string &message)
330{
332}
333
void MakeCrashLog()
Makes the crash log, writes it to a file and then subsequently tries to make a crash dump and crash s...
Definition crashlog.cpp:272
bool WriteCrashLog()
Write the crash log to a file.
Definition crashlog.cpp:191
virtual void SurveyStacktrace(nlohmann::json &survey) const =0
Convert stacktrace to JSON.
std::string CreateFileName(const char *ext, bool with_dir=true) const
Create a timestamped filename.
Definition crashlog.cpp:76
static std::string message
Error message coming from FatalError(format, ...).
Definition crashlog.h:33
void SendSurvey() const
Send the survey result, noting it was a crash.
Definition crashlog.cpp:260
virtual bool TryExecute(std::string_view section_name, std::function< bool()> &&func)=0
Execute the func() and return its value.
virtual bool WriteCrashDump()
Write the (crash) dump to a file.
Definition crashlog.cpp:212
bool WriteSavegame()
Write the (crash) savegame to a file.
Definition crashlog.cpp:223
bool WriteScreenshot()
Write the (crash) screenshot to a file.
Definition crashlog.cpp:246
void FillCrashLog()
Fill the crash log buffer with all data of a crash log.
Definition crashlog.cpp:89
static void SetErrorMessage(const std::string &message)
Sets a message for the error message handler.
Definition crashlog.cpp:329
virtual void SurveyCrash(nlohmann::json &survey) const =0
Convert system crash reason to JSON.
static void AfterCrashLogCleanup()
Try to close the sound/video stuff so it doesn't keep lingering around incorrect video states or so,...
Definition crashlog.cpp:338
virtual void Stop()=0
Stop this driver.
void Emergency()
Logs a emergency savegame.
Definition gamelog.cpp:352
void Print(std::function< void(const std::string &)> proc)
Prints active gamelog.
Definition gamelog.cpp:147
static MusicDriver * GetInstance()
Get the currently active instance of the music driver.
void Transmit(Reason reason, bool blocking=false)
Transmit the survey.
static SoundDriver * GetInstance()
Get the currently active instance of the sound driver.
static YearMonthDay ConvertDateToYMD(Date date)
Converts a Date to a Year, Month & Day.
static VideoDriver * GetInstance()
Get the currently active instance of the video driver.
CompanyID _local_company
Company controlled by the human player at this client. Can also be COMPANY_SPECTATOR.
CompanyID _current_company
Company currently doing an action.
Functions related to companies.
static void SurveyRecentNews(nlohmann::json &json)
Writes up to 32 recent news messages to the buffer, with the most recent first.
Definition crashlog.cpp:55
constexpr uint8_t CRASHLOG_SURVEY_VERSION
The version of the schema of the JSON information.
Definition crashlog.cpp:36
static void SurveyGamelog(nlohmann::json &json)
Writes the gamelog data to the buffer.
Definition crashlog.cpp:42
Functions to be called to log a crash.
std::string _personal_dir
custom directory for personal settings, saves, newgrf, etc.
Definition fileio.cpp:869
std::optional< FileHandle > FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition fileio.cpp:243
Functions for Standard In/Out file operations.
Types for Standard In/Out file operations.
@ SLO_SAVE
File is being saved.
Definition fileio_type.h:56
@ DFT_GAME_FILE
Save game or scenario file.
Definition fileio_type.h:32
@ NO_DIRECTORY
A path without any base directory.
String formatting functions and helpers.
Gamelog _gamelog
Gamelog instance.
Definition gamelog.cpp:31
Functions to be called to log fundamental changes to the game.
Functions related to maps.
Base for all music playback.
Part of the network protocol handling opt-in survey.
Functions related to news.
uint32_t SerialiseNewsReference(const NewsReference &reference)
Encode a NewsReference for serialisation, e.g.
Definition news_gui.cpp:922
const NewsContainer & GetNews()
Get read-only reference to all news items.
Definition news_gui.cpp:79
GUI functions related to the news.
A number of safeguards to prevent using unsafe methods.
SaveOrLoadResult SaveOrLoad(const std::string &filename, SaveLoadOperation fop, DetailedFileType dft, Subdirectory sb, bool threaded)
Main Save or Load function where the high-level saveload functions are handled.
Functions/types related to saving and loading games.
@ SL_OK
completed successfully
Definition saveload.h:409
bool MakeScreenshot(ScreenshotType t, std::string name, uint32_t width, uint32_t height)
Schedule making a screenshot.
std::string _full_screenshot_path
Pathname of the screenshot file.
Functions to make screenshots.
@ SC_CRASHLOG
Raw screenshot from blitter buffer.
Definition screenshot.h:18
Base for all sound drivers.
Definition of base types and functions in a cross-platform compatible way.
static bool IsInitialized()
Check whether the map has been initialized, as to not try to save the map during crashlog when the ma...
Definition map_func.h:353
void SurveySettings(nlohmann::json &survey, bool skip_if_default)
Convert settings to JSON.
Definition survey.cpp:160
void SurveyPlugins(nlohmann::json &survey)
Convert plugin information to JSON.
Definition survey.cpp:461
void SurveyTimers(nlohmann::json &survey)
Convert timer information to JSON.
Definition survey.cpp:339
void SurveyGameSession(nlohmann::json &survey)
Convert game session information to JSON.
Definition survey.cpp:238
void SurveyGameScript(nlohmann::json &survey)
Convert game-script information to JSON.
Definition survey.cpp:382
void SurveyConfiguration(nlohmann::json &survey)
Convert generic game information to JSON.
Definition survey.cpp:252
void SurveyOpenTTD(nlohmann::json &survey)
Convert generic OpenTTD information to JSON.
Definition survey.cpp:206
void SurveyFont(nlohmann::json &survey)
Convert font information to JSON.
Definition survey.cpp:295
void SurveyCompanies(nlohmann::json &survey)
Convert company information to JSON.
Definition survey.cpp:308
void SurveyCompiler(nlohmann::json &survey)
Convert compiler information to JSON.
Definition survey.cpp:178
void SurveyLibraries(nlohmann::json &survey)
Convert compiled libraries information to JSON.
Definition survey.cpp:394
void SurveyGrfs(nlohmann::json &survey)
Convert GRF information to JSON.
Definition survey.cpp:355
Functions to survey the current game / system, for crashlog and network-survey.
Base of all video drivers.