OpenTTD Source  14.0-beta3
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"
15 #include "music/music_driver.hpp"
16 #include "sound/sound_driver.hpp"
17 #include "video/video_driver.hpp"
18 #include "saveload/saveload.h"
19 #include "screenshot.h"
20 #include "network/network_survey.h"
21 #include "news_gui.h"
22 #include "fileio_func.h"
23 #include "fileio_type.h"
24 
25 #include "company_func.h"
26 #include "3rdparty/fmt/chrono.h"
27 #include "3rdparty/fmt/std.h"
28 #include "core/format.hpp"
29 
30 #include "safeguards.h"
31 
32 /* static */ std::string CrashLog::message{};
33 
35 constexpr uint8_t CRASHLOG_SURVEY_VERSION = 1;
36 
41 static void SurveyGamelog(nlohmann::json &json)
42 {
43  json = nlohmann::json::array();
44 
45  _gamelog.Print([&json](const std::string &s) {
46  json.push_back(s);
47  });
48 }
49 
54 static void SurveyRecentNews(nlohmann::json &json)
55 {
56  json = nlohmann::json::array();
57 
58  int i = 0;
59  for (NewsItem *news = _latest_news; i < 32 && news != nullptr; news = news->prev, i++) {
60  TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(news->date);
61  json.push_back(fmt::format("({}-{:02}-{:02}) StringID: {}, Type: {}, Ref1: {}, {}, Ref2: {}, {}",
62  ymd.year, ymd.month + 1, ymd.day, news->string_id, news->type,
63  news->reftype1, news->ref1, news->reftype2, news->ref2));
64  }
65 }
66 
73 std::string CrashLog::CreateFileName(const char *ext, bool with_dir) const
74 {
75  static std::string crashname;
76 
77  if (crashname.empty()) {
78  crashname = fmt::format("crash{:%Y%m%d%H%M%S}", fmt::gmtime(time(nullptr)));
79  }
80  return fmt::format("{}{}{}", with_dir ? _personal_dir : std::string{}, crashname, ext);
81 }
82 
87 {
88  /* Reminder: this JSON is read in an automated fashion.
89  * If any structural changes are applied, please bump the version. */
90  this->survey["schema"] = CRASHLOG_SURVEY_VERSION;
91  this->survey["date"] = fmt::format("{:%Y-%m-%d %H:%M:%S} (UTC)", fmt::gmtime(time(nullptr)));
92 
93  /* If no internal reason was logged, it must be a crash. */
94  if (CrashLog::message.empty()) {
95  this->SurveyCrash(this->survey["crash"]);
96  } else {
97  this->survey["crash"]["reason"] = CrashLog::message;
98  CrashLog::message.clear();
99  }
100 
101  if (!this->TryExecute("stacktrace", [this]() { this->SurveyStacktrace(this->survey["stacktrace"]); return true; })) {
102  this->survey["stacktrace"] = "crashed while gathering information";
103  }
104 
105  {
106  auto &info = this->survey["info"];
107  if (!this->TryExecute("os", [&info]() { SurveyOS(info["os"]); return true; })) {
108  info["os"] = "crashed while gathering information";
109  }
110  if (!this->TryExecute("openttd", [&info]() { SurveyOpenTTD(info["openttd"]); return true; })) {
111  info["openttd"] = "crashed while gathering information";
112  }
113  if (!this->TryExecute("configuration", [&info]() { SurveyConfiguration(info["configuration"]); return true; })) {
114  info["configuration"] = "crashed while gathering information";
115  }
116  if (!this->TryExecute("font", [&info]() { SurveyFont(info["font"]); return true; })) {
117  info["font"] = "crashed while gathering information";
118  }
119  if (!this->TryExecute("compiler", [&info]() { SurveyCompiler(info["compiler"]); return true; })) {
120  info["compiler"] = "crashed while gathering information";
121  }
122  if (!this->TryExecute("libraries", [&info]() { SurveyLibraries(info["libraries"]); return true; })) {
123  info["libraries"] = "crashed while gathering information";
124  }
125  if (!this->TryExecute("plugins", [&info]() { SurveyPlugins(info["plugins"]); return true; })) {
126  info["plugins"] = "crashed while gathering information";
127  }
128  }
129 
130  {
131  auto &game = this->survey["game"];
132  game["local_company"] = _local_company;
133  game["current_company"] = _current_company;
134 
135  if (!this->TryExecute("timers", [&game]() { SurveyTimers(game["timers"]); return true; })) {
136  game["libraries"] = "crashed while gathering information";
137  }
138  if (!this->TryExecute("companies", [&game]() { SurveyCompanies(game["companies"]); return true; })) {
139  game["companies"] = "crashed while gathering information";
140  }
141  if (!this->TryExecute("settings", [&game]() { SurveySettings(game["settings_changed"], true); return true; })) {
142  game["settings"] = "crashed while gathering information";
143  }
144  if (!this->TryExecute("grfs", [&game]() { SurveyGrfs(game["grfs"]); return true; })) {
145  game["grfs"] = "crashed while gathering information";
146  }
147  if (!this->TryExecute("game_script", [&game]() { SurveyGameScript(game["game_script"]); return true; })) {
148  game["game_script"] = "crashed while gathering information";
149  }
150  if (!this->TryExecute("gamelog", [&game]() { SurveyGamelog(game["gamelog"]); return true; })) {
151  game["gamelog"] = "crashed while gathering information";
152  }
153  if (!this->TryExecute("news", [&game]() { SurveyRecentNews(game["news"]); return true; })) {
154  game["news"] = "crashed while gathering information";
155  }
156  }
157 }
158 
159 void CrashLog::PrintCrashLog() const
160 {
161  fmt::print(" OpenTTD version:\n");
162  fmt::print(" Version: {}\n", this->survey["info"]["openttd"]["version"]["revision"].get<std::string>());
163  fmt::print(" Hash: {}\n", this->survey["info"]["openttd"]["version"]["hash"].get<std::string>());
164  fmt::print(" NewGRF ver: {}\n", this->survey["info"]["openttd"]["version"]["newgrf"].get<std::string>());
165  fmt::print(" Content ver: {}\n", this->survey["info"]["openttd"]["version"]["content"].get<std::string>());
166  fmt::print("\n");
167 
168  fmt::print(" Crash:\n");
169  fmt::print(" Reason: {}\n", this->survey["crash"]["reason"].get<std::string>());
170  fmt::print("\n");
171 
172  fmt::print(" Stacktrace:\n");
173  for (const auto &line : this->survey["stacktrace"]) {
174  fmt::print(" {}\n", line.get<std::string>());
175  }
176  fmt::print("\n");
177 }
178 
185 {
186  this->crashlog_filename = this->CreateFileName(".json.log");
187 
188  FILE *file = FioFOpenFile(this->crashlog_filename, "w", NO_DIRECTORY);
189  if (file == nullptr) return false;
190 
191  std::string survey_json = this->survey.dump(4);
192 
193  size_t len = survey_json.size();
194  size_t written = fwrite(survey_json.data(), 1, len, file);
195 
196  FioFCloseFile(file);
197  return len == written;
198 }
199 
206 /* virtual */ bool CrashLog::WriteCrashDump()
207 {
208  fmt::print("No method to create a crash.dmp available.\n");
209  return false;
210 }
211 
218 {
219  /* If the map doesn't exist, saving will fail too. If the map got
220  * initialised, there is a big chance the rest is initialised too. */
221  if (!Map::IsInitialized()) return false;
222 
223  try {
225 
226  this->savegame_filename = this->CreateFileName(".sav");
227 
228  /* Don't do a threaded saveload. */
229  return SaveOrLoad(this->savegame_filename, SLO_SAVE, DFT_GAME_FILE, NO_DIRECTORY, false) == SL_OK;
230  } catch (...) {
231  return false;
232  }
233 }
234 
241 {
242  /* Don't draw when we have invalid screen size */
243  if (_screen.width < 1 || _screen.height < 1 || _screen.dst_ptr == nullptr) return false;
244 
245  std::string filename = this->CreateFileName("", false);
246  bool res = MakeScreenshot(SC_CRASHLOG, filename);
247  if (res) this->screenshot_filename = _full_screenshot_path;
248  return res;
249 }
250 
255 {
256  if (_game_mode == GM_NORMAL) {
258  }
259 }
260 
267 {
268  /* Don't keep looping logging crashes. */
269  static bool crashlogged = false;
270  if (crashlogged) return;
271  crashlogged = true;
272 
273  fmt::print("Crash encountered, generating crash log...\n");
274  this->FillCrashLog();
275  fmt::print("Crash log generated.\n\n");
276 
277  fmt::print("Crash in summary:\n");
278  this->TryExecute("crashlog", [this]() { this->PrintCrashLog(); return true; });
279 
280  fmt::print("Writing crash log to disk...\n");
281  bool ret = this->TryExecute("crashlog", [this]() { return this->WriteCrashLog(); });
282  if (ret) {
283  fmt::print("Crash log written to {}. Please add this file to any bug reports.\n\n", this->crashlog_filename);
284  } else {
285  fmt::print("Writing crash log failed. Please attach the output above to any bug reports.\n\n");
286  this->crashlog_filename = "(failed to write crash log)";
287  }
288 
289  fmt::print("Writing crash dump to disk...\n");
290  ret = this->TryExecute("crashdump", [this]() { return this->WriteCrashDump(); });
291  if (ret) {
292  fmt::print("Crash dump written to {}. Please add this file to any bug reports.\n\n", this->crashdump_filename);
293  } else {
294  fmt::print("Writing crash dump failed.\n\n");
295  this->crashdump_filename = "(failed to write crash dump)";
296  }
297 
298  fmt::print("Writing crash savegame...\n");
299  ret = this->TryExecute("savegame", [this]() { return this->WriteSavegame(); });
300  if (ret) {
301  fmt::print("Crash savegame written to {}. Please add this file and the last (auto)save to any bug reports.\n\n", this->savegame_filename);
302  } else {
303  fmt::print("Writing crash savegame failed. Please attach the last (auto)save to any bug reports.\n\n");
304  this->savegame_filename = "(failed to write crash savegame)";
305  }
306 
307  fmt::print("Writing crash screenshot...\n");
308  ret = this->TryExecute("screenshot", [this]() { return this->WriteScreenshot(); });
309  if (ret) {
310  fmt::print("Crash screenshot written to {}. Please add this file to any bug reports.\n\n", this->screenshot_filename);
311  } else {
312  fmt::print("Writing crash screenshot failed.\n\n");
313  this->screenshot_filename = "(failed to write crash screenshot)";
314  }
315 
316  this->TryExecute("survey", [this]() { this->SendSurvey(); return true; });
317 }
318 
323 /* static */ void CrashLog::SetErrorMessage(const std::string &message)
324 {
326 }
327 
333 {
337 }
SurveyConfiguration
void SurveyConfiguration(nlohmann::json &survey)
Convert generic game information to JSON.
Definition: survey.cpp:243
SurveyLibraries
void SurveyLibraries(nlohmann::json &survey)
Convert compiled libraries information to JSON.
Definition: survey.cpp:386
SurveyTimers
void SurveyTimers(nlohmann::json &survey)
Convert timer information to JSON.
Definition: survey.cpp:330
_personal_dir
std::string _personal_dir
custom directory for personal settings, saves, newgrf, etc.
Definition: fileio.cpp:953
CrashLog::AfterCrashLogCleanup
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:332
NewsItem
Information about a single item of news.
Definition: news_type.h:128
_gamelog
Gamelog _gamelog
Gamelog instance.
Definition: gamelog.cpp:31
CrashLog::WriteCrashLog
bool WriteCrashLog()
Write the crash log to a file.
Definition: crashlog.cpp:184
SaveOrLoad
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.
Definition: saveload.cpp:3037
CrashLog::SurveyCrash
virtual void SurveyCrash(nlohmann::json &survey) const =0
Convert system crash reason to JSON.
CrashLog::MakeCrashLog
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:266
map_func.h
Map::IsInitialized
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:354
format.hpp
DFT_GAME_FILE
@ DFT_GAME_FILE
Save game or scenario file.
Definition: fileio_type.h:31
saveload.h
fileio_func.h
gamelog.h
SurveyGamelog
static void SurveyGamelog(nlohmann::json &json)
Writes the gamelog data to the buffer.
Definition: crashlog.cpp:41
CrashLog::FillCrashLog
void FillCrashLog()
Fill the crash log buffer with all data of a crash log.
Definition: crashlog.cpp:86
screenshot.h
SurveySettings
void SurveySettings(nlohmann::json &survey, bool skip_if_default)
Convert settings to JSON.
Definition: survey.cpp:160
Gamelog::Emergency
void Emergency()
Logs a emergency savegame.
Definition: gamelog.cpp:352
SLO_SAVE
@ SLO_SAVE
File is being saved.
Definition: fileio_type.h:50
survey.h
MusicDriver::GetInstance
static MusicDriver * GetInstance()
Get the currently active instance of the music driver.
Definition: music_driver.hpp:46
SurveyRecentNews
static void SurveyRecentNews(nlohmann::json &json)
Writes up to 32 recent news messages to the buffer, with the most recent first.
Definition: crashlog.cpp:54
FioFOpenFile
FILE * 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:263
SurveyOpenTTD
void SurveyOpenTTD(nlohmann::json &survey)
Convert generic OpenTTD information to JSON.
Definition: survey.cpp:206
_local_company
CompanyID _local_company
Company controlled by the human player at this client. Can also be COMPANY_SPECTATOR.
Definition: company_cmd.cpp:49
SurveyCompanies
void SurveyCompanies(nlohmann::json &survey)
Convert company information to JSON.
Definition: survey.cpp:299
TimerGameCalendar::ConvertDateToYMD
static YearMonthDay ConvertDateToYMD(Date date)
Converts a Date to a Year, Month & Day.
Definition: timer_game_calendar.cpp:42
safeguards.h
music_driver.hpp
CrashLog::CreateFileName
std::string CreateFileName(const char *ext, bool with_dir=true) const
Create a timestamped filename.
Definition: crashlog.cpp:73
NewsItem::prev
NewsItem * prev
Previous news item.
Definition: news_type.h:129
SurveyGrfs
void SurveyGrfs(nlohmann::json &survey)
Convert GRF information to JSON.
Definition: survey.cpp:347
stdafx.h
NetworkSurveyHandler::Transmit
void Transmit(Reason reason, bool blocking=false)
Transmit the survey.
Definition: network_survey.cpp:87
VideoDriver::GetInstance
static VideoDriver * GetInstance()
Get the currently active instance of the video driver.
Definition: video_driver.hpp:200
sound_driver.hpp
CrashLog::WriteScreenshot
bool WriteScreenshot()
Write the (crash) screenshot to a file.
Definition: crashlog.cpp:240
_current_company
CompanyID _current_company
Company currently doing an action.
Definition: company_cmd.cpp:50
CRASHLOG_SURVEY_VERSION
constexpr uint8_t CRASHLOG_SURVEY_VERSION
The version of the schema of the JSON information.
Definition: crashlog.cpp:35
SurveyCompiler
void SurveyCompiler(nlohmann::json &survey)
Convert compiler information to JSON.
Definition: survey.cpp:178
Gamelog::Print
void Print(std::function< void(const std::string &)> proc)
Prints active gamelog.
Definition: gamelog.cpp:147
CrashLog::SetErrorMessage
static void SetErrorMessage(const std::string &message)
Sets a message for the error message handler.
Definition: crashlog.cpp:323
video_driver.hpp
NO_DIRECTORY
@ NO_DIRECTORY
A path without any base directory.
Definition: fileio_type.h:126
news_gui.h
fileio_type.h
company_func.h
_latest_news
NewsItem * _latest_news
tail of news items queue
Definition: news_gui.cpp:53
SoundDriver::GetInstance
static SoundDriver * GetInstance()
Get the currently active instance of the sound driver.
Definition: sound_driver.hpp:35
crashlog.h
CrashLog::SendSurvey
void SendSurvey() const
Send the survey result, noting it was a crash.
Definition: crashlog.cpp:254
CrashLog::message
static std::string message
Error message coming from #FatalError(format, ...).
Definition: crashlog.h:21
SC_CRASHLOG
@ SC_CRASHLOG
Raw screenshot from blitter buffer.
Definition: screenshot.h:20
network_survey.h
MakeScreenshot
bool MakeScreenshot(ScreenshotType t, std::string name, uint32_t width, uint32_t height)
Schedule making a screenshot.
Definition: screenshot.cpp:979
CrashLog::WriteCrashDump
virtual bool WriteCrashDump()
Write the (crash) dump to a file.
Definition: crashlog.cpp:206
NetworkSurveyHandler::Reason::CRASH
@ CRASH
Game crashed.
SurveyGameScript
void SurveyGameScript(nlohmann::json &survey)
Convert game-script information to JSON.
Definition: survey.cpp:374
CrashLog::TryExecute
virtual bool TryExecute(std::string_view section_name, std::function< bool()> &&func)=0
Execute the func() and return its value.
Driver::Stop
virtual void Stop()=0
Stop this driver.
SL_OK
@ SL_OK
completed successfully
Definition: saveload.h:384
SurveyFont
void SurveyFont(nlohmann::json &survey)
Convert font information to JSON.
Definition: survey.cpp:286
SurveyPlugins
void SurveyPlugins(nlohmann::json &survey)
Convert plugin information to JSON.
Definition: survey.cpp:456
FioFCloseFile
void FioFCloseFile(FILE *f)
Close a file in a safe way.
Definition: fileio.cpp:148
_full_screenshot_path
std::string _full_screenshot_path
Pathname of the screenshot file.
Definition: screenshot.cpp:44
CrashLog::WriteSavegame
bool WriteSavegame()
Write the (crash) savegame to a file.
Definition: crashlog.cpp:217
CrashLog::SurveyStacktrace
virtual void SurveyStacktrace(nlohmann::json &survey) const =0
Convert stacktrace to JSON.