Commit 15c048bd authored by Steven Bennetts's avatar Steven Bennetts Committed by Commit Bot

Add 'Store Feedback System Logs' to net-internals

Often testers and users need to generate system logs for debugging.
(See issue for details on why this is important / helpful).

Currently these only include logs gathered by debugd, and Chrome user
logs.

Feedback reports also gather "device logs" (chrome://device-logs) that
can be helpful for investigating networking issues.

This CL provides a separate section for writing system_logs.txt.zip to
the Downloads directory instead of the individual files gathered by
debugd, with text explaining the differences and encouraging the use
of system_logs for attaching to issues or sending to developers since
that file is compatible with logprocessor and Android Bug Tool for
easier analysis.

Note: No existing functionality has been removed.

Bug: 841441
Change-Id: I1d0423eff8c6e43cfec0b4d24e81e174ef09a1af
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2136049
Commit-Queue: Steven Bennetts <stevenjb@chromium.org>
Reviewed-by: default avatarAhmed Fakhry <afakhry@chromium.org>
Reviewed-by: default avatarEric Roman <eroman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#757580}
parent e77248f6
......@@ -2343,6 +2343,8 @@ source_set("chromeos") {
"system_logs/single_debug_daemon_log_source.h",
"system_logs/single_log_file_log_source.cc",
"system_logs/single_log_file_log_source.h",
"system_logs/system_logs_writer.cc",
"system_logs/system_logs_writer.h",
"system_logs/touch_log_source.cc",
"system_logs/touch_log_source.h",
"system_token_cert_db_initializer.cc",
......
......@@ -11,6 +11,9 @@
namespace chromeos {
// Class for writing logs collected from debugd to a specified location. Also
// supports writing the Chrome user log. Currently used by
// chrome://net-internals#chromeos.
class DebugLogWriter {
public:
// Called once StoreDebugLogs is complete. Takes two parameters:
......
// Copyright 2020 The Chromium 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 "chrome/browser/chromeos/system_logs/system_logs_writer.h"
#include "base/files/scoped_temp_dir.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/chromeos/system_logs/debug_log_writer.h"
#include "chrome/browser/feedback/system_logs/chrome_system_logs_fetcher.h"
#include "chrome/common/logging_chrome.h"
#include "components/feedback/feedback_util.h"
#include "components/feedback/system_logs/system_logs_fetcher.h"
#include "third_party/zlib/google/zip.h"
namespace chromeos {
namespace {
// Writes |contents| to a temp directory then compresses it to |dest_file_path|.
// Returns the name of the compressed file (with the zip extension) on success,
// or nullopt on failure.
base::Optional<base::FilePath> WriteCompressedFile(
const std::string& contents,
base::FilePath dest_file_path) {
base::ScopedTempDir temp_dir;
if (!temp_dir.CreateUniqueTempDir()) {
LOG(ERROR) << "Unable to create temp dir.";
return base::nullopt;
}
base::FilePath temp_file_path(
temp_dir.GetPath().Append(dest_file_path.BaseName()));
if (!base::WriteFile(temp_file_path, contents.c_str(), contents.size())) {
LOG(ERROR) << "Unable to write file: " << temp_file_path.value();
return base::nullopt;
}
base::FilePath zip_file_path(
dest_file_path.AddExtension(FILE_PATH_LITERAL(".zip")));
if (!zip::Zip(temp_dir.GetPath(), zip_file_path,
/*include_hidden_files=*/false)) {
LOG(ERROR) << "Failed to zip file to: " << zip_file_path.value();
return base::nullopt;
}
return zip_file_path;
}
// Called when SystemLogsFetcher::Fetch completes. Converts |sys_info| into a
// single string using the same mechanism as Feedback reports, then writes the
// string to a compressed system_logs.txt.zip file and invokes |callback|.
void FetchCompleted(
const base::FilePath& dest_dir,
base::OnceCallback<void(base::Optional<base::FilePath>)> callback,
std::unique_ptr<system_logs::SystemLogsResponse> sys_info) {
base::FilePath system_logs_file_path =
logging::GenerateTimestampedName(
dest_dir.Append(FILE_PATH_LITERAL("system_logs")), base::Time::Now())
.AddExtension(FILE_PATH_LITERAL(".txt"));
std::string system_logs = feedback_util::LogsToString(*sys_info.get());
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(WriteCompressedFile, std::move(system_logs),
system_logs_file_path),
std::move(callback));
}
} // namespace
namespace system_logs_writer {
void WriteSystemLogs(
const base::FilePath& dest_dir,
base::OnceCallback<void(base::Optional<base::FilePath>)> callback) {
system_logs::BuildChromeSystemLogsFetcher()->Fetch(
base::BindOnce(FetchCompleted, dest_dir, std::move(callback)));
}
} // namespace system_logs_writer
} // namespace chromeos
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_SYSTEM_LOGS_SYSTEM_LOGS_WRITER_H_
#define CHROME_BROWSER_CHROMEOS_SYSTEM_LOGS_SYSTEM_LOGS_WRITER_H_
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/optional.h"
// Helper function for writing system logs used in Feedback reports. Currently
// used by chrome://net-internals#chromeos for manual uploading of system logs.
namespace chromeos {
namespace system_logs_writer {
// Writes system_logs.txt.zip to |dest_dir|, containing the contents from
// Feedback reports. Runs |callback| on completion with the complete file path
// on success, or nullopt on failure.
void WriteSystemLogs(
const base::FilePath& dest_dir,
base::OnceCallback<void(base::Optional<base::FilePath>)> callback);
} // namespace system_logs_writer
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_SYSTEM_LOGS_SYSTEM_LOGS_WRITER_H_
......@@ -101,6 +101,10 @@ const BrowserBridge = (function() {
this.send('storeCombinedDebugLogs');
},
storeFeedbackSystemLogs() {
this.send('storeFeedbackSystemLogs');
},
setNetworkDebugMode(subsystem) {
this.send('setNetworkDebugMode', [subsystem]);
},
......@@ -149,6 +153,12 @@ const BrowserBridge = (function() {
}
},
receivedStoreFeedbackSystemLogs(status) {
for (let i = 0; i < this.storeDebugLogsObservers_.length; i++) {
this.storeDebugLogsObservers_[i].onStoreFeedbackSystemLogs(status);
}
},
receivedSetNetworkDebugMode(status) {
for (let i = 0; i < this.setNetworkDebugModeObservers_.length; i++) {
this.setNetworkDebugModeObservers_[i].onSetNetworkDebugMode(status);
......
......@@ -6,3 +6,7 @@
#chromeos-view-parse-status {
margin-top: 20px;
}
input + label {
margin-inline-start: 10px;
}
......@@ -14,22 +14,40 @@
<div id="chromeos-view-parse-status" hidden>
</div>
<div id="chromeos-view-store-debug-logs-div">
<h4>Store Logs</h4>
<h4>Store system logs</h4>
<div>
<p>This stores system_logs_{date}-{time}.txt.zip to the Downloads
directory. The archive contains a single file identical to the
system_logs file attached to Feedback reports.</p>
<p>Also writes a policies_{date}-{time}.json file for providing policy
configurations when that might be a factor.</p>
<p>Prefer this when uploading logs to issues or sending logs to developers
(unless otherwise requested).</p>
<input type="button"
id="chromeos-view-store-feedback-system-logs"
value="Store Feedback Report System Logs">
<label id="chromeos-view-store-feedback-system-logs-status"></label>
</div>
<h4>Store debugd system logs</h4>
<div>
<p>This stores the logs collected by debugd as individual files in a
single archive file in the Downloads directory:
debug-logs_{date}-{time}.tgz.
</p>
<p>Also writes a policies_{date}-{time}.json file for providing policy
configurations when that might be a factor.</p>
<input type="button"
id="chromeos-view-store-debug-logs"
value="Store System Logs">
<label for="chromeos-view-store-debug-logs"
id="chromeos-view-store-debug-logs-status">
</label>
<label id="chromeos-view-store-debug-logs-status"></label>
</div>
<div>
<p>Identical to the above, but also incldes the Chrome user logs.
The output file is named combined-logs_{date}-{time}.tgz.</p>
<input type="button"
id="chromeos-view-store-combined-debug-logs"
value="Store System and User Logs">
<label for="chromeos-view-store-combined-debug-logs"
id="chromeos-view-store-combined-debug-logs-status">
</label>
<label id="chromeos-view-store-combined-debug-logs-status"></label>
</div>
</div>
<div id="chromeos-view-network-debugging-div">
......
......@@ -129,6 +129,15 @@ const CrosView = (function() {
$(CrosView.STORE_COMBINED_DEBUG_LOGS_STATUS_ID).innerText = status;
}
/**
* Set storing combined debug logs status.
*
* @private
*/
function setStoreFeedbackSystemLogsStatus_(status) {
$(CrosView.STORE_FEEDBACK_SYSTEM_LOGS_STATUS_ID).innerText = status;
}
/**
* Set status for current debug mode.
*
......@@ -177,6 +186,11 @@ const CrosView = (function() {
$(CrosView.STORE_COMBINED_DEBUG_LOGS_STATUS_ID).innerText = '';
g_browser.storeCombinedDebugLogs();
}, false);
$(CrosView.STORE_FEEDBACK_SYSTEM_LOGS_ID)
.addEventListener('click', function(event) {
$(CrosView.STORE_FEEDBACK_SYSTEM_LOGS_STATUS_ID).innerText = '';
g_browser.storeFeedbackSystemLogs();
}, false);
$(CrosView.DEBUG_WIFI_ID).addEventListener('click', function(event) {
setNetworkDebugMode_('wifi');
......@@ -245,6 +259,10 @@ const CrosView = (function() {
'chromeos-view-store-combined-debug-logs';
CrosView.STORE_COMBINED_DEBUG_LOGS_STATUS_ID =
'chromeos-view-store-combined-debug-logs-status';
CrosView.STORE_FEEDBACK_SYSTEM_LOGS_ID =
'chromeos-view-store-feedback-system-logs';
CrosView.STORE_FEEDBACK_SYSTEM_LOGS_STATUS_ID =
'chromeos-view-store-feedback-system-logs-status';
CrosView.DEBUG_WIFI_ID = 'chromeos-view-network-debugging-wifi';
CrosView.DEBUG_ETHERNET_ID = 'chromeos-view-network-debugging-ethernet';
CrosView.DEBUG_CELLULAR_ID = 'chromeos-view-network-debugging-cellular';
......@@ -260,6 +278,7 @@ const CrosView = (function() {
onONCFileParse: setParseStatus_,
onStoreDebugLogs: setStoreDebugLogsStatus_,
onStoreCombinedDebugLogs: setStoreCombinedDebugLogsStatus_,
onStoreFeedbackSystemLogs: setStoreFeedbackSystemLogsStatus_,
onSetNetworkDebugMode: setNetworkDebugModeStatus_,
};
......
......@@ -44,6 +44,7 @@
#include "chrome/browser/chromeos/file_manager/filesystem_api_util.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/chromeos/system_logs/debug_log_writer.h"
#include "chrome/browser/chromeos/system_logs/system_logs_writer.h"
#include "chrome/browser/net/nss_context.h"
#include "chrome/common/logging_chrome.h"
#include "chromeos/dbus/dbus_thread_manager.h"
......@@ -72,6 +73,28 @@ content::WebUIDataSource* CreateNetInternalsHTMLSource() {
void IgnoreBoolCallback(bool result) {}
#if defined(OS_CHROMEOS)
base::FilePath GetDownloadsDirectory(content::WebUI* web_ui) {
Profile* profile = Profile::FromWebUI(web_ui);
const DownloadPrefs* const prefs = DownloadPrefs::FromBrowserContext(profile);
base::FilePath path = prefs->DownloadPath();
if (file_manager::util::IsUnderNonNativeLocalPath(profile, path))
path = prefs->GetDefaultDownloadDirectoryForProfile();
return path;
}
std::string GetJsonPolicies(content::WebUI* web_ui) {
auto client = std::make_unique<policy::ChromePolicyConversionsClient>(
web_ui->GetWebContents()->GetBrowserContext());
return policy::DictionaryPolicyConversions(std::move(client)).ToJSON();
}
void WriteTimestampedFile(base::FilePath file_path, std::string contents) {
file_path = logging::GenerateTimestampedName(file_path, base::Time::Now());
base::WriteFile(file_path, contents.data(), contents.size());
}
#endif
// This class receives javascript messages from the renderer.
// Note that the WebUI infrastructure runs on the UI thread, therefore all of
// this class's methods are expected to run on the UI thread.
......@@ -125,7 +148,7 @@ class NetInternalsMessageHandler
void OnCloseIdleSockets(const base::ListValue* list);
void OnFlushSocketPools(const base::ListValue* list);
#if defined(OS_CHROMEOS)
void OnDumpPolicyLogsCompleted(const base::FilePath& path,
void OnWritePolicyLogsCompleted(const base::FilePath& path,
bool should_compress,
bool combined,
const char* received_event);
......@@ -136,6 +159,11 @@ class NetInternalsMessageHandler
void OnStoreDebugLogsCompleted(const char* received_event,
const base::FilePath& log_path,
bool succeeded);
void OnStoreFeedbackSystemLogs(const char* received_event,
const base::ListValue* list);
void OnStoreFeedbackSystemLogsCompleted(
const char* received_event,
base::Optional<base::FilePath> system_logs_path);
void OnSetNetworkDebugMode(const base::ListValue* list);
void OnSetNetworkDebugModeCompleted(const std::string& subsystem,
bool succeeded);
......@@ -210,6 +238,11 @@ void NetInternalsMessageHandler::RegisterMessages() {
base::BindRepeating(&NetInternalsMessageHandler::OnStoreDebugLogs,
base::Unretained(this), true /* combined */,
"receivedStoreCombinedDebugLogs"));
web_ui()->RegisterMessageCallback(
"storeFeedbackSystemLogs",
base::BindRepeating(
&NetInternalsMessageHandler::OnStoreFeedbackSystemLogs,
base::Unretained(this), "receivedStoreFeedbackSystemLogs"));
web_ui()->RegisterMessageCallback(
"setNetworkDebugMode",
base::BindRepeating(&NetInternalsMessageHandler::OnSetNetworkDebugMode,
......@@ -422,38 +455,27 @@ void NetInternalsMessageHandler::OnImportONCFile(
onc_blob, passcode));
}
void DumpPolicyLogs(base::FilePath file_path, std::string json_policies) {
file_path = logging::GenerateTimestampedName(file_path, base::Time::Now());
base::WriteFile(file_path, json_policies.data(), json_policies.size());
}
void NetInternalsMessageHandler::OnStoreDebugLogs(bool combined,
const char* received_event,
const base::ListValue* list) {
DCHECK(list);
SendJavascriptCommand(received_event, base::Value("Creating log file..."));
Profile* profile = Profile::FromWebUI(web_ui());
const DownloadPrefs* const prefs = DownloadPrefs::FromBrowserContext(profile);
base::FilePath path = prefs->DownloadPath();
if (file_manager::util::IsUnderNonNativeLocalPath(profile, path))
path = prefs->GetDefaultDownloadDirectoryForProfile();
base::FilePath policies_path = path.Append("policies.json");
auto client = std::make_unique<policy::ChromePolicyConversionsClient>(
web_ui()->GetWebContents()->GetBrowserContext());
std::string json_policies =
policy::DictionaryPolicyConversions(std::move(client)).ToJSON();
base::FilePath path = GetDownloadsDirectory(web_ui());
std::string json_policies = GetJsonPolicies(web_ui());
base::ThreadPool::PostTaskAndReply(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(DumpPolicyLogs, policies_path, json_policies),
base::BindOnce(&NetInternalsMessageHandler::OnDumpPolicyLogsCompleted,
base::BindOnce(WriteTimestampedFile, path.Append("policies.json"),
json_policies),
base::BindOnce(&NetInternalsMessageHandler::OnWritePolicyLogsCompleted,
AsWeakPtr(), path, true /* should_compress */, combined,
received_event));
}
void NetInternalsMessageHandler::OnDumpPolicyLogsCompleted(
void NetInternalsMessageHandler::OnWritePolicyLogsCompleted(
const base::FilePath& path,
bool should_compress,
bool combined,
......@@ -483,6 +505,42 @@ void NetInternalsMessageHandler::OnStoreDebugLogsCompleted(
SendJavascriptCommand(received_event, base::Value(status));
}
void NetInternalsMessageHandler::OnStoreFeedbackSystemLogs(
const char* received_event,
const base::ListValue* list) {
DCHECK(list);
SendJavascriptCommand(received_event,
base::Value("Creating system logs file..."));
base::FilePath downloads_path = GetDownloadsDirectory(web_ui());
std::string json_policies = GetJsonPolicies(web_ui());
// Write the policies file to disk as a blocking task, then call
// system_logs_writer::WriteSystemLogs to asynchronously write the system
// logs and respond when complete.
base::ThreadPool::PostTaskAndReply(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(WriteTimestampedFile,
downloads_path.Append("policies.json"), json_policies),
base::BindOnce(
chromeos::system_logs_writer::WriteSystemLogs, downloads_path,
base::BindOnce(
&NetInternalsMessageHandler::OnStoreFeedbackSystemLogsCompleted,
AsWeakPtr(), received_event)));
}
void NetInternalsMessageHandler::OnStoreFeedbackSystemLogsCompleted(
const char* received_event,
base::Optional<base::FilePath> system_logs_path) {
std::string status = system_logs_path
? "Created system_logs file: " +
system_logs_path->BaseName().AsUTF8Unsafe()
: "Failed to create system logs file.";
SendJavascriptCommand(received_event, base::Value(status));
}
void NetInternalsMessageHandler::OnSetNetworkDebugMode(
const base::ListValue* list) {
std::string subsystem;
......
......@@ -7,7 +7,6 @@
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "components/feedback/feedback_report.h"
#include "components/feedback/feedback_util.h"
#include "components/feedback/proto/common.pb.h"
......@@ -24,10 +23,6 @@ constexpr int kChromeOSProductId = 208;
constexpr int kChromeBrowserProductId = 237;
#endif
constexpr char kMultilineIndicatorString[] = "<multiline>\n";
constexpr char kMultilineStartString[] = "---------- START ----------\n";
constexpr char kMultilineEndString[] = "---------- END ----------\n\n";
// The below thresholds were chosen arbitrarily to conveniently show small data
// as part of the report itself without having to look into the system_logs.zip
// file.
......@@ -54,34 +49,6 @@ bool BelowCompressionThreshold(const std::string& content) {
return true;
}
// Converts the system logs into a string that we can compress and send
// with the report.
std::string LogsToString(const FeedbackCommon::SystemLogsMap& sys_info) {
std::string syslogs_string;
for (const auto& iter : sys_info) {
std::string key = iter.first;
std::string value = iter.second;
base::TrimString(key, "\n ", &key);
base::TrimString(value, "\n ", &value);
// We must avoid adding the crash IDs to the system_logs.txt file for
// privacy reasons. They should just be part of the product specific data.
if (key == feedback::FeedbackReport::kCrashReportIdsKey ||
key == feedback::FeedbackReport::kAllCrashReportIdsKey)
continue;
if (value.find("\n") != std::string::npos) {
syslogs_string.append(key + "=" + kMultilineIndicatorString +
kMultilineStartString + value + "\n" +
kMultilineEndString);
} else {
syslogs_string.append(key + "=" + value + "\n");
}
}
return syslogs_string;
}
void AddFeedbackData(userfeedback::ExtensionSubmit* feedback_data,
const std::string& key,
const std::string& value) {
......@@ -228,7 +195,9 @@ void FeedbackCommon::CompressFile(const base::FilePath& filename,
}
void FeedbackCommon::CompressLogs() {
std::string logs = LogsToString(logs_);
// Convert the system logs into a string that we can compress and send with
// the report.
std::string logs = feedback_util::LogsToString(logs_);
if (!logs.empty()) {
CompressFile(base::FilePath(kLogsFilename), kLogsAttachmentName,
std::move(logs));
......
......@@ -9,15 +9,23 @@
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/string_util.h"
#include "components/feedback/feedback_report.h"
#include "third_party/zlib/google/zip.h"
namespace feedback_util {
namespace {
constexpr char kMultilineIndicatorString[] = "<multiline>\n";
constexpr char kMultilineStartString[] = "---------- START ----------\n";
constexpr char kMultilineEndString[] = "---------- END ----------\n\n";
} // namespace
namespace feedback_util {
bool ZipString(const base::FilePath& filename,
const std::string& data, std::string* compressed_logs) {
const std::string& data,
std::string* compressed_logs) {
base::ScopedTempDir temp_dir;
base::FilePath zip_file;
......@@ -39,4 +47,30 @@ bool ZipString(const base::FilePath& filename,
return succeed;
}
std::string LogsToString(const FeedbackCommon::SystemLogsMap& sys_info) {
std::string syslogs_string;
for (const auto& iter : sys_info) {
std::string key = iter.first;
base::TrimString(key, "\n ", &key);
if (key == feedback::FeedbackReport::kCrashReportIdsKey ||
key == feedback::FeedbackReport::kAllCrashReportIdsKey) {
// Avoid adding the crash IDs to the system_logs.txt file for privacy
// reasons. They should just be part of the product specific data.
continue;
}
std::string value = iter.second;
base::TrimString(value, "\n ", &value);
if (value.find("\n") != std::string::npos) {
syslogs_string.append(key + "=" + kMultilineIndicatorString +
kMultilineStartString + value + "\n" +
kMultilineEndString);
} else {
syslogs_string.append(key + "=" + value + "\n");
}
}
return syslogs_string;
}
} // namespace feedback_util
......@@ -8,6 +8,7 @@
#include <string>
#include "base/files/file_path.h"
#include "components/feedback/feedback_common.h"
namespace feedback_util {
......@@ -15,6 +16,10 @@ bool ZipString(const base::FilePath& filename,
const std::string& data,
std::string* compressed_data);
// Converts the entries in |sys_info| into a single string. Primarily used for
// creating a system_logs.txt file attached to feedback reports.
std::string LogsToString(const FeedbackCommon::SystemLogsMap& sys_info);
} // namespace feedback_util
#endif // COMPONENTS_FEEDBACK_FEEDBACK_UTIL_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment