| // Copyright 2013 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/extensions/api/log_private/log_private_api.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/files/file_util.h" |
| #include "base/json/json_writer.h" |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/download/download_prefs.h" |
| #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h" |
| #include "chrome/browser/extensions/api/log_private/filter_handler.h" |
| #include "chrome/browser/extensions/api/log_private/log_parser.h" |
| #include "chrome/browser/extensions/api/log_private/syslog_parser.h" |
| #include "chrome/browser/feedback/system_logs/scrubbed_system_logs_fetcher.h" |
| #include "chrome/browser/io_thread.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/common/extensions/api/log_private.h" |
| #include "chrome/common/logging_chrome.h" |
| #include "components/net_log/chrome_net_log.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/browser/extension_function.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/granted_file_entry.h" |
| #include "net/log/net_log_entry.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include "chrome/browser/chromeos/system_logs/debug_log_writer.h" |
| #endif |
| |
| using content::BrowserThread; |
| |
| namespace events { |
| const char kOnCapturedEvents[] = "logPrivate.onCapturedEvents"; |
| } // namespace events |
| |
| namespace extensions { |
| namespace { |
| |
| const char kAppLogsSubdir[] = "apps"; |
| const char kLogDumpsSubdir[] = "log_dumps"; |
| const char kLogFileNameBase[] = "net-internals"; |
| const int kNetLogEventDelayMilliseconds = 100; |
| |
| // Gets sequenced task runner for file specific calls within this API. |
| scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() { |
| base::SequencedWorkerPool* pool = BrowserThread::GetBlockingPool(); |
| return pool->GetSequencedTaskRunnerWithShutdownBehavior( |
| pool->GetNamedSequenceToken(FileResource::kSequenceToken), |
| base::SequencedWorkerPool::BLOCK_SHUTDOWN); |
| } |
| |
| #if DCHECK_IS_ON() |
| base::LazyInstance<base::SequenceChecker>::Leaky |
| g_file_resource_sequence_checker = LAZY_INSTANCE_INITIALIZER; |
| #endif |
| |
| // Checks if we are running on sequenced task runner thread. |
| void AssertCurrentlyOnFileResourceSequence() { |
| #if DCHECK_IS_ON() |
| DCHECK(g_file_resource_sequence_checker.Get().CalledOnValidSequence()); |
| #endif |
| } |
| |
| std::unique_ptr<LogParser> CreateLogParser(const std::string& log_type) { |
| if (log_type == "syslog") |
| return std::unique_ptr<LogParser>(new SyslogParser()); |
| // TODO(shinfan): Add more parser here |
| |
| NOTREACHED() << "Invalid log type: " << log_type; |
| return std::unique_ptr<LogParser>(); |
| } |
| |
| void CollectLogInfo(FilterHandler* filter_handler, |
| system_logs::SystemLogsResponse* logs, |
| std::vector<api::log_private::LogEntry>* output) { |
| for (system_logs::SystemLogsResponse::const_iterator |
| request_it = logs->begin(); request_it != logs->end(); ++request_it) { |
| if (!filter_handler->IsValidSource(request_it->first)) { |
| continue; |
| } |
| std::unique_ptr<LogParser> parser(CreateLogParser(request_it->first)); |
| if (parser) { |
| parser->Parse(request_it->second, output, filter_handler); |
| } |
| } |
| } |
| |
| // Returns directory location of app-specific logs that are initiated with |
| // logPrivate.startEventRecorder() calls - /home/chronos/user/log/apps |
| base::FilePath GetAppLogDirectory() { |
| return logging::GetSessionLogDir(*base::CommandLine::ForCurrentProcess()) |
| .Append(kAppLogsSubdir); |
| } |
| |
| // Returns directory location where logs dumps initiated with chrome.dumpLogs |
| // will be stored - /home/chronos/<user_profile_dir>/Downloads/log_dumps |
| base::FilePath GetLogDumpDirectory(content::BrowserContext* context) { |
| const DownloadPrefs* const prefs = DownloadPrefs::FromBrowserContext(context); |
| return prefs->DownloadPath().Append(kLogDumpsSubdir); |
| } |
| |
| // Removes direcotry content of |logs_dumps| and |app_logs_dir| (only for the |
| // primary profile). |
| void CleanUpLeftoverLogs(bool is_primary_profile, |
| const base::FilePath& app_logs_dir, |
| const base::FilePath& logs_dumps) { |
| LOG(WARNING) << "Deleting " << app_logs_dir.value(); |
| LOG(WARNING) << "Deleting " << logs_dumps.value(); |
| |
| AssertCurrentlyOnFileResourceSequence(); |
| base::DeleteFile(logs_dumps, true); |
| |
| // App-specific logs are stored in /home/chronos/user/log/apps directory that |
| // is shared between all profiles in multi-profile case. We should not |
| // nuke it for non-primary profiles. |
| if (!is_primary_profile) |
| return; |
| |
| base::DeleteFile(app_logs_dir, true); |
| } |
| |
| } // namespace |
| |
| const char FileResource::kSequenceToken[] = "log_api_files"; |
| |
| FileResource::FileResource(const std::string& owner_extension_id, |
| const base::FilePath& path) |
| : ApiResource(owner_extension_id), path_(path) { |
| } |
| |
| FileResource::~FileResource() { |
| base::DeleteFile(path_, true); |
| } |
| |
| bool FileResource::IsPersistent() const { |
| return false; |
| } |
| |
| // static |
| LogPrivateAPI* LogPrivateAPI::Get(content::BrowserContext* context) { |
| LogPrivateAPI* api = GetFactoryInstance()->Get(context); |
| api->Initialize(); |
| return api; |
| } |
| |
| LogPrivateAPI::LogPrivateAPI(content::BrowserContext* context) |
| : browser_context_(context), |
| logging_net_internals_(false), |
| event_sink_(api::log_private::EVENT_SINK_CAPTURE), |
| extension_registry_observer_(this), |
| log_file_resources_(context), |
| initialized_(false) { |
| } |
| |
| LogPrivateAPI::~LogPrivateAPI() { |
| } |
| |
| void LogPrivateAPI::StartNetInternalsWatch( |
| const std::string& extension_id, |
| api::log_private::EventSink event_sink, |
| const base::Closure& closure) { |
| net_internal_watches_.insert(extension_id); |
| |
| // Nuke any leftover app-specific or dumped log files from previous sessions. |
| BrowserThread::PostTaskAndReply( |
| BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&LogPrivateAPI::MaybeStartNetInternalLogging, |
| base::Unretained(this), |
| extension_id, |
| g_browser_process->io_thread(), |
| event_sink), |
| closure); |
| } |
| |
| void LogPrivateAPI::StopNetInternalsWatch(const std::string& extension_id, |
| const base::Closure& closure) { |
| net_internal_watches_.erase(extension_id); |
| MaybeStopNetInternalLogging(closure); |
| } |
| |
| void LogPrivateAPI::StopAllWatches(const std::string& extension_id, |
| const base::Closure& closure) { |
| StopNetInternalsWatch(extension_id, closure); |
| } |
| |
| void LogPrivateAPI::RegisterTempFile(const std::string& owner_extension_id, |
| const base::FilePath& file_path) { |
| GetSequencedTaskRunner()->PostTask( |
| FROM_HERE, |
| base::Bind(&LogPrivateAPI::RegisterTempFileOnFileResourceSequence, |
| base::Unretained(this), owner_extension_id, file_path)); |
| } |
| |
| static base::LazyInstance<BrowserContextKeyedAPIFactory<LogPrivateAPI> > |
| g_factory = LAZY_INSTANCE_INITIALIZER; |
| |
| // static |
| BrowserContextKeyedAPIFactory<LogPrivateAPI>* |
| LogPrivateAPI::GetFactoryInstance() { |
| return g_factory.Pointer(); |
| } |
| |
| void LogPrivateAPI::OnAddEntry(const net::NetLogEntry& entry) { |
| // We could receive events on whatever thread they happen to be generated, |
| // since we are only interested in network events, we should ignore any |
| // other thread than BrowserThread::IO. |
| if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { |
| return; |
| } |
| |
| if (!pending_entries_.get()) { |
| pending_entries_.reset(new base::ListValue()); |
| BrowserThread::PostDelayedTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&LogPrivateAPI::PostPendingEntries, base::Unretained(this)), |
| base::TimeDelta::FromMilliseconds(kNetLogEventDelayMilliseconds)); |
| } |
| pending_entries_->Append(entry.ToValue()); |
| } |
| |
| void LogPrivateAPI::PostPendingEntries() { |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind(&LogPrivateAPI:: AddEntriesOnUI, |
| base::Unretained(this), |
| base::Passed(&pending_entries_))); |
| } |
| |
| void LogPrivateAPI::AddEntriesOnUI(std::unique_ptr<base::ListValue> value) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| for (std::set<std::string>::iterator ix = net_internal_watches_.begin(); |
| ix != net_internal_watches_.end(); ++ix) { |
| // Create the event's arguments value. |
| std::unique_ptr<base::ListValue> event_args(new base::ListValue()); |
| event_args->Append(value->DeepCopy()); |
| std::unique_ptr<Event> event( |
| new Event(::extensions::events::LOG_PRIVATE_ON_CAPTURED_EVENTS, |
| ::events::kOnCapturedEvents, std::move(event_args))); |
| EventRouter::Get(browser_context_) |
| ->DispatchEventToExtension(*ix, std::move(event)); |
| } |
| } |
| |
| void LogPrivateAPI::CreateTempNetLogFile(const std::string& owner_extension_id, |
| base::ScopedFILE* file) { |
| AssertCurrentlyOnFileResourceSequence(); |
| |
| // Create app-specific subdirectory in session logs folder. |
| base::FilePath app_log_dir = GetAppLogDirectory().Append(owner_extension_id); |
| if (!base::DirectoryExists(app_log_dir)) { |
| if (!base::CreateDirectory(app_log_dir)) { |
| LOG(ERROR) << "Could not create dir " << app_log_dir.value(); |
| return; |
| } |
| } |
| |
| base::FilePath file_path = app_log_dir.Append(kLogFileNameBase); |
| file_path = logging::GenerateTimestampedName(file_path, base::Time::Now()); |
| FILE* file_ptr = fopen(file_path.value().c_str(), "w"); |
| if (file_ptr == nullptr) { |
| LOG(ERROR) << "Could not open " << file_path.value(); |
| return; |
| } |
| |
| RegisterTempFileOnFileResourceSequence(owner_extension_id, file_path); |
| return file->reset(file_ptr); |
| } |
| |
| void LogPrivateAPI::StartObservingNetEvents( |
| IOThread* io_thread, |
| base::ScopedFILE* file) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (!file->get()) |
| return; |
| |
| write_to_file_observer_.reset(new net::WriteToFileNetLogObserver()); |
| write_to_file_observer_->set_capture_mode( |
| net::NetLogCaptureMode::IncludeCookiesAndCredentials()); |
| write_to_file_observer_->StartObserving(io_thread->net_log(), |
| std::move(*file), nullptr, nullptr); |
| } |
| |
| void LogPrivateAPI::MaybeStartNetInternalLogging( |
| const std::string& caller_extension_id, |
| IOThread* io_thread, |
| api::log_private::EventSink event_sink) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (!logging_net_internals_) { |
| logging_net_internals_ = true; |
| event_sink_ = event_sink; |
| switch (event_sink_) { |
| case api::log_private::EVENT_SINK_CAPTURE: { |
| io_thread->net_log()->DeprecatedAddObserver( |
| this, net::NetLogCaptureMode::IncludeCookiesAndCredentials()); |
| break; |
| } |
| case api::log_private::EVENT_SINK_FILE: { |
| base::ScopedFILE* file = new base::ScopedFILE(); |
| // Initialize a FILE on the blocking pool and start observing event |
| // on IO thread. |
| GetSequencedTaskRunner()->PostTaskAndReply( |
| FROM_HERE, |
| base::Bind(&LogPrivateAPI::CreateTempNetLogFile, |
| base::Unretained(this), |
| caller_extension_id, |
| file), |
| base::Bind(&LogPrivateAPI::StartObservingNetEvents, |
| base::Unretained(this), |
| io_thread, |
| base::Owned(file))); |
| break; |
| } |
| case api::log_private::EVENT_SINK_NONE: { |
| NOTREACHED(); |
| break; |
| } |
| } |
| } |
| } |
| |
| void LogPrivateAPI::MaybeStopNetInternalLogging(const base::Closure& closure) { |
| if (net_internal_watches_.empty()) { |
| if (closure.is_null()) { |
| BrowserThread::PostTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&LogPrivateAPI::StopNetInternalLogging, |
| base::Unretained(this))); |
| } else { |
| BrowserThread::PostTaskAndReply( |
| BrowserThread::IO, |
| FROM_HERE, |
| base::Bind(&LogPrivateAPI::StopNetInternalLogging, |
| base::Unretained(this)), |
| closure); |
| } |
| } |
| } |
| |
| void LogPrivateAPI::StopNetInternalLogging() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (net_log() && logging_net_internals_) { |
| logging_net_internals_ = false; |
| switch (event_sink_) { |
| case api::log_private::EVENT_SINK_CAPTURE: |
| net_log()->DeprecatedRemoveObserver(this); |
| break; |
| case api::log_private::EVENT_SINK_FILE: |
| write_to_file_observer_->StopObserving(nullptr); |
| write_to_file_observer_.reset(); |
| break; |
| case api::log_private::EVENT_SINK_NONE: |
| NOTREACHED(); |
| break; |
| } |
| } |
| } |
| |
| void LogPrivateAPI::Initialize() { |
| if (initialized_) |
| return; |
| |
| // Clean up temp files and folders from the previous sessions. |
| initialized_ = true; |
| extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); |
| GetSequencedTaskRunner()->PostTask( |
| FROM_HERE, |
| base::Bind(&CleanUpLeftoverLogs, |
| Profile::FromBrowserContext(browser_context_) == |
| ProfileManager::GetPrimaryUserProfile(), |
| GetAppLogDirectory(), |
| GetLogDumpDirectory(browser_context_))); |
| } |
| |
| void LogPrivateAPI::RegisterTempFileOnFileResourceSequence( |
| const std::string& owner_extension_id, |
| const base::FilePath& file_path) { |
| AssertCurrentlyOnFileResourceSequence(); |
| log_file_resources_.Add(new FileResource(owner_extension_id, file_path)); |
| } |
| |
| void LogPrivateAPI::OnExtensionUnloaded( |
| content::BrowserContext* browser_context, |
| const Extension* extension, |
| UnloadedExtensionInfo::Reason reason) { |
| StopNetInternalsWatch(extension->id(), base::Closure()); |
| } |
| |
| LogPrivateGetHistoricalFunction::LogPrivateGetHistoricalFunction() { |
| } |
| |
| LogPrivateGetHistoricalFunction::~LogPrivateGetHistoricalFunction() { |
| } |
| |
| bool LogPrivateGetHistoricalFunction::RunAsync() { |
| // Get parameters |
| std::unique_ptr<api::log_private::GetHistorical::Params> params( |
| api::log_private::GetHistorical::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| filter_handler_.reset(new FilterHandler(params->filter)); |
| |
| system_logs::SystemLogsFetcherBase* fetcher; |
| if ((params->filter).scrub) { |
| fetcher = new system_logs::ScrubbedSystemLogsFetcher(); |
| } else { |
| fetcher = new system_logs::AboutSystemLogsFetcher(); |
| } |
| fetcher->Fetch( |
| base::Bind(&LogPrivateGetHistoricalFunction::OnSystemLogsLoaded, this)); |
| |
| return true; |
| } |
| |
| void LogPrivateGetHistoricalFunction::OnSystemLogsLoaded( |
| std::unique_ptr<system_logs::SystemLogsResponse> sys_info) { |
| // Prepare result |
| api::log_private::Result result; |
| CollectLogInfo(filter_handler_.get(), sys_info.get(), &result.data); |
| api::log_private::Filter::Populate( |
| *((filter_handler_->GetFilter())->ToValue()), &result.filter); |
| SetResult(result.ToValue()); |
| SendResponse(true); |
| } |
| |
| LogPrivateStartEventRecorderFunction::LogPrivateStartEventRecorderFunction() { |
| } |
| |
| LogPrivateStartEventRecorderFunction::~LogPrivateStartEventRecorderFunction() { |
| } |
| |
| bool LogPrivateStartEventRecorderFunction::RunAsync() { |
| std::unique_ptr<api::log_private::StartEventRecorder::Params> params( |
| api::log_private::StartEventRecorder::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| switch (params->event_type) { |
| case api::log_private::EVENT_TYPE_NETWORK: |
| LogPrivateAPI::Get(Profile::FromBrowserContext(browser_context())) |
| ->StartNetInternalsWatch( |
| extension_id(), |
| params->sink, |
| base::Bind( |
| &LogPrivateStartEventRecorderFunction::OnEventRecorderStarted, |
| this)); |
| break; |
| case api::log_private::EVENT_TYPE_NONE: |
| NOTREACHED(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void LogPrivateStartEventRecorderFunction::OnEventRecorderStarted() { |
| SendResponse(true); |
| } |
| |
| LogPrivateStopEventRecorderFunction::LogPrivateStopEventRecorderFunction() { |
| } |
| |
| LogPrivateStopEventRecorderFunction::~LogPrivateStopEventRecorderFunction() { |
| } |
| |
| bool LogPrivateStopEventRecorderFunction::RunAsync() { |
| std::unique_ptr<api::log_private::StopEventRecorder::Params> params( |
| api::log_private::StopEventRecorder::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| switch (params->event_type) { |
| case api::log_private::EVENT_TYPE_NETWORK: |
| LogPrivateAPI::Get(Profile::FromBrowserContext(browser_context())) |
| ->StopNetInternalsWatch( |
| extension_id(), |
| base::Bind( |
| &LogPrivateStopEventRecorderFunction::OnEventRecorderStopped, |
| this)); |
| break; |
| case api::log_private::EVENT_TYPE_NONE: |
| NOTREACHED(); |
| return false; |
| } |
| return true; |
| } |
| |
| void LogPrivateStopEventRecorderFunction::OnEventRecorderStopped() { |
| SendResponse(true); |
| } |
| |
| LogPrivateDumpLogsFunction::LogPrivateDumpLogsFunction() { |
| } |
| |
| LogPrivateDumpLogsFunction::~LogPrivateDumpLogsFunction() { |
| } |
| |
| bool LogPrivateDumpLogsFunction::RunAsync() { |
| LogPrivateAPI::Get(Profile::FromBrowserContext(browser_context())) |
| ->StopAllWatches( |
| extension_id(), |
| base::Bind(&LogPrivateDumpLogsFunction::OnStopAllWatches, this)); |
| return true; |
| } |
| |
| void LogPrivateDumpLogsFunction::OnStopAllWatches() { |
| chromeos::DebugLogWriter::StoreCombinedLogs( |
| GetLogDumpDirectory(browser_context()).Append(extension_id()), |
| FileResource::kSequenceToken, |
| base::Bind(&LogPrivateDumpLogsFunction::OnStoreLogsCompleted, this)); |
| } |
| |
| void LogPrivateDumpLogsFunction::OnStoreLogsCompleted( |
| const base::FilePath& log_path, |
| bool succeeded) { |
| if (succeeded) { |
| LogPrivateAPI::Get(Profile::FromBrowserContext(browser_context())) |
| ->RegisterTempFile(extension_id(), log_path); |
| } |
| |
| std::unique_ptr<base::DictionaryValue> response(new base::DictionaryValue()); |
| extensions::GrantedFileEntry file_entry = |
| extensions::app_file_handler_util::CreateFileEntry( |
| Profile::FromBrowserContext(browser_context()), |
| extension(), |
| render_frame_host()->GetProcess()->GetID(), |
| log_path, |
| false); |
| |
| auto entry = base::MakeUnique<base::DictionaryValue>(); |
| entry->SetString("fileSystemId", file_entry.filesystem_id); |
| entry->SetString("baseName", file_entry.registered_name); |
| entry->SetString("id", file_entry.id); |
| entry->SetBoolean("isDirectory", false); |
| base::ListValue* entry_list = new base::ListValue(); |
| entry_list->Append(std::move(entry)); |
| response->Set("entries", entry_list); |
| response->SetBoolean("multiple", false); |
| SetResult(std::move(response)); |
| SendResponse(succeeded); |
| } |
| |
| } // namespace extensions |