File indexing completed on 2024-05-19 05:49:16
0001 /* 0002 SPDX-FileCopyrightText: 2007 Nicolas Ternisien <nicolas.ternisien@gmail.com> 0003 SPDX-FileCopyrightText: 2015 Vyacheslav Matyushin 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "journaldLocalAnalyzer.h" 0009 #include "journaldConfiguration.h" 0010 #include "ksystemlogConfig.h" 0011 #include "ksystemlog_debug.h" 0012 #include "logViewModel.h" 0013 0014 #include <KLocalizedString> 0015 0016 #include <QtConcurrent> 0017 0018 JournaldLocalAnalyzer::JournaldLocalAnalyzer(LogMode *mode, QString filter) 0019 : JournaldAnalyzer(mode) 0020 { 0021 // Initialize journal access flags and open the journal. 0022 mJournalFlags = 0; 0023 auto *configuration = mode->logModeConfiguration<JournaldConfiguration *>(); 0024 switch (configuration->entriesType()) { 0025 case JournaldConfiguration::EntriesAll: 0026 break; 0027 case JournaldConfiguration::EntriesCurrentUser: 0028 mJournalFlags |= SD_JOURNAL_CURRENT_USER; 0029 break; 0030 case JournaldConfiguration::EntriesSystem: 0031 mJournalFlags |= SD_JOURNAL_SYSTEM; 0032 break; 0033 } 0034 const int ret = sd_journal_open(&mJournal, mJournalFlags); 0035 if (ret < 0) { 0036 qCWarning(KSYSTEMLOG) << "Journald analyzer failed to open system journal"; 0037 return; 0038 } 0039 0040 const qintptr fd = sd_journal_get_fd(mJournal); 0041 mJournalNotifier = new QSocketNotifier(fd, QSocketNotifier::Read); 0042 mJournalNotifier->setEnabled(false); 0043 connect(mJournalNotifier, &QSocketNotifier::activated, this, &JournaldLocalAnalyzer::journalDescriptorUpdated); 0044 0045 if (configuration->displayCurrentBootOnly()) { 0046 QFile file(QLatin1String("/proc/sys/kernel/random/boot_id")); 0047 if (file.open(QIODevice::ReadOnly | QFile::Text)) { 0048 QTextStream stream(&file); 0049 mCurrentBootID = stream.readAll().trimmed(); 0050 mCurrentBootID.remove(QChar::fromLatin1('-')); 0051 mFilters << QStringLiteral("_BOOT_ID=%1").arg(mCurrentBootID); 0052 } else { 0053 qCWarning(KSYSTEMLOG) << "Journald analyzer failed to open /proc/sys/kernel/random/boot_id"; 0054 } 0055 } 0056 0057 if (!filter.isEmpty()) { 0058 mFilters << filter; 0059 mFilterName = filter.section(QChar::fromLatin1('='), 1); 0060 } 0061 } 0062 0063 JournaldLocalAnalyzer::~JournaldLocalAnalyzer() 0064 { 0065 watchLogFiles(false); 0066 sd_journal_close(mJournal); 0067 delete mJournalNotifier; 0068 } 0069 0070 void JournaldLocalAnalyzer::watchLogFiles(bool enabled) 0071 { 0072 if (!mJournalNotifier) { 0073 return; 0074 } 0075 mJournalNotifier->setEnabled(enabled); 0076 0077 mWorkerMutex.lock(); 0078 mForgetWatchers = enabled; 0079 mWorkerMutex.unlock(); 0080 0081 if (enabled) { 0082 auto watcher = new JournalWatcher(); 0083 mWorkerMutex.lock(); 0084 mJournalWatchers.append(watcher); 0085 mWorkerMutex.unlock(); 0086 connect(watcher, &JournalWatcher::finished, this, &JournaldLocalAnalyzer::readJournalInitialFinished); 0087 watcher->setFuture(QtConcurrent::run(&JournaldLocalAnalyzer::readJournal, this, mFilters)); 0088 } else { 0089 for (JournalWatcher *watcher : mJournalWatchers) { 0090 watcher->waitForFinished(); 0091 } 0092 qDeleteAll(mJournalWatchers); 0093 mJournalWatchers.clear(); 0094 0095 if (mCursor) { 0096 free(mCursor); 0097 mCursor = nullptr; 0098 } 0099 } 0100 } 0101 0102 QStringList JournaldLocalAnalyzer::units() const 0103 { 0104 return JournaldLocalAnalyzer::unitsStatic(); 0105 } 0106 0107 QStringList JournaldLocalAnalyzer::unitsStatic() 0108 { 0109 return getUniqueFieldValues(QStringLiteral("_SYSTEMD_UNIT")); 0110 } 0111 0112 QStringList JournaldLocalAnalyzer::syslogIdentifiers() const 0113 { 0114 return JournaldLocalAnalyzer::syslogIdentifiersStatic(); 0115 } 0116 0117 QStringList JournaldLocalAnalyzer::syslogIdentifiersStatic() 0118 { 0119 return getUniqueFieldValues(QStringLiteral("SYSLOG_IDENTIFIER")); 0120 } 0121 0122 void JournaldLocalAnalyzer::readJournalInitialFinished() 0123 { 0124 readJournalFinished(FullRead); 0125 } 0126 0127 void JournaldLocalAnalyzer::readJournalUpdateFinished() 0128 { 0129 readJournalFinished(UpdatingRead); 0130 } 0131 0132 void JournaldLocalAnalyzer::readJournalFinished(ReadingMode readingMode) 0133 { 0134 auto watcher = static_cast<JournalWatcher *>(sender()); 0135 if (!watcher) { 0136 return; 0137 } 0138 0139 QList<JournalEntry> entries = watcher->result(); 0140 0141 if (mParsingPaused) { 0142 qCDebug(KSYSTEMLOG) << "Parsing is paused, discarding journald entries."; 0143 } else if (entries.empty()) { 0144 qCDebug(KSYSTEMLOG) << "Received no entries."; 0145 } else { 0146 mInsertionLocking.lock(); 0147 mLogViewModel->startingMultipleInsertions(); 0148 0149 if (FullRead == readingMode) { 0150 Q_EMIT statusBarChanged(i18n("Reading journald entries...")); 0151 // Start displaying the loading bar. 0152 Q_EMIT readFileStarted(*mLogMode, LogFile(), 0, 1); 0153 } 0154 0155 // Add journald entries to the model. 0156 int const entriesInserted = updateModel(entries, readingMode); 0157 0158 mLogViewModel->endingMultipleInsertions(readingMode, entriesInserted); 0159 0160 if (FullRead == readingMode) { 0161 Q_EMIT statusBarChanged(i18n("Journald entries loaded successfully.")); 0162 0163 // Stop displaying the loading bar. 0164 Q_EMIT readEnded(); 0165 } 0166 0167 // Inform LogManager that new lines have been added. 0168 Q_EMIT logUpdated(entriesInserted); 0169 0170 mInsertionLocking.unlock(); 0171 } 0172 0173 mWorkerMutex.lock(); 0174 if (mForgetWatchers) { 0175 mJournalWatchers.removeAll(watcher); 0176 watcher->deleteLater(); 0177 } 0178 mWorkerMutex.unlock(); 0179 } 0180 0181 void JournaldLocalAnalyzer::journalDescriptorUpdated(int fd) 0182 { 0183 qCDebug(KSYSTEMLOG) << "Journal was updated."; 0184 QFile file; 0185 file.open(fd, QIODevice::ReadOnly); 0186 file.readAll(); 0187 file.close(); 0188 0189 if (mParsingPaused) { 0190 qCDebug(KSYSTEMLOG) << "Parsing is paused, will not fetch new journald entries."; 0191 return; 0192 } 0193 0194 auto watcher = new JournalWatcher(); 0195 mWorkerMutex.lock(); 0196 mJournalWatchers.append(watcher); 0197 mWorkerMutex.unlock(); 0198 connect(watcher, &JournalWatcher::finished, this, &JournaldLocalAnalyzer::readJournalUpdateFinished); 0199 watcher->setFuture(QtConcurrent::run(&JournaldLocalAnalyzer::readJournal, this, mFilters)); 0200 } 0201 0202 QList<JournaldLocalAnalyzer::JournalEntry> JournaldLocalAnalyzer::readJournal(const QStringList &filters) 0203 { 0204 QMutexLocker const mutexLocker(&mWorkerMutex); 0205 QList<JournalEntry> entryList; 0206 sd_journal *journal; 0207 0208 if (!mFilterName.isEmpty()) { 0209 Q_EMIT statusChanged(mFilterName); 0210 } 0211 0212 int res = sd_journal_open(&journal, mJournalFlags); 0213 if (res < 0) { 0214 qCWarning(KSYSTEMLOG) << "Failed to access the journal."; 0215 return QList<JournalEntry>(); 0216 } 0217 0218 if (prepareJournalReading(journal, filters)) { 0219 // Iterate over filtered entries. 0220 while (1) { 0221 JournalEntry entry; 0222 res = sd_journal_next(journal); 0223 if (res < 0) { 0224 qCWarning(KSYSTEMLOG) << "Failed to access next journal entry."; 0225 break; 0226 } 0227 if (res == 0) { 0228 // Reached last journal entry. 0229 break; 0230 } 0231 entry = readJournalEntry(journal); 0232 entryList.append(entry); 0233 } 0234 0235 free(mCursor); 0236 sd_journal_get_cursor(journal, &mCursor); 0237 } 0238 0239 sd_journal_close(journal); 0240 if (!entryList.empty()) { 0241 qCDebug(KSYSTEMLOG) << "Read" << entryList.size() << "journal entries."; 0242 } 0243 return entryList; 0244 } 0245 0246 bool JournaldLocalAnalyzer::prepareJournalReading(sd_journal *journal, const QStringList &filters) 0247 { 0248 int res; 0249 0250 // Set entries filter. 0251 for (const QString &filter : filters) { 0252 res = sd_journal_add_match(journal, filter.toUtf8().constData(), 0); 0253 if (res < 0) { 0254 qCWarning(KSYSTEMLOG) << "Failed to set journal filter."; 0255 return false; 0256 } 0257 } 0258 0259 // Go to the latest journal entry. 0260 res = sd_journal_seek_tail(journal); 0261 if (res < 0) { 0262 qCWarning(KSYSTEMLOG) << "Failed to seek journal tail."; 0263 return false; 0264 } 0265 0266 // Read number of entries allowed by KSystemLog configuration. 0267 int const maxEntriesNum = KSystemLogConfig::maxLines(); 0268 0269 // Seek to cursor. 0270 if (mCursor) { 0271 int entriesNum = 0; 0272 // Continue searching for the oldest entry until 0273 // either cursor is found or maximum number of entries is traversed. 0274 while (entriesNum < maxEntriesNum) { 0275 entriesNum++; 0276 0277 res = sd_journal_previous(journal); 0278 if (res < 0) { 0279 qCWarning(KSYSTEMLOG) << "Failed to seek previous journal entry."; 0280 return false; 0281 } 0282 0283 res = sd_journal_test_cursor(journal, mCursor); 0284 if (res > 0) { 0285 if (entriesNum == 1) { 0286 // No new entries are found. 0287 return false; 0288 } 0289 // Latest journal entry before journal update is found. 0290 break; 0291 } 0292 } 0293 } else { 0294 // Jump over maxEntriesNum entries backwards from the end of the journal. 0295 res = sd_journal_previous_skip(journal, maxEntriesNum + 1); 0296 if (res < 0) { 0297 // Seek failed. Read entries from the beginning. 0298 res = sd_journal_seek_head(journal); 0299 if (res < 0) { 0300 qCWarning(KSYSTEMLOG) << "Failed to seek journal head."; 0301 return false; 0302 } 0303 } 0304 } 0305 return true; 0306 } 0307 0308 JournaldLocalAnalyzer::JournalEntry JournaldLocalAnalyzer::readJournalEntry(sd_journal *journal) const 0309 { 0310 // Reads a single journal entry into JournalEntry structure. 0311 JournalEntry entry; 0312 const void *data; 0313 size_t length; 0314 uint64_t time; 0315 int res; 0316 0317 res = sd_journal_get_realtime_usec(journal, &time); 0318 if (res == 0) { 0319 entry.date.setMSecsSinceEpoch(time / 1000); 0320 } 0321 0322 res = sd_journal_get_data(journal, "SYSLOG_IDENTIFIER", &data, &length); 0323 if (res == 0) { 0324 entry.unit = QString::fromUtf8((const char *)data, length).section(QChar::fromLatin1('='), 1); 0325 } else { 0326 res = sd_journal_get_data(journal, "_SYSTEMD_UNIT", &data, &length); 0327 if (res == 0) { 0328 entry.unit = QString::fromUtf8((const char *)data, length).section(QChar::fromLatin1('='), 1); 0329 } 0330 } 0331 0332 res = sd_journal_get_data(journal, "MESSAGE", &data, &length); 0333 if (res == 0) { 0334 entry.message = QString::fromUtf8((const char *)data, length).section(QChar::fromLatin1('='), 1); 0335 entry.message.remove(QRegularExpression(QLatin1String(ConsoleColorEscapeSequence))); 0336 } 0337 0338 res = sd_journal_get_data(journal, "PRIORITY", &data, &length); 0339 if (res == 0) { 0340 entry.priority = QString::fromUtf8((const char *)data, length).section(QChar::fromLatin1('='), 1).toInt(); 0341 } 0342 0343 res = sd_journal_get_data(journal, "_BOOT_ID", &data, &length); 0344 if (res == 0) { 0345 entry.bootID = QString::fromUtf8((const char *)data, length).section(QChar::fromLatin1('='), 1); 0346 } 0347 0348 return entry; 0349 } 0350 0351 QStringList JournaldLocalAnalyzer::getUniqueFieldValues(const QString &id, int flags) 0352 { 0353 QStringList units; 0354 sd_journal *journal; 0355 int res = sd_journal_open(&journal, flags); 0356 if (res == 0) { 0357 const void *data; 0358 size_t length; 0359 0360 // Get all unique field values. The order is not defined. 0361 res = sd_journal_query_unique(journal, id.toUtf8().constData()); 0362 if (res == 0) { 0363 SD_JOURNAL_FOREACH_UNIQUE(journal, data, length) 0364 { 0365 const QString unit = QString::fromUtf8((const char *)data, length).section(QChar::fromLatin1('='), 1); 0366 if (unit.startsWith(QLatin1String("systemd-coredump@"))) { 0367 continue; // these never contain any log information, and can easily fill up menu 0368 } 0369 units.append(unit); 0370 } 0371 } 0372 0373 units.removeDuplicates(); 0374 units.sort(); 0375 sd_journal_close(journal); 0376 } else { 0377 qCWarning(KSYSTEMLOG) << "Failed to open the journal and extract unique values for field" << id; 0378 } 0379 return units; 0380 } 0381 0382 #include "moc_journaldLocalAnalyzer.cpp"