blob: 94efb23fa850b013f708631c15fa3452035e19d3 [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "croslog/viewer_plaintext.h"
#include <memory>
#include <unistd.h>
#include <utility>
#include "base/files/file_util.h"
#include "base/json/string_escape.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "croslog/cursor_util.h"
#include "croslog/log_parser_audit.h"
#include "croslog/log_parser_syslog.h"
#include "croslog/severity.h"
namespace croslog {
namespace {
const char* kLogSources[] = {
// Log files from rsyslog:
// clang-format off
"/var/log/arc.log",
"/var/log/boot.log",
"/var/log/hammerd.log",
"/var/log/messages",
"/var/log/net.log",
"/var/log/secure",
"/var/log/upstart.log",
// clang-format on
};
const char kAuditLogSources[] = "/var/log/audit/audit.log";
int64_t ToMicrosecondsSinceUnixEpoch(base::Time time) {
return (time - base::Time::UnixEpoch()).InMicroseconds();
}
} // anonymous namespace
ViewerPlaintext::ViewerPlaintext(const croslog::Config& config)
: config_(config) {
Initialize();
}
// For test
ViewerPlaintext::ViewerPlaintext(const croslog::Config& config,
BootRecords&& boot_logs)
: config_(config), boot_records_{std::move(boot_logs)} {
Initialize();
}
void ViewerPlaintext::Initialize() {
if (!config_.grep.empty()) {
config_grep_.emplace(config_.grep);
if (!config_grep_->ok())
config_grep_.reset();
}
if (!config_.after_cursor.empty()) {
if (ParseCursor(config_.after_cursor, &config_cursor_time_))
config_cursor_mode_ = CursorMode::NEWER;
else
LOG(WARNING) << "Invalid cursor format in 'after-cursor' option.";
} else if (!config_.cursor.empty()) {
if (ParseCursor(config_.cursor, &config_cursor_time_))
config_cursor_mode_ = CursorMode::SAME_AND_NEWER;
else
LOG(WARNING) << "Invalid cursor format in 'cursor' option.";
}
config_show_cursor_ = config_.show_cursor && !config_.follow;
config_boot_range_.reset();
if (config_.boot.has_value()) {
auto range = boot_records_.GetBootRange(*config_.boot);
if (range.has_value())
config_boot_range_.emplace(*range);
}
}
bool ViewerPlaintext::Run() {
bool install_change_watcher = config_.follow;
for (size_t i = 0; i < base::size(kLogSources); i++) {
base::FilePath path(kLogSources[i]);
if (!base::PathExists(path))
continue;
multiplexer_.AddSource(path, std::make_unique<LogParserSyslog>(),
install_change_watcher);
}
if (base::PathExists(base::FilePath(kAuditLogSources))) {
multiplexer_.AddSource(base::FilePath(kAuditLogSources),
std::make_unique<LogParserAudit>(),
install_change_watcher);
}
multiplexer_.AddObserver(this);
if (config_.lines >= 0) {
multiplexer_.SetLinesFromLast(config_.lines);
} else if (config_.follow) {
multiplexer_.SetLinesFromLast(10);
}
ReadRemainingLogs();
if (config_.follow) {
// Wait for file changes.
run_loop_.Run();
multiplexer_.RemoveObserver(this);
}
return true;
}
void ViewerPlaintext::OnLogFileChanged() {
ReadRemainingLogs();
}
bool ViewerPlaintext::ShouldFilterOutEntry(const LogEntry& e) {
if (config_cursor_mode_ != CursorMode::UNSPECIFIED) {
if ((config_cursor_mode_ == CursorMode::NEWER &&
config_cursor_time_ >= e.time()) ||
(config_cursor_mode_ == CursorMode::SAME_AND_NEWER &&
config_cursor_time_ > e.time())) {
// TODO(yoshiki): Consider the case that multiple logs have the same
// time.
return true;
}
}
const std::string& tag = e.tag();
if (!config_.identifier.empty() && config_.identifier != tag)
return true;
const Severity severity = e.severity();
if (config_.severity != Severity::UNSPECIFIED && config_.severity < severity)
return true;
const std::string& message = e.message();
if (config_grep_.has_value() && !RE2::PartialMatch(message, *config_grep_))
return true;
if (config_.boot.has_value()) {
if (!config_boot_range_.has_value() ||
!config_boot_range_->Contains(e.time())) {
return true;
}
}
return false;
}
void ViewerPlaintext::ReadRemainingLogs() {
base::Time last_shown_log_time;
while (true) {
const MaybeLogEntry& e = multiplexer_.Forward();
if (!e.has_value())
break;
// Shoe the last cursor regardless of visibility.
if (config_show_cursor_)
last_shown_log_time = e->time();
if (ShouldFilterOutEntry(*e))
continue;
WriteLog(*e);
}
if (config_show_cursor_) {
if (last_shown_log_time.is_null()) {
multiplexer_.SetLinesFromLast(1);
const MaybeLogEntry& e = multiplexer_.Forward();
if (e.has_value())
last_shown_log_time = e->time();
}
if (!last_shown_log_time.is_null()) {
WriteOutput("-- cursor: ");
WriteOutput(GenerateCursor(last_shown_log_time));
WriteOutput("\n", 1);
}
}
}
std::vector<std::pair<std::string, std::string>>
ViewerPlaintext::GenerateKeyValues(const LogEntry& e) {
std::vector<std::pair<std::string, std::string>> kvs;
kvs.push_back(std::make_pair(
"PRIORITY", base::NumberToString(static_cast<int>(e.severity()))));
kvs.push_back(std::make_pair("SYSLOG_IDENTIFIER", e.tag()));
const std::string& boot_id = GetBootIdAt(e.time());
if (!boot_id.empty())
kvs.push_back(std::make_pair("_BOOT_ID", boot_id));
std::string timestamp =
base::NumberToString(ToMicrosecondsSinceUnixEpoch(e.time()));
kvs.push_back(std::make_pair("__REALTIME_TIMESTAMP", timestamp));
kvs.push_back(std::make_pair("_SOURCE_REALTIME_TIMESTAMP", timestamp));
if (e.pid() != -1) {
kvs.push_back(std::make_pair("SYSLOG_PID", base::NumberToString(e.pid())));
kvs.push_back(std::make_pair("_PID", base::NumberToString(e.pid())));
}
kvs.push_back(std::make_pair("MESSAGE", e.message()));
return kvs;
}
void ViewerPlaintext::WriteLog(const LogEntry& entry) {
if (config_.output == OutputMode::EXPORT)
return WriteLogInExportFormat(entry);
if (config_.output == OutputMode::JSON)
return WriteLogInJsonFormat(entry);
const std::string& s = entry.entire_line();
WriteOutput(s);
WriteOutput("\n", 1);
}
void ViewerPlaintext::WriteLogInExportFormat(const LogEntry& entry) {
const auto&& kvs = GenerateKeyValues(entry);
for (const auto& kv : kvs) {
WriteOutput(kv.first);
WriteOutput("=", 1);
WriteOutput(kv.second);
WriteOutput("\n", 1);
}
WriteOutput("\n", 1);
}
std::string ViewerPlaintext::GetBootIdAt(base::Time time) {
const auto& boot_ranges = boot_records_.boot_ranges();
DCHECK_GE(cache_boot_range_index_, -1);
DCHECK_LT(cache_boot_range_index_, static_cast<int>(boot_ranges.size()));
// First, tries to reuse the index used at the last time. In most case, the
// logs are read sequentially and the boot id is likely to be same as the
// previous.
if (cache_boot_range_index_ != -1 &&
boot_ranges[cache_boot_range_index_].Contains(time)) {
return boot_ranges[cache_boot_range_index_].boot_id();
}
// Otherwise, searches the boot id sequentially from the boot log.
for (int i = 0; i < boot_ranges.size(); i++) {
const auto& boot_range = boot_ranges[i];
if (boot_range.Contains(time)) {
cache_boot_range_index_ = i;
return boot_range.boot_id();
}
}
return base::EmptyString();
}
void ViewerPlaintext::WriteLogInJsonFormat(const LogEntry& entry) {
const auto&& kvs = GenerateKeyValues(entry);
bool first = true;
WriteOutput("{", 1);
for (const auto& kv : kvs) {
std::string escaped_value;
bool ret_value = base::EscapeJSONString(kv.second, true, &escaped_value);
if (!ret_value)
escaped_value = "<<INVALID>>";
if (!first)
WriteOutput(", \"", 3);
else
WriteOutput("\"", 1);
// All keys are hard-corded and unnecessary to escape.
WriteOutput(kv.first);
WriteOutput("\": ", 3);
WriteOutput(escaped_value);
first = false;
}
WriteOutput("}\n", 2);
}
void ViewerPlaintext::WriteOutput(const std::string& str) {
WriteOutput(str.data(), str.size());
}
void ViewerPlaintext::WriteOutput(const char* str, size_t size) {
bool write_stdout_result =
base::WriteFileDescriptor(STDOUT_FILENO, str, size);
CHECK(write_stdout_result);
}
} // namespace croslog