blob: 3ae673a53acf37ccc4a48417f2c9b89a0117123d [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/system_logs/debug_daemon_log_source.h"
#include <stddef.h>
#include <utility>
#include "ash/constants/ash_features.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/os_feedback/chrome_os_feedback_delegate.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/common/chrome_switches.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h"
#include "components/feedback/feedback_util.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"
namespace system_logs {
namespace {
constexpr char kEmpty[] = "<empty>";
constexpr char kNotAvailable[] = "<not available>";
constexpr char kRoutesKeyName[] = "routes";
constexpr char kRoutesv6KeyName[] = "routes6";
constexpr char kLogTruncated[] = "<earlier logs truncated>\n";
// List of user log files that Chrome reads directly as these logs are generated
// by Chrome itself.
constexpr struct UserLogs {
// A string key used as a title for this log in feedback reports.
const char* log_key;
// The log file's path relative to the user's profile directory.
const char* log_file_relative_path;
// If true, |log_file_relative_path| is a pattern for which files to match.
// This works like shell globbing. If there are multiple matches files, it
// chooses the newest file, with the latest modified time.
bool pattern = false;
} kUserLogs[] = {
{"chrome_user_log", "log/chrome"},
{"chrome_user_log.PREVIOUS", "log/chrome_????????-??????", true},
{"libassistant_user_log", "google-assistant-library/log/libassistant.log"},
{"login-times", "login-times"},
{"logout-times", "logout-times"},
};
// List of debugd entries to exclude from the results.
constexpr std::array<const char*, 2> kExcludeList = {
// Shill device and service properties are retrieved by ShillLogSource.
"network-devices",
"network-services",
};
// Buffer size for user logs in bytes. Given that maximum feedback report size
// is ~7M and that majority of log files are under 1M, we set a per-file limit
// of 1MiB.
const int64_t kMaxLogSize = 1024 * 1024;
std::vector<debugd::FeedbackLogType> GetLogTypesForUser(
const user_manager::User* user) {
// The default list of log types that we request from debugd.
std::vector<debugd::FeedbackLogType> included_log_types = {
debugd::FeedbackLogType::ARC_BUG_REPORT,
debugd::FeedbackLogType::CONNECTIVITY_REPORT,
debugd::FeedbackLogType::VERBOSE_COMMAND_LOGS,
debugd::FeedbackLogType::COMMAND_LOGS,
debugd::FeedbackLogType::FEEDBACK_LOGS,
debugd::FeedbackLogType::BLUETOOTH_BQR,
debugd::FeedbackLogType::LSB_RELEASE_INFO,
debugd::FeedbackLogType::PERF_DATA,
debugd::FeedbackLogType::OS_RELEASE_INFO,
debugd::FeedbackLogType::VAR_LOG_FILES};
if (user && ash::ChromeOsFeedbackDelegate::IsWifiDebugLogsAllowed(
user->GetProfilePrefs())) {
// Include WIFI_FIRMWARE_DUMPS since it is allowed for the user.
included_log_types.push_back(debugd::FeedbackLogType::WIFI_FIRMWARE_DUMPS);
}
return included_log_types;
}
} // namespace
std::string ReadUserLogFile(const base::FilePath& log_file_path) {
std::optional<std::string> maybe_value =
feedback_util::ReadEndOfFile(log_file_path, kMaxLogSize);
if (maybe_value.has_value() && maybe_value.value().size() == kMaxLogSize) {
maybe_value.value().replace(0, strlen(kLogTruncated), kLogTruncated);
LOG(WARNING) << "Large log file was likely truncated: " << log_file_path;
}
return (maybe_value.has_value() && !maybe_value.value().empty())
? maybe_value.value()
: std::string(kNotAvailable);
}
std::string ReadUserLogFilePattern(
const base::FilePath& log_file_path_pattern) {
base::FilePath log_file_dir = log_file_path_pattern.DirName();
base::FileEnumerator file_enumerator(
log_file_dir, /*recursive=*/false, base::FileEnumerator::FILES,
log_file_path_pattern.BaseName().value());
base::Time newest_file_mtime;
base::FilePath newest_file_path;
for (base::FilePath path = file_enumerator.Next(); !path.empty();
path = file_enumerator.Next()) {
const base::FileEnumerator::FileInfo info = file_enumerator.GetInfo();
if (newest_file_mtime.is_null() ||
info.GetLastModifiedTime() >= newest_file_mtime) {
newest_file_mtime = info.GetLastModifiedTime();
newest_file_path = path;
}
}
return newest_file_mtime.is_null() ? std::string(kNotAvailable)
: ReadUserLogFile(newest_file_path);
}
// Reads the contents of the user log files listed in |kUserLogs| and adds them
// to the |response| parameter.
void ReadUserLogFiles(const std::vector<base::FilePath>& profile_dirs,
SystemLogsResponse* response) {
for (size_t i = 0; i < profile_dirs.size(); ++i) {
std::string profile_prefix = "Profile[" + base::NumberToString(i) + "] ";
const base::FilePath profile_dir = profile_dirs[i];
for (const auto& log : kUserLogs) {
const base::FilePath log_file_path_or_pattern =
profile_dir.AppendASCII(log.log_file_relative_path);
const std::string content =
log.pattern ? ReadUserLogFilePattern(log_file_path_or_pattern)
: ReadUserLogFile(log_file_path_or_pattern);
response->emplace(profile_prefix + log.log_key, content);
}
}
}
DebugDaemonLogSource::DebugDaemonLogSource(bool scrub)
: SystemLogsSource("DebugDemon"),
response_(new SystemLogsResponse()),
num_pending_requests_(0),
scrub_(scrub) {}
DebugDaemonLogSource::~DebugDaemonLogSource() = default;
void DebugDaemonLogSource::Fetch(SysLogsSourceCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!callback.is_null());
DCHECK(callback_.is_null());
callback_ = std::move(callback);
ash::DebugDaemonClient* client = ash::DebugDaemonClient::Get();
client->GetRoutes(true, // Numeric
false, // No IPv6
false, // All tables option disabled
base::BindOnce(&DebugDaemonLogSource::OnGetRoutes,
weak_ptr_factory_.GetWeakPtr(), false));
++num_pending_requests_;
client->GetRoutes(true, // Numeric
true, // with IPv6
false, // All tables option disabled
base::BindOnce(&DebugDaemonLogSource::OnGetRoutes,
weak_ptr_factory_.GetWeakPtr(), true));
++num_pending_requests_;
const auto start_time = base::TimeTicks::Now();
if (scrub_) {
const user_manager::User* user =
user_manager::UserManager::Get()->GetActiveUser();
const auto account_identifier =
cryptohome::CreateAccountIdentifierFromAccountId(
user ? user->GetAccountId() : EmptyAccountId());
client->GetFeedbackLogs(
account_identifier, GetLogTypesForUser(user),
base::BindOnce(&DebugDaemonLogSource::OnGetLogs,
weak_ptr_factory_.GetWeakPtr(), start_time));
} else {
client->GetAllLogs(base::BindOnce(&DebugDaemonLogSource::OnGetLogs,
weak_ptr_factory_.GetWeakPtr(),
start_time));
}
++num_pending_requests_;
}
void DebugDaemonLogSource::OnGetRoutes(
bool is_ipv6,
std::optional<std::vector<std::string>> routes) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::string key = is_ipv6 ? kRoutesv6KeyName : kRoutesKeyName;
(*response_)[key] = routes.has_value()
? base::JoinString(routes.value(), "\n")
: kNotAvailable;
RequestCompleted();
}
void DebugDaemonLogSource::OnGetOneLog(std::string key,
std::optional<std::string> status) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
(*response_)[std::move(key)] = std::move(status).value_or(kNotAvailable);
RequestCompleted();
}
void DebugDaemonLogSource::OnGetLogs(const base::TimeTicks get_start_time,
bool succeeded,
const KeyValueMap& logs) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// We are interested in the performance of gathering the logs for feedback
// reports only where the logs will always be scrubbed. GetBigFeedbackLogs is
// the dbus method used.
if (scrub_) {
base::UmaHistogramBoolean("Feedback.ChromeOSApp.GetBigFeedbackLogs.Success",
succeeded);
base::UmaHistogramMediumTimes(
"Feedback.ChromeOSApp.Duration.GetBigFeedbackLogs",
base::TimeTicks::Now() - get_start_time);
}
int empty_log_count = 0;
int not_available_log_count = 0;
int other_log_count = 0;
// We ignore 'succeeded' for this callback - we want to display as much of the
// debug info as we can even if we failed partway through parsing, and if we
// couldn't fetch any of it, none of the fields will even appear.
for (const auto& log : logs) {
if (base::Contains(kExcludeList, log.first)) {
continue;
}
response_->insert(log);
if (log.second == kEmpty) {
++empty_log_count;
} else if (log.second == kNotAvailable) {
++not_available_log_count;
} else {
++other_log_count;
}
}
if (scrub_) {
// Record stats for the logs received from debugd.
// As of today, the total logs are about 211.
base::UmaHistogramCounts1000(
"Feedback.ChromeOSApp.GetBigFeedbackLogs.EmptyCount", empty_log_count);
base::UmaHistogramCounts1000(
"Feedback.ChromeOSApp.GetBigFeedbackLogs.NotAvailableCount",
not_available_log_count);
base::UmaHistogramCounts1000(
"Feedback.ChromeOSApp.GetBigFeedbackLogs.OtherCount", other_log_count);
}
RequestCompleted();
}
void DebugDaemonLogSource::GetLoggedInUsersLogFiles() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// List all logged-in users' profile directories.
std::vector<base::FilePath> profile_dirs;
const user_manager::UserList& users =
user_manager::UserManager::Get()->GetLoggedInUsers();
for (const user_manager::User* user : users) {
if (user->username_hash().empty()) {
continue;
}
profile_dirs.emplace_back(
ash::ProfileHelper::GetProfilePathByUserIdHash(user->username_hash()));
}
auto response = std::make_unique<SystemLogsResponse>();
SystemLogsResponse* response_ptr = response.get();
base::ThreadPool::PostTaskAndReply(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(&ReadUserLogFiles, profile_dirs, response_ptr),
base::BindOnce(&DebugDaemonLogSource::MergeUserLogFilesResponse,
weak_ptr_factory_.GetWeakPtr(), std::move(response)));
}
void DebugDaemonLogSource::MergeUserLogFilesResponse(
std::unique_ptr<SystemLogsResponse> response) {
for (auto& pair : *response) {
response_->emplace(pair.first, std::move(pair.second));
}
auto response_to_return = std::make_unique<SystemLogsResponse>();
std::swap(response_to_return, response_);
DCHECK(!callback_.is_null());
std::move(callback_).Run(std::move(response_to_return));
}
void DebugDaemonLogSource::RequestCompleted() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!callback_.is_null());
--num_pending_requests_;
if (num_pending_requests_ > 0) {
return;
}
// When all other logs are collected, fetch the user logs, because any errors
// fetching the other logs is reported in the user logs.
GetLoggedInUsersLogFiles();
}
} // namespace system_logs