123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 |
- // Copyright (c) 2012 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 "build/build_config.h"
- // Need to include this before most other files because it defines
- // IPC_MESSAGE_LOG_ENABLED. We need to use it to define
- // IPC_MESSAGE_MACROS_LOG_ENABLED so render_messages.h will generate the
- // ViewMsgLog et al. functions.
- #include "ipc/ipc_buildflags.h"
- // On Windows, the about:ipc dialog shows IPCs; on POSIX, we hook up a
- // logger in this file. (We implement about:ipc on Mac but implement
- // the loggers here anyway). We need to do this real early to be sure
- // IPC_MESSAGE_MACROS_LOG_ENABLED doesn't get undefined.
- #if BUILDFLAG(IS_POSIX) && BUILDFLAG(IPC_MESSAGE_LOG_ENABLED)
- #define IPC_MESSAGE_MACROS_LOG_ENABLED
- #include "content/public/common/content_ipc_logging.h"
- #define IPC_LOG_TABLE_ADD_ENTRY(msg_id, logger) \
- content::RegisterIPCLogger(msg_id, logger)
- #include "chrome/common/all_messages.h"
- #endif
- #if BUILDFLAG(IS_WIN)
- #include <windows.h>
- #endif
- #include "chrome/common/logging_chrome.h"
- #include <fstream> // NOLINT
- #include <memory> // NOLINT
- #include <string> // NOLINT
- #include "base/base_switches.h"
- #include "base/bind.h"
- #include "base/command_line.h"
- #include "base/compiler_specific.h"
- #include "base/debug/debugger.h"
- #include "base/debug/dump_without_crashing.h"
- #include "base/environment.h"
- #include "base/files/file_path.h"
- #include "base/files/file_util.h"
- #include "base/metrics/statistics_recorder.h"
- #include "base/path_service.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/string_util.h"
- #include "base/strings/stringprintf.h"
- #include "base/strings/utf_string_conversions.h"
- #include "build/chromeos_buildflags.h"
- #include "chrome/common/chrome_constants.h"
- #include "chrome/common/chrome_paths.h"
- #include "chrome/common/chrome_switches.h"
- #include "chrome/common/env_vars.h"
- #include "content/public/common/content_switches.h"
- #include "ipc/ipc_logging.h"
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- #include "ash/constants/ash_switches.h"
- #endif
- #if BUILDFLAG(IS_WIN)
- #include <initguid.h>
- #include "base/logging_win.h"
- #include "base/syslog_logging.h"
- #include "chrome/common/win/eventlog_messages.h"
- #include "chrome/install_static/install_details.h"
- #endif
- namespace logging {
- namespace {
- // When true, this means that error dialogs should not be shown.
- bool dialogs_are_suppressed_ = false;
- ScopedLogAssertHandler* assert_handler_ = nullptr;
- // This should be true for exactly the period between the end of
- // InitChromeLogging() and the beginning of CleanupChromeLogging().
- bool chrome_logging_initialized_ = false;
- // Set if we called InitChromeLogging() but failed to initialize.
- bool chrome_logging_failed_ = false;
- // This should be true for exactly the period between the end of
- // InitChromeLogging() and the beginning of CleanupChromeLogging().
- bool chrome_logging_redirected_ = false;
- // The directory on which we do rotation of log files instead of switching
- // with symlink. Because this directory doesn't support symlinks and the logic
- // doesn't work correctly.
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- constexpr char kChronosHomeDir[] = "/home/chronos/user/";
- #endif // BUILDFLAG(IS_CHROMEOS_ASH)
- #if BUILDFLAG(IS_WIN)
- // {7FE69228-633E-4f06-80C1-527FEA23E3A7}
- const GUID kChromeTraceProviderName = {
- 0x7fe69228, 0x633e, 0x4f06,
- { 0x80, 0xc1, 0x52, 0x7f, 0xea, 0x23, 0xe3, 0xa7 } };
- #endif
- // Assertion handler for logging errors that occur when dialogs are
- // silenced. To record a new error, pass the log string associated
- // with that error in the str parameter.
- NOINLINE void SilentRuntimeAssertHandler(const char* file,
- int line,
- const base::StringPiece message,
- const base::StringPiece stack_trace) {
- base::debug::BreakDebugger();
- }
- // Suppresses error/assertion dialogs and enables the logging of
- // those errors into silenced_errors_.
- void SuppressDialogs() {
- if (dialogs_are_suppressed_)
- return;
- assert_handler_ = new ScopedLogAssertHandler(
- base::BindRepeating(SilentRuntimeAssertHandler));
- #if BUILDFLAG(IS_WIN)
- UINT new_flags =
- SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX;
- // Preserve existing error mode, as discussed at http://t/dmea
- UINT existing_flags = SetErrorMode(new_flags);
- SetErrorMode(existing_flags | new_flags);
- #endif
- dialogs_are_suppressed_ = true;
- }
- } // anonymous namespace
- LoggingDestination DetermineLoggingDestination(
- const base::CommandLine& command_line) {
- #if BUILDFLAG(IS_FUCHSIA)
- // On Fuchsia, the default logging mode is the system log in both debug and
- // release mode.
- const LoggingDestination kDefaultLoggingMode = LOG_TO_SYSTEM_DEBUG_LOG;
- #elif NDEBUG
- const LoggingDestination kDefaultLoggingMode = LOG_TO_FILE;
- #else
- const LoggingDestination kDefaultLoggingMode = LOG_TO_ALL;
- #endif // BUILDFLAG(IS_FUCHSIA)
- #ifdef NDEBUG
- bool enable_logging = false;
- const char* kInvertLoggingSwitch = switches::kEnableLogging;
- #else
- bool enable_logging = true;
- const char* kInvertLoggingSwitch = switches::kDisableLogging;
- #endif
- if (command_line.HasSwitch(kInvertLoggingSwitch))
- enable_logging = !enable_logging;
- if (!enable_logging)
- return LOG_NONE;
- if (command_line.HasSwitch(switches::kEnableLogging)) {
- // Let --enable-logging=stderr force only stderr, particularly useful for
- // non-debug builds where otherwise you can't get logs to stderr at all.
- std::string logging_destination =
- command_line.GetSwitchValueASCII(switches::kEnableLogging);
- if (logging_destination == "stderr") {
- return LOG_TO_SYSTEM_DEBUG_LOG | LOG_TO_STDERR;
- } else if (logging_destination != "") {
- PLOG(ERROR) << "Invalid logging destination: " << logging_destination;
- }
- }
- return kDefaultLoggingMode;
- }
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- bool RotateLogFile(const base::FilePath& target_path) {
- DCHECK(!target_path.empty());
- // If the old log file doesn't exist, do nothing.
- if (!base::PathExists(target_path)) {
- return true;
- }
- // Retrieve the creation time of the old log file.
- base::File::Info info;
- {
- // Opens a file, only if it exists.
- base::File fp(target_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
- if (!fp.GetInfo(&info)) {
- // On failure, keep using the same file.
- return false;
- }
- }
- // Generate the rotated log path name from the creation time.
- // (eg. "/home/chrome/user/log/chrome_220102-030405")
- base::Time timestamp = info.creation_time;
- base::FilePath rotated_path = GenerateTimestampedName(target_path, timestamp);
- // Rare case: if the target path already exists, generate the alternative by
- // incrementing the timestamp. This may happen when the Chrome restarts
- // multiple times in a second.
- while (base::PathExists(rotated_path)) {
- timestamp += base::Seconds(1);
- rotated_path = GenerateTimestampedName(target_path, timestamp);
- }
- // Rename the old log file: |target_path| => |rotated_path|.
- // We don't use |base::Move|, since we don't consider the inter-filesystem
- // move in this logic. The current logic depends on the fact that the ctime
- // won't be changed after rotation, but ctime may be changed on
- // inter-filesystem move.
- if (!base::ReplaceFile(target_path, rotated_path, nullptr)) {
- PLOG(ERROR) << "Failed to rotate the log files: " << target_path << " => "
- << rotated_path;
- return false;
- }
- return true;
- }
- base::FilePath SetUpSymlinkIfNeeded(const base::FilePath& symlink_path,
- bool new_log) {
- DCHECK(!symlink_path.empty());
- // For backward compatibility, set up a .../chrome symlink to
- // .../chrome.LATEST as needed. This code needs to run only
- // after the migration (i.e. the addition of chrome.LATEST).
- if (symlink_path.Extension() == ".LATEST") {
- base::FilePath extensionless_path = symlink_path.ReplaceExtension("");
- base::FilePath target_path;
- bool extensionless_symlink_exists =
- base::ReadSymbolicLink(extensionless_path, &target_path);
- if (target_path != symlink_path) {
- // No link, or wrong link. Clean up. This should happen only once in
- // each log directory after the OS version update, but some of those
- // directories may not be accessed for a long time, so this code needs to
- // stay in forever :/
- if (extensionless_symlink_exists &&
- !base::DeleteFile(extensionless_path)) {
- DPLOG(WARNING) << "Cannot delete " << extensionless_path.value();
- }
- // After cleaning up, create the symlink.
- if (!base::CreateSymbolicLink(symlink_path, extensionless_path)) {
- DPLOG(ERROR) << "Cannot create " << extensionless_path.value();
- }
- }
- }
- // If not starting a new log, then just log through the existing symlink, but
- // if the symlink doesn't exist, create it.
- //
- // If starting a new log, then rename the old symlink as
- // symlink_path.PREVIOUS and make a new symlink to a fresh log file.
- // Check for existence of the symlink.
- base::FilePath target_path;
- bool symlink_exists = base::ReadSymbolicLink(symlink_path, &target_path);
- if (symlink_exists && !new_log)
- return target_path;
- // Remove any extension before time-stamping.
- target_path = GenerateTimestampedName(symlink_path.RemoveExtension(),
- base::Time::Now());
- if (symlink_exists) {
- base::FilePath previous_symlink_path =
- symlink_path.ReplaceExtension(".PREVIOUS");
- // Rename symlink to .PREVIOUS. This nukes an existing symlink just like
- // the rename(2) syscall does.
- if (!base::ReplaceFile(symlink_path, previous_symlink_path, nullptr)) {
- DPLOG(WARNING) << "Cannot rename " << symlink_path.value() << " to "
- << previous_symlink_path.value();
- }
- }
- // If all went well, the symlink no longer exists. Recreate it.
- base::FilePath relative_target_path = target_path.BaseName();
- if (!base::CreateSymbolicLink(relative_target_path, symlink_path)) {
- DPLOG(ERROR) << "Unable to create symlink " << symlink_path.value()
- << " pointing at " << relative_target_path.value();
- }
- return target_path;
- }
- void RemoveSymlinkAndLog(const base::FilePath& link_path,
- const base::FilePath& target_path) {
- if (::unlink(link_path.value().c_str()) == -1)
- DPLOG(WARNING) << "Unable to unlink symlink " << link_path.value();
- if (target_path != link_path && ::unlink(target_path.value().c_str()) == -1)
- DPLOG(WARNING) << "Unable to unlink log file " << target_path.value();
- }
- base::FilePath GetSessionLogDir(const base::CommandLine& command_line) {
- std::string log_dir;
- std::unique_ptr<base::Environment> env(base::Environment::Create());
- if (!env->GetVar(env_vars::kSessionLogDir, &log_dir))
- NOTREACHED();
- return base::FilePath(log_dir);
- }
- base::FilePath GetSessionLogFile(const base::CommandLine& command_line) {
- return GetSessionLogDir(command_line)
- .Append(GetLogFileName(command_line).BaseName());
- }
- base::FilePath SetUpLogFile(const base::FilePath& target_path, bool new_log) {
- const bool supports_symlinks =
- !(target_path.IsAbsolute() &&
- base::StartsWith(target_path.value(), kChronosHomeDir));
- // TODO(crbug.com/1326369): Remove the old symlink logic.
- if (supports_symlinks) {
- // As for now, we keep the original log rotation logic on the file system
- // which supports symlinks.
- return SetUpSymlinkIfNeeded(target_path, new_log);
- }
- // Chrome OS doesn't support symlinks on this file system, so that it uses
- // the rotation logic which doesn't use symlinks.
- if (!new_log) {
- // Keep using the same log file without doing anything.
- return target_path;
- }
- // For backward compatibility, ignore a ".LATEST" extension the way
- // |SetUpSymlinkIfNeeded()| does.
- base::FilePath bare_path = target_path;
- if (target_path.Extension() == ".LATEST") {
- bare_path = target_path.ReplaceExtension("");
- }
- // Try to rotate the log.
- if (!RotateLogFile(bare_path)) {
- DPLOG(ERROR) << "Failed to rotate the log file: " << bare_path.value()
- << ". Keeping using the same log file without rotating.";
- }
- return bare_path;
- }
- #endif // BUILDFLAG(IS_CHROMEOS_ASH)
- void InitChromeLogging(const base::CommandLine& command_line,
- OldFileDeletionState delete_old_log_file) {
- DCHECK(!chrome_logging_initialized_) <<
- "Attempted to initialize logging when it was already initialized.";
- LoggingDestination logging_dest = DetermineLoggingDestination(command_line);
- LogLockingState log_locking_state = LOCK_LOG_FILE;
- base::FilePath log_path;
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- base::FilePath target_path;
- #endif
- // Don't resolve the log path unless we need to. Otherwise we leave an open
- // ALPC handle after sandbox lockdown on Windows.
- if ((logging_dest & LOG_TO_FILE) != 0) {
- log_path = GetLogFileName(command_line);
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- // For BWSI (Incognito) logins, we want to put the logs in the user
- // profile directory that is created for the temporary session instead
- // of in the system log directory, for privacy reasons.
- if (command_line.HasSwitch(ash::switches::kGuestSession))
- log_path = GetSessionLogFile(command_line);
- // Prepares a log file. We rotate the previous log file and prepare a new
- // log file if we've been asked to delete the old log, since that
- // indicates the start of a new session.
- target_path =
- SetUpLogFile(log_path, delete_old_log_file == DELETE_OLD_LOG_FILE);
- // Because ChromeOS manages the move to a new session by redirecting
- // the link, it shouldn't remove the old file in the logging code,
- // since that will remove the newly created link instead.
- delete_old_log_file = APPEND_TO_OLD_LOG_FILE;
- #endif // BUILDFLAG(IS_CHROMEOS_ASH)
- } else {
- log_locking_state = DONT_LOCK_LOG_FILE;
- }
- LoggingSettings settings;
- settings.logging_dest = logging_dest;
- settings.log_file_path = log_path.value().c_str();
- settings.lock_log = log_locking_state;
- settings.delete_old = delete_old_log_file;
- bool success = InitLogging(settings);
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- if (!success) {
- DPLOG(ERROR) << "Unable to initialize logging to " << log_path.value()
- << " (which should be a link to " << target_path.value() << ")";
- RemoveSymlinkAndLog(log_path, target_path);
- chrome_logging_failed_ = true;
- return;
- }
- #else // BUILDFLAG(IS_CHROMEOS_ASH)
- if (!success) {
- DPLOG(ERROR) << "Unable to initialize logging to " << log_path.value();
- chrome_logging_failed_ = true;
- return;
- }
- #endif // BUILDFLAG(IS_CHROMEOS_ASH)
- // We call running in unattended mode "headless", and allow headless mode to
- // be configured either by the Environment Variable or by the Command Line
- // Switch. This is for automated test purposes.
- std::unique_ptr<base::Environment> env(base::Environment::Create());
- const bool is_headless = env->HasVar(env_vars::kHeadless) ||
- command_line.HasSwitch(switches::kNoErrorDialogs);
- // Show fatal log messages in a dialog in debug builds when not headless.
- if (!is_headless)
- SetShowErrorDialogs(true);
- // we want process and thread IDs because we have a lot of things running
- SetLogItems(true, // enable_process_id
- true, // enable_thread_id
- true, // enable_timestamp
- false); // enable_tickcount
- // Suppress system error dialogs when headless.
- if (is_headless)
- SuppressDialogs();
- // Use a minimum log level if the command line asks for one. Ignore this
- // switch if there's vlog level switch present too (as both of these switches
- // refer to the same underlying log level, and the vlog level switch has
- // already been processed inside InitLogging). If there is neither
- // log level nor vlog level specified, then just leave the default level
- // (INFO).
- if (command_line.HasSwitch(switches::kLoggingLevel) &&
- GetMinLogLevel() >= 0) {
- std::string log_level =
- command_line.GetSwitchValueASCII(switches::kLoggingLevel);
- int level = 0;
- if (base::StringToInt(log_level, &level) && level >= 0 &&
- level < LOGGING_NUM_SEVERITIES) {
- SetMinLogLevel(level);
- } else {
- DLOG(WARNING) << "Bad log level: " << log_level;
- }
- }
- #if BUILDFLAG(IS_WIN)
- // Enable trace control and transport through event tracing for Windows.
- LogEventProvider::Initialize(kChromeTraceProviderName);
- // Enable logging to the Windows Event Log.
- SetEventSource(base::WideToASCII(
- install_static::InstallDetails::Get().install_full_name()),
- BROWSER_CATEGORY, MSG_LOG_MESSAGE);
- #endif
- base::StatisticsRecorder::InitLogOnShutdown();
- chrome_logging_initialized_ = true;
- }
- // This is a no-op, but we'll keep it around in case
- // we need to do more cleanup in the future.
- void CleanupChromeLogging() {
- if (chrome_logging_failed_)
- return; // We failed to initiailize logging, no cleanup.
- DCHECK(chrome_logging_initialized_)
- << "Attempted to clean up logging when it wasn't initialized.";
- CloseLogFile();
- chrome_logging_initialized_ = false;
- chrome_logging_redirected_ = false;
- }
- base::FilePath GetLogFileName(const base::CommandLine& command_line) {
- auto filename = command_line.GetSwitchValueNative(switches::kLogFile);
- if (!filename.empty())
- return base::FilePath(filename);
- std::string env_filename;
- base::Environment::Create()->GetVar(env_vars::kLogFileName, &env_filename);
- if (!env_filename.empty())
- return base::FilePath::FromUTF8Unsafe(env_filename);
- const base::FilePath log_filename(FILE_PATH_LITERAL("chrome_debug.log"));
- base::FilePath log_path;
- if (base::PathService::Get(chrome::DIR_LOGS, &log_path)) {
- log_path = log_path.Append(log_filename);
- return log_path;
- } else {
- // error with path service, just use some default file somewhere
- return log_filename;
- }
- }
- bool DialogsAreSuppressed() {
- return dialogs_are_suppressed_;
- }
- #if BUILDFLAG(IS_CHROMEOS_ASH)
- base::FilePath GenerateTimestampedName(const base::FilePath& base_path,
- base::Time timestamp) {
- base::Time::Exploded time_deets;
- timestamp.UTCExplode(&time_deets);
- std::string suffix = base::StringPrintf("_%02d%02d%02d-%02d%02d%02d",
- time_deets.year,
- time_deets.month,
- time_deets.day_of_month,
- time_deets.hour,
- time_deets.minute,
- time_deets.second);
- return base_path.InsertBeforeExtensionASCII(suffix);
- }
- #endif // BUILDFLAG(IS_CHROMEOS_ASH)
- } // namespace logging
|