Freeciv21
Develop your civilization from humble roots to a global empire
log.cpp
Go to the documentation of this file.
1 /*__ ___ ***************************************
2 / \ / \ Copyright (c) 1996-2020 Freeciv21 and Freeciv
3 \_ \ / __/ contributors. This file is part of Freeciv21.
4  _\ \ / /__ Freeciv21 is free software: you can redistribute it
5  \___ \____/ __/ and/or modify it under the terms of the GNU General
6  \_ _/ Public License as published by the Free Software
7  | @ @ \_ Foundation, either version 3 of the License,
8  | or (at your option) any later version.
9  _/ /\ You should have received a copy of the GNU
10  /o) (o/\ \_ General Public License along with Freeciv21.
11  \_____/ / If not, see https://www.gnu.org/licenses/.
12  \____/ ********************************************************/
13 
14 #include <fc_config.h>
15 
16 #include <vector>
17 
18 // Qt
19 #include <QFileInfo>
20 #include <QLoggingCategory>
21 #include <QMutexLocker>
22 #include <QString>
23 
24 // Windows
25 #ifdef Q_OS_WIN
26 #include <windows.h>
27 #endif
28 
29 // utility
30 #include "fcintl.h"
31 #include "shared.h"
32 
33 #include "log.h"
34 
35 Q_LOGGING_CATEGORY(assert_category, "freeciv.assert")
36 
37 namespace {
38 static QString log_level = QStringLiteral();
39 static bool fatal_assertions = false;
40 
41 static QBasicMutex mutex;
42 static void handle_message(QtMsgType type, const QMessageLogContext &context,
43  const QString &message);
44 static QtMessageHandler original_handler = nullptr;
45 static QFile *log_file = nullptr;
46 } // anonymous namespace
47 
55 bool log_init(const QString &level_str, const QStringList &extra_rules)
56 {
57  // Even if it's invalid.
58  log_level = level_str;
59 
60  // Install our handler
61  original_handler = qInstallMessageHandler(&handle_message);
62 
63 #ifdef Q_OS_WIN
64  {
65  // Enable VT-100 mode in cmd.exe
66  auto handle = GetStdHandle(STD_OUTPUT_HANDLE);
67  if (handle != INVALID_HANDLE_VALUE) {
68  DWORD mode = 0;
69  if (GetConsoleMode(handle, &mode)) {
70  mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
71  SetConsoleMode(handle, mode);
72  }
73  }
74  }
75 #endif
76 
77  // Set the default format (override with QT_MESSAGE_PATTERN)
78 #ifdef QT_DEBUG
79  // In debug builds, we have the source location
80  qSetMessagePattern(
81  QStringLiteral("[%{type}] %{appname} (%{file}:%{line}) - %{message}"));
82 #else
83  // Not a debug build, the function name will not be known
84  qSetMessagePattern(QStringLiteral("[%{type}] %{appname} - %{message}"));
85 #endif
86 
87  // Create default filter rules to pass to Qt. We do it this way so the user
88  // can override our simplistic rules with environment variables.
89  auto rules = QStringList();
90  if (level_str == QStringLiteral("fatal")) {
91  // Level "fatal" cannot be disabled, so we omit it below.
92  rules += {
93  QStringLiteral("*.critical = false"),
94  QStringLiteral("*.warning = false"),
95  QStringLiteral("*.info = false"),
96  QStringLiteral("*.debug = false"),
97  };
98  } else if (level_str == QStringLiteral("critical")) {
99  rules += {
100  QStringLiteral("*.critical = true"),
101  QStringLiteral("*.warning = false"),
102  QStringLiteral("*.info = false"),
103  QStringLiteral("*.debug = false"),
104  };
105  } else if (level_str == QStringLiteral("warning")) {
106  rules += {
107  QStringLiteral("*.critical = true"),
108  QStringLiteral("*.warning = true"),
109  QStringLiteral("*.info = false"),
110  QStringLiteral("*.debug = false"),
111  };
112  } else if (level_str == QStringLiteral("info")) {
113  rules += {
114  QStringLiteral("*.critical = true"),
115  QStringLiteral("*.warning = true"),
116  QStringLiteral("*.info = true"),
117  QStringLiteral("*.debug = false"),
118  QStringLiteral("qt.*.info = false"),
119  };
120  } else if (level_str == QStringLiteral("debug")) {
121  rules += {
122  QStringLiteral("*.critical = true"),
123  QStringLiteral("*.warning = true"),
124  QStringLiteral("*.info = true"),
125  QStringLiteral("*.debug = true"),
126  QStringLiteral("qt.*.info = false"),
127  QStringLiteral("qt.*.debug = false"),
128  };
129  } else {
130  // Not a known name
131  // TRANS: Do not translate "fatal", "critical", "warning", "info" or
132  // "debug". It's exactly what the user must type.
133  qCritical(_("\"%s\" is not a valid log level name (valid names are "
134  "fatal/critical/warning/info/debug)"),
135  qUtf8Printable(level_str));
136  return false;
137  }
138 
139  rules += extra_rules;
140  QLoggingCategory::setFilterRules(rules.join('\n'));
141 
142  qDebug() << "Applied logging rules" << rules;
143 
144  return true;
145 }
146 
151 namespace {
152 static void handle_message(QtMsgType type, const QMessageLogContext &context,
153  const QString &message)
154 {
155  // Forward to file
156  if (log_file != nullptr) {
157  QMutexLocker lock(&mutex);
158  log_file->write((message + QStringLiteral("\n")).toLocal8Bit());
159 
160  // Make sure we flush when it looks serious, maybe we'll crash soon
161  if (type == QtFatalMsg || type == QtCriticalMsg) {
162  log_file->flush();
163  }
164  }
165 
166  // Forward to the Qt handler
167  if (original_handler != nullptr) {
168  original_handler(type, context, message);
169  }
170 }
171 } // anonymous namespace
172 
177 void log_set_file(const QString &path)
178 {
179  // Don't try to open null file names
180  if (path.isEmpty()) {
181  return;
182  }
183 
184  // Open a new file. Note that we can't hold the mutex because QFile
185  // might want to log.
186  auto *new_file = new QFile(path);
187  if (!new_file->open(QIODevice::WriteOnly | QIODevice::Text)) {
188  // Could not open the log file.
189  // TRANS: %1 is an error message
190  qCritical().noquote()
191  << QString(_("Could not open log file for writing: %1"))
192  .arg(new_file->errorString()); // FIXME translate?
193  // Keep the old one if it was there
194  return;
195  }
196 
197  // Unset the old one
198  delete log_file;
199  log_file = new_file;
200 }
201 
207 const QString &log_get_level() { return log_level; }
208 
212 void log_close()
213 {
214  QMutexLocker locker(&mutex);
215 
216  delete log_file;
217  log_file = nullptr;
218  // Reinstall the old handler
219  qInstallMessageHandler(original_handler);
220 }
221 
226 void fc_assert_set_fatal(bool fatal) { fatal_assertions = fatal; }
227 
231 bool fc_assert_are_fatal() { return fatal_assertions; }
232 
236 void fc_assert_handle_failure(const char *condition, const char *file,
237  int line, const char *function,
238  const QString &message)
239 {
240  Q_UNUSED(function)
241  QMessageLogger logger(file, line, assert_category().categoryName());
242  logger.critical("Assertion %s failed", condition);
243  if (!message.isEmpty()) {
244  logger.critical().noquote() << message;
245  }
246  logger.critical().noquote() // TRANS: No full stop after the URL.
247  << QString(_("Please report this message at %1")).arg(BUG_URL);
248  if (fc_assert_are_fatal()) {
249  logger.fatal("%s", _("Assertion failed"));
250  }
251 }
252 
253 void log_time(const QString &msg, bool log)
254 {
255  static bool logging;
256  if (log) {
257  logging = true;
258  }
259  if (logging) {
260  qInfo() << qUtf8Printable(msg);
261  }
262 }
#define _(String)
Definition: fcintl.h:50
const QString & log_get_level()
Retrieves the log level passed to log_init (even if log_init failed).
Definition: log.cpp:207
void fc_assert_handle_failure(const char *condition, const char *file, int line, const char *function, const QString &message)
Handles a failed assertion.
Definition: log.cpp:236
void log_time(const QString &msg, bool log)
Definition: log.cpp:253
bool log_init(const QString &level_str, const QStringList &extra_rules)
Parses a log level string as provided by the user on the command line, and installs the corresponding...
Definition: log.cpp:55
bool fc_assert_are_fatal()
Checks whether the fc_assert* macros should raise on failed assertion.
Definition: log.cpp:231
void fc_assert_set_fatal(bool fatal)
Set what signal the assert* macros should raise on failed assertion (-1 to disable).
Definition: log.cpp:226
void log_set_file(const QString &path)
Redirects the log to a file.
Definition: log.cpp:177
void log_close()
Deinitialize logging module.
Definition: log.cpp:212
Q_LOGGING_CATEGORY(tileset_category, "freeciv.tileset")
Functions for handling the tilespec files which describe the files and contents of tilesets.