swift
logpattern.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2014 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "misc/logpattern.h"
5 
6 #include <algorithm>
7 
8 #include <QHash>
9 #include <QList>
10 
11 #include "misc/logcategory.h"
12 #include "misc/logcategorylist.h"
13 #include "misc/sequence.h"
14 
15 namespace swift::misc
16 {
17  const QHash<QString, CLogPattern> &CLogPattern::allHumanReadablePatterns()
18  {
19  static const QHash<QString, CLogPattern> patterns {
20  { "uncategorized (swift)", exactMatch(CLogCategories::uncategorized()) },
21  { "background task", exactMatch(CLogCategories::worker()) },
22  { "cache", exactMatch(CLogCategories::cache()) },
23  { "cmd.line handling", exactMatch(CLogCategories::cmdLine()) },
24  { "data inconsistency", exactMatch(CLogCategories::dataInconsistency()) },
25  { "DBus", exactMatch(CLogCategories::dbus()) },
26  { "downloading data", exactMatch(CLogCategories::download()) },
27  { "driver", exactMatch(CLogCategories::driver()) },
28  { "flight plan", exactMatch(CLogCategories::flightPlan()) },
29  { "FSD", exactMatch(CLogCategories::fsd()) },
30  { "interpolator", exactMatch(CLogCategories::interpolator()) },
31  { "JSON (conversion)", exactMatch(CLogCategories::json()) },
32  { "model cache", exactMatch(CLogCategories::modelCache()) },
33  { "model GUI", exactMatch(CLogCategories::modelGui()) },
34  { "model loader", exactMatch(CLogCategories::modelLoader()) },
35  { "model mapping", exactMatch(CLogCategories::mapping()) },
36  { "model matching", exactMatch(CLogCategories::matching()) },
37  { "model set cache", exactMatch(CLogCategories::modelSetCache()) },
38  { "network (flight)", exactMatch(CLogCategories::network()) },
39  { "plugin", exactMatch(CLogCategories::plugin()) },
40  { "services", exactMatch(CLogCategories::services()) },
41  { "settings", exactMatch(CLogCategories::settings()) },
42  { "startup phase", exactMatch(CLogCategories::startup()) },
43  { "swift context slots", exactMatch(CLogCategories::contextSlot()) },
44  { "swift contexts", exactMatch(CLogCategories::context()) },
45  { "swift core", exactMatch(CLogCategories::swiftCore()) },
46  { "swift data tool", exactMatch(CLogCategories::swiftDataTool()) },
47  { "swift DB webservice related", exactMatch(CLogCategories::swiftDbWebservice()) },
48  { "swift GUI", exactMatch(CLogCategories::guiComponent()) },
49  { "swift pilot client", exactMatch(CLogCategories::swiftPilotClient()) },
50  { "validation", exactMatch(CLogCategories::validation()) },
51  { "VATSIM specific", exactMatch(CLogCategories::vatsimSpecific()) },
52  { "verification", exactMatch(CLogCategories::verification()) },
53  { "webservice related", exactMatch(CLogCategories::webservice()) },
54  { "wizard", exactMatch(CLogCategories::wizard()) },
55  { "Qt library", startsWith("qt.") },
56  { "uncategorized (default)", exactMatch("default") },
57  { "uncategorized (none)", empty() }
58  };
59  return patterns;
60  }
61 
63  {
64  static const QStringList names = allHumanReadablePatterns().keys();
65  return names;
66  }
67 
69  {
70  QStringList patternNames;
71  for (const QString &name : CLogPattern::allHumanReadableNames())
72  {
73  if (CLogPattern::fromHumanReadableName(name).match(message)) { patternNames.push_back(name); }
74  }
75  return patternNames;
76  }
77 
79  {
80  if (message.getCategories().isEmpty()) { return {}; }
81  QStringList c(humanReadableNamesFrom(message).join(", "));
82  return c.isEmpty() ? message.getCategories().toQStringList() : c;
83  }
84 
85  const CLogPattern &CLogPattern::fromHumanReadableName(const QString &name)
86  {
87  static const CLogPattern empty {};
88  auto it = allHumanReadablePatterns().find(name);
89  return it == allHumanReadablePatterns().end() ? empty : *it;
90  }
91 
92  CLogPattern::CLogPattern(Strategy strategy, const QSet<QString> &strings) : m_strategy(strategy), m_strings(strings)
93  {
94  static const decltype(m_severities) s { CStatusMessage::SeverityDebug, CStatusMessage::SeverityInfo,
96  m_severities = s;
97  }
98 
99  CLogPattern::CLogPattern() : CLogPattern(Everything, {}) {}
100 
102  {
103  return { ExactMatch, { category.toQString() } };
104  }
105 
107  {
108  if (categories.size() == 0) { return empty(); }
109  if (categories.size() == 1) { return exactMatch(categories[0]); }
110  const QStringList strList = categories.toQStringList();
111  return { AnyOf, QSet<QString>(strList.begin(), strList.end()) };
112  }
113 
115  {
116  if (categories.size() == 0) { return {}; }
117  if (categories.size() == 1) { return exactMatch(categories[0]); }
118  const QStringList strList = categories.toQStringList();
119  return { AnyOf, QSet<QString>(strList.begin(), strList.end()) };
120  }
121 
122  CLogPattern CLogPattern::startsWith(const QString &prefix) { return { StartsWith, { prefix } }; }
123 
124  CLogPattern CLogPattern::endsWith(const QString &suffix) { return { EndsWith, { suffix } }; }
125 
126  CLogPattern CLogPattern::contains(const QString &substring) { return { Contains, { substring } }; }
127 
128  CLogPattern CLogPattern::empty() { return { Nothing, {} }; }
129 
131  {
132  auto result = *this;
133  result.m_severities = { severity };
134  return result;
135  }
136 
137  CLogPattern CLogPattern::withSeverities(const QSet<CStatusMessage::StatusSeverity> &severities) const
138  {
139  auto result = *this;
140  result.m_severities = severities;
141  return result;
142  }
143 
145  {
146  auto result = *this;
147  result.m_severities.clear();
148  switch (minimumSeverity)
149  {
150  default:
151  case CStatusMessage::SeverityDebug: result.m_severities.insert(CStatusMessage::SeverityDebug); [[fallthrough]];
152  case CStatusMessage::SeverityInfo: result.m_severities.insert(CStatusMessage::SeverityInfo); [[fallthrough]];
154  result.m_severities.insert(CStatusMessage::SeverityWarning);
155  [[fallthrough]];
156  case CStatusMessage::SeverityError: result.m_severities.insert(CStatusMessage::SeverityError);
157  }
158  return result;
159  }
160 
161  bool CLogPattern::checkInvariants() const
162  {
163  switch (m_strategy)
164  {
165  case Everything: return m_strings.isEmpty();
166  case ExactMatch: return m_strings.size() == 1;
167  case AnyOf: return m_strings.size() > 1;
168  case AllOf: return m_strings.size() > 1;
169  case StartsWith: return m_strings.size() == 1;
170  case EndsWith: return m_strings.size() == 1;
171  case Contains: return m_strings.size() == 1;
172  case Nothing: return m_strings.isEmpty();
173  default: return false;
174  }
175  }
176 
177  bool CLogPattern::match(const CStatusMessage &message) const
178  {
179  if (!checkInvariants())
180  {
181  Q_ASSERT(false);
182  return true;
183  }
184 
185  if (!m_severities.contains(message.getSeverity())) { return false; }
186 
187  switch (m_strategy)
188  {
189  default:
190  case Everything: return true;
191  case ExactMatch: return message.getCategories().contains(getString());
192  case AnyOf:
193  return std::any_of(m_strings.begin(), m_strings.end(),
194  [&](const QString &s) { return message.getCategories().contains(s); });
195  case AllOf:
196  return std::all_of(m_strings.begin(), m_strings.end(),
197  [&](const QString &s) { return message.getCategories().contains(s); });
198  case StartsWith:
199  return message.getCategories().containsBy(
200  [this](const CLogCategory &cat) { return cat.startsWith(getPrefix()); });
201  case EndsWith:
202  return message.getCategories().containsBy(
203  [this](const CLogCategory &cat) { return cat.endsWith(getSuffix()); });
204  case Contains:
205  return message.getCategories().containsBy(
206  [this](const CLogCategory &cat) { return cat.contains(getSubstring()); });
207  case Nothing: return message.getCategories().isEmpty();
208  }
209  }
210 
212  {
213  if (!(checkInvariants() && other.checkInvariants()))
214  {
215  Q_ASSERT(false);
216  return false;
217  }
218 
219  // For this function to return true, the severities matched by this pattern must be a subset of the severities
220  // matched by the other, and the categories matched by this pattern must be a subset of the categories matched
221  // by the other, and at least one of these two subset relations must be a proper subset relation.
222 
223  if (!other.m_severities.contains(m_severities))
224  {
225  // Severities are not a subset
226  return false;
227  }
228  if (m_strategy == other.m_strategy && m_strings == other.m_strings)
229  {
230  // Severities are a subset, and categories are an improper subset, so it all depends
231  // on whether the subset relation of the severities is proper or improper
232  return m_severities.size() < other.m_severities.size();
233  }
234 
235  // If we got this far then the severity set is a (proper or improper) subset of the other severity set,
236  // and the question of whether this pattern is a proper subset of the other pattern depends upon whether the
237  // set of categories matched by this pattern is a proper subset of the set of categories matched by the other.
238  // The matrix below is a guide to the implementation that follows.
239  //
240  // Matrix of "categories matched by X is proper subset of categories matched by Y" for two strategies X and Y
241  // 0 means always false
242  // 1 means always true
243  // ? means it depends on a particular relation between their string sets
244  //
245  // Y Ev EM An Al SW EW Co No
246  // X
247  // Ev 0 0 0 0 0 0 0 0 (Everything)
248  // EM 1 0 ? 0 ? ? ? 0 (ExactMatch)
249  // An 1 0 ? 0 ? ? ? 0 (AnyOf)
250  // Al 1 ? ? ? ? ? ? 0 (AllOf)
251  // SW 1 0 0 0 ? 0 ? 0 (StartsWith)
252  // EW 1 0 0 0 0 ? ? 0 (EndsWith)
253  // Co 1 0 0 0 0 0 ? 0 (Contains)
254  // No 1 0 0 0 0 0 0 0 (Nothing)
255 
256  if (m_strategy != Everything && other.m_strategy == Everything) { return true; }
257  switch (m_strategy)
258  {
259  case ExactMatch:
260  switch (other.m_strategy)
261  {
262  case AnyOf: return other.m_strings.contains(getString());
263  case StartsWith: return getString().startsWith(other.getPrefix());
264  case EndsWith: return getString().endsWith(other.getSuffix());
265  case Contains: return getString().contains(other.getSubstring());
266  default:;
267  }
268  break;
269  case AnyOf:
270  switch (other.m_strategy)
271  {
272  case AnyOf: return other.m_strings.contains(m_strings) && other.m_strings.size() > m_strings.size();
273  case StartsWith:
274  return std::all_of(m_strings.begin(), m_strings.end(),
275  [&](const QString &s) { return s.startsWith(other.getPrefix()); });
276  case EndsWith:
277  return std::all_of(m_strings.begin(), m_strings.end(),
278  [&](const QString &s) { return s.endsWith(other.getSuffix()); });
279  case Contains:
280  return std::all_of(m_strings.begin(), m_strings.end(),
281  [&](const QString &s) { return s.contains(other.getSubstring()); });
282  default:;
283  }
284  break;
285  case AllOf:
286  switch (other.m_strategy)
287  {
288  case ExactMatch: return m_strings.contains(other.getString());
289  case AnyOf: return !(m_strings & other.m_strings).isEmpty();
290  case AllOf: return m_strings.contains(other.m_strings) && m_strings.size() > other.m_strings.size();
291  case StartsWith:
292  return std::any_of(m_strings.begin(), m_strings.end(),
293  [&](const QString &s) { return s.startsWith(other.getPrefix()); });
294  case EndsWith:
295  return std::any_of(m_strings.begin(), m_strings.end(),
296  [&](const QString &s) { return s.endsWith(other.getSuffix()); });
297  case Contains:
298  return std::any_of(m_strings.begin(), m_strings.end(),
299  [&](const QString &s) { return s.contains(other.getSubstring()); });
300  default:;
301  }
302  break;
303  case StartsWith:
304  switch (other.m_strategy)
305  {
306  case StartsWith:
307  return getPrefix().startsWith(other.getPrefix()) && getPrefix().size() > other.getPrefix().size();
308  case Contains: return getPrefix().contains(other.getSubstring());
309  default:;
310  }
311  break;
312  case EndsWith:
313  switch (other.m_strategy)
314  {
315  case EndsWith:
316  return getSuffix().endsWith(other.getSuffix()) && getSuffix().size() > other.getSuffix().size();
317  case Contains: return getSuffix().contains(other.getSubstring());
318  default:;
319  }
320  break;
321  case Contains:
322  switch (other.m_strategy)
323  {
324  case Contains:
325  return getSubstring().contains(other.getSubstring()) &&
326  getSubstring().size() > other.getSubstring().size();
327  default:;
328  }
329  break;
330  default:;
331  }
332  return false;
333  }
334 
335  QString CLogPattern::convertToQString(bool i18n) const
336  {
337  Q_UNUSED(i18n)
338  QString strategy;
339  QString categories = m_strings.values().join("|"); // clazy:exclude=container-anti-pattern
340  switch (m_strategy)
341  {
342  case Everything: strategy = "all"; break;
343  case ExactMatch: strategy = "exact match:" + categories; break;
344  case AnyOf: strategy = "any of:" + categories; break;
345  case AllOf: strategy = "all of:" + categories; break;
346  case StartsWith: strategy = "starts with:" + categories; break;
347  case EndsWith: strategy = "ends with:" + categories; break;
348  case Contains: strategy = "contains:" + categories; break;
349  case Nothing: strategy = "none"; break;
350  default: strategy = "<invalid>"; break;
351  }
352  return "{" + CStatusMessage::severitiesToString(m_severities) + "," + strategy + "}";
353  }
354 
355  void CLogPattern::marshallToDbus(QDBusArgument &argument) const
356  {
357  // a bug in QtDBus prevents us from marshalling m_severities as a list
358  quint8 severities = 0;
359  for (auto s : m_severities) { severities |= (1 << static_cast<int>(s)); }
360 
361  argument << severities << m_strategy << m_strings.values();
362  }
363 
364  void CLogPattern::unmarshallFromDbus(const QDBusArgument &argument)
365  {
366  quint8 severities;
367  QStringList strings;
368  argument >> severities >> m_strategy >> strings;
369  m_strings = QSet<QString>(strings.begin(), strings.end());
370 
371  m_severities.clear();
372  for (int s : { 0, 1, 2, 3 })
373  {
374  if (severities & (1 << s)) { m_severities.insert(static_cast<CStatusMessage::StatusSeverity>(s)); }
375  }
376  }
377 
378  void CLogPattern::marshalToDataStream(QDataStream &stream) const
379  {
380  quint8 severities = 0;
381  for (auto s : m_severities) { severities |= (1 << static_cast<int>(s)); }
382 
383  stream << severities << m_strategy << m_strings.values();
384  }
385 
386  void CLogPattern::unmarshalFromDataStream(QDataStream &stream)
387  {
388  quint8 severities;
389  QStringList strings;
390  stream >> severities >> m_strategy >> strings;
391  m_strings = QSet<QString>(strings.begin(), strings.end());
392 
393  m_severities.clear();
394  for (int s : { 0, 1, 2, 3 })
395  {
396  if (severities & (1 << s)) { m_severities.insert(static_cast<CStatusMessage::StatusSeverity>(s)); }
397  }
398  }
399 } // namespace swift::misc
static const QString & verification()
Verification.
Definition: logcategories.h:31
static const QString & uncategorized()
Uncategorized.
Definition: logcategories.h:24
static const QString & webservice()
Webservice.
static const QString & download()
Generic downloads.
static const QString & matching()
Matching.
static const QString & dbus()
DBus related.
Definition: logcategories.h:59
static const QString & swiftDataTool()
swift data tool (aka mapping tool)
static const QString & services()
Core/base services such as caching etc.
Definition: logcategories.h:45
static const QString & contextSlot()
Context slots.
Definition: logcategories.h:87
static const QString & cmdLine()
Cmd.line parsing.
static const QString & interpolator()
Interpolator.
Definition: logcategories.h:73
static const QString & json()
JSON and JSON conversions.
static const QString & worker()
Background task.
static const QString & wizard()
Wizard.
static const QString & vatsimSpecific()
VATSIM specific.
static const QString & guiComponent()
GUI components.
Definition: logcategories.h:94
static const QString & mapping()
Mapping.
static const QString & context()
Contexts.
Definition: logcategories.h:66
static const QString & modelLoader()
Model loader.
static const QString & modelSetCache()
Model set cache.
static const QString & modelCache()
Model cache.
static const QString & plugin()
Plugin.
static const QString & modelGui()
Model UI.
static const QString & cache()
Cache.
static const QString & settings()
Settings.
static const QString & network()
Network specific, but not necessarily one specific flight network.
static const QString & validation()
Validation.
Definition: logcategories.h:38
static const QString & swiftCore()
swift core
static const QString & flightPlan()
Flight plan.
Definition: logcategories.h:80
static const QString & driver()
Driver.
static const QString & startup()
Startup of application.
static const QString & fsd()
FSD specific.
static const QString & swiftDbWebservice()
Webservice with swift DB.
static const QString & dataInconsistency()
Data inconsistency.
static const QString & swiftPilotClient()
swift GUI
A log category is an arbitrary string tag which can be attached to log messages.
Definition: logcategory.h:28
bool startsWith(const QString &prefix) const
Returns true if the category string starts with the given prefix.
Definition: logcategory.h:40
bool endsWith(const QString &suffix) const
Returns true if the category string ends with the given suffix.
Definition: logcategory.h:43
bool contains(const QString &substring) const
Returns true if the category string contains the given substring.
Definition: logcategory.h:46
A sequence of log categories.
QStringList toQStringList() const
Convert each of the categories to a QString and return the result as a QStringList.
Value class for matching log messages based on their categories.
Definition: logpattern.h:49
static QStringList humanReadableNamesFrom(const CStatusMessage &message)
Human readable categories of message.
Definition: logpattern.cpp:68
bool match(const CStatusMessage &message) const
Returns true if the given message matches this pattern.
Definition: logpattern.cpp:177
static QStringList humanOrTechnicalCategoriesFrom(const CStatusMessage &message)
Human or machine readable categories of message.
Definition: logpattern.cpp:78
static const CLogPattern & fromHumanReadableName(const QString &name)
Return a predefined CLogPattern corresponding to the given human-readable name.
Definition: logpattern.cpp:85
QString convertToQString(bool i18n=false) const
Cast as QString.
Definition: logpattern.cpp:335
CLogPattern withSeverities(const QSet< CStatusMessage::StatusSeverity > &severities) const
Returns a CLogPattern which will match the same messages as this one, but only with some given severi...
Definition: logpattern.cpp:137
static CLogPattern endsWith(const QString &suffix)
Returns a CLogPattern which will match any message with a category which ends with the given suffix.
Definition: logpattern.cpp:124
void unmarshallFromDbus(const QDBusArgument &argument)
Unmarshall without begin/endStructure, for when composed within another object.
Definition: logpattern.cpp:364
static CLogPattern allOf(const CLogCategoryList &categories)
Returns a CLogPattern which will match any message with all of the given categories.
Definition: logpattern.cpp:114
static const QStringList & allHumanReadableNames()
Get a list of human-readable names of predefined log patterns.
Definition: logpattern.cpp:62
void marshallToDbus(QDBusArgument &argument) const
Marshall without begin/endStructure, for when composed within another object.
Definition: logpattern.cpp:355
static CLogPattern exactMatch(const CLogCategory &category)
Returns a CLogPattern which will match any message with the given category.
Definition: logpattern.cpp:101
CLogPattern withSeverity(CStatusMessage::StatusSeverity severity) const
Returns a CLogPattern which will match the same messages as this one, but only with a given severity.
Definition: logpattern.cpp:130
static CLogPattern startsWith(const QString &prefix)
Returns a CLogPattern which will match any message with a category which starts with the given prefix...
Definition: logpattern.cpp:122
static CLogPattern contains(const QString &substring)
Returns a CLogPattern which will match any message with a category which contains the given substring...
Definition: logpattern.cpp:126
static CLogPattern empty()
Returns a CLogPattern which will match any message without a category.
Definition: logpattern.cpp:128
void marshalToDataStream(QDataStream &stream) const
Marshal a value to a QDataStream.
Definition: logpattern.cpp:378
bool isProperSubsetOf(const CLogPattern &other) const
Returns true if this pattern is a proper subset of the other pattern.
Definition: logpattern.cpp:211
void unmarshalFromDataStream(QDataStream &stream)
Unmarshal a value from a QDataStream.
Definition: logpattern.cpp:386
static CLogPattern anyOf(const CLogCategoryList &categories)
Returns a CLogPattern which will match any message with any of the given categories.
Definition: logpattern.cpp:106
CLogPattern()
Default constructed CLogPattern will match any message.
Definition: logpattern.cpp:99
CLogPattern withSeverityAtOrAbove(CStatusMessage::StatusSeverity minimumSeverity) const
Returns a CLogPattern which will match the same messages, but only with a severity at or above the gi...
Definition: logpattern.cpp:144
bool containsBy(Predicate p) const
Return true if there is an element for which a given predicate returns true.
Definition: range.h:101
bool contains(const T &object) const
Return true if there is an element equal to given object. Uses the most efficient implementation avai...
Definition: range.h:109
size_type size() const
Returns number of elements in the sequence.
Definition: sequence.h:273
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Streamable status message, e.g.
constexpr static auto SeverityDebug
Status severities.
constexpr static auto SeverityError
Status severities.
StatusSeverity getSeverity() const
Message severity.
const CLogCategoryList & getCategories() const
Message categories.
constexpr static auto SeverityInfo
Status severities.
constexpr static auto SeverityWarning
Status severities.
static QString severitiesToString(const QSet< StatusSeverity > &severities)
Severity set as string.
QString toQString(bool i18n=false) const
Cast as QString.
Definition: mixinstring.h:76
Free functions in swift::misc.
StatusSeverity
Status severities.
Definition: statusmessage.h:35