logging_chrome.cc revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "build/build_config.h"
6
7// Need to include this before most other files because it defines
8// IPC_MESSAGE_LOG_ENABLED. We need to use it to define
9// IPC_MESSAGE_MACROS_LOG_ENABLED so render_messages.h will generate the
10// ViewMsgLog et al. functions.
11#include "ipc/ipc_message.h"
12
13// On Windows, the about:ipc dialog shows IPCs; on POSIX, we hook up a
14// logger in this file.  (We implement about:ipc on Mac but implement
15// the loggers here anyway).  We need to do this real early to be sure
16// IPC_MESSAGE_MACROS_LOG_ENABLED doesn't get undefined.
17#if defined(OS_POSIX) && defined(IPC_MESSAGE_LOG_ENABLED)
18#define IPC_MESSAGE_MACROS_LOG_ENABLED
19#include "chrome/common/devtools_messages.h"
20#include "chrome/common/plugin_messages.h"
21#include "chrome/common/render_messages.h"
22#include "chrome/common/worker_messages.h"
23#endif
24
25#if defined(OS_WIN)
26#include <windows.h>
27#endif
28
29#include <fstream>
30
31#include "chrome/common/logging_chrome.h"
32
33#include "base/command_line.h"
34#include "base/compiler_specific.h"
35#include "base/debug/debugger.h"
36#include "base/environment.h"
37#include "base/file_path.h"
38#include "base/file_util.h"
39#include "base/logging.h"
40#include "base/path_service.h"
41#include "base/string_number_conversions.h"
42#include "base/string_util.h"
43#include "base/time.h"
44#include "base/utf_string_conversions.h"
45#include "chrome/common/chrome_paths.h"
46#include "chrome/common/chrome_switches.h"
47#include "chrome/common/env_vars.h"
48#include "ipc/ipc_logging.h"
49#if defined(OS_WIN)
50#include "base/logging_win.h"
51#include <initguid.h>
52#endif
53
54// When true, this means that error dialogs should not be shown.
55static bool dialogs_are_suppressed_ = false;
56
57// This should be true for exactly the period between the end of
58// InitChromeLogging() and the beginning of CleanupChromeLogging().
59static bool chrome_logging_initialized_ = false;
60
61// This should be true for exactly the period between the end of
62// InitChromeLogging() and the beginning of CleanupChromeLogging().
63static bool chrome_logging_redirected_ = false;
64
65#if defined(OS_WIN)
66// {7FE69228-633E-4f06-80C1-527FEA23E3A7}
67static const GUID kChromeTraceProviderName = {
68    0x7fe69228, 0x633e, 0x4f06,
69        { 0x80, 0xc1, 0x52, 0x7f, 0xea, 0x23, 0xe3, 0xa7 } };
70#endif
71
72// Assertion handler for logging errors that occur when dialogs are
73// silenced.  To record a new error, pass the log string associated
74// with that error in the str parameter.
75MSVC_DISABLE_OPTIMIZE();
76static void SilentRuntimeAssertHandler(const std::string& str) {
77  base::debug::BreakDebugger();
78}
79static void SilentRuntimeReportHandler(const std::string& str) {
80}
81MSVC_ENABLE_OPTIMIZE();
82
83// Suppresses error/assertion dialogs and enables the logging of
84// those errors into silenced_errors_.
85static void SuppressDialogs() {
86  if (dialogs_are_suppressed_)
87    return;
88
89  logging::SetLogAssertHandler(SilentRuntimeAssertHandler);
90  logging::SetLogReportHandler(SilentRuntimeReportHandler);
91
92#if defined(OS_WIN)
93  UINT new_flags = SEM_FAILCRITICALERRORS |
94                   SEM_NOGPFAULTERRORBOX |
95                   SEM_NOOPENFILEERRORBOX;
96
97  // Preserve existing error mode, as discussed at http://t/dmea
98  UINT existing_flags = SetErrorMode(new_flags);
99  SetErrorMode(existing_flags | new_flags);
100#endif
101
102  dialogs_are_suppressed_ = true;
103}
104
105namespace logging {
106
107LoggingDestination DetermineLogMode(const CommandLine& command_line) {
108  // only use OutputDebugString in debug mode
109#ifdef NDEBUG
110  bool enable_logging = false;
111  const char *kInvertLoggingSwitch = switches::kEnableLogging;
112  const logging::LoggingDestination kDefaultLoggingMode =
113      logging::LOG_ONLY_TO_FILE;
114#else
115  bool enable_logging = true;
116  const char *kInvertLoggingSwitch = switches::kDisableLogging;
117  const logging::LoggingDestination kDefaultLoggingMode =
118      logging::LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG;
119#endif
120
121  if (command_line.HasSwitch(kInvertLoggingSwitch))
122    enable_logging = !enable_logging;
123
124  logging::LoggingDestination log_mode;
125  if (enable_logging) {
126    // Let --enable-logging=stderr force only stderr, particularly useful for
127    // non-debug builds where otherwise you can't get logs to stderr at all.
128    if (command_line.GetSwitchValueASCII(switches::kEnableLogging) == "stderr")
129      log_mode = logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG;
130    else
131      log_mode = kDefaultLoggingMode;
132  } else {
133    log_mode = logging::LOG_NONE;
134  }
135  return log_mode;
136}
137
138#if defined(OS_CHROMEOS)
139namespace {
140FilePath GenerateTimestampedName(const FilePath& base_path,
141                                 base::Time timestamp) {
142  base::Time::Exploded time_deets;
143  timestamp.LocalExplode(&time_deets);
144  std::string suffix = base::StringPrintf("_%02d%02d%02d-%02d%02d%02d",
145                                          time_deets.year,
146                                          time_deets.month,
147                                          time_deets.day_of_month,
148                                          time_deets.hour,
149                                          time_deets.minute,
150                                          time_deets.second);
151  return base_path.InsertBeforeExtension(suffix);
152}
153
154FilePath SetUpSymlinkIfNeeded(const FilePath& symlink_path, bool new_log) {
155  DCHECK(!symlink_path.empty());
156
157  // If not starting a new log, then just log through the existing
158  // symlink, but if the symlink doesn't exist, create it.  If
159  // starting a new log, then delete the old symlink and make a new
160  // one to a fresh log file.
161  FilePath target_path;
162  bool symlink_exists = file_util::PathExists(symlink_path);
163  if (new_log || !symlink_exists) {
164    target_path = GenerateTimestampedName(symlink_path, base::Time::Now());
165
166    // We don't care if the unlink fails; we're going to continue anyway.
167    if (::unlink(symlink_path.value().c_str()) == -1) {
168      if (symlink_exists) // only warn if we might expect it to succeed.
169        PLOG(WARNING) << "Unable to unlink " << symlink_path.value();
170    }
171    if (!file_util::CreateSymbolicLink(target_path, symlink_path)) {
172      PLOG(ERROR) << "Unable to create symlink " << symlink_path.value()
173                  << " pointing at " << target_path.value();
174    }
175  } else {
176    if (!file_util::ReadSymbolicLink(symlink_path, &target_path))
177      PLOG(ERROR) << "Unable to read symlink " << symlink_path.value();
178  }
179  return target_path;
180}
181
182void RemoveSymlinkAndLog(const FilePath& link_path,
183                         const FilePath& target_path) {
184  if (::unlink(link_path.value().c_str()) == -1)
185    PLOG(WARNING) << "Unable to unlink symlink " << link_path.value();
186  if (::unlink(target_path.value().c_str()) == -1)
187    PLOG(WARNING) << "Unable to unlink log file " << target_path.value();
188}
189
190}  // anonymous namespace
191
192FilePath GetSessionLogFile(const CommandLine& command_line) {
193  FilePath log_dir;
194  std::string log_dir_str;
195  scoped_ptr<base::Environment> env(base::Environment::Create());
196  if (env->GetVar(env_vars::kSessionLogDir, &log_dir_str) &&
197      !log_dir_str.empty()) {
198    log_dir = FilePath(log_dir_str);
199  } else {
200    PathService::Get(chrome::DIR_USER_DATA, &log_dir);
201    FilePath login_profile =
202        command_line.GetSwitchValuePath(switches::kLoginProfile);
203    log_dir = log_dir.Append(login_profile);
204  }
205  return log_dir.Append(GetLogFileName().BaseName());
206}
207
208void RedirectChromeLogging(const CommandLine& command_line) {
209  DCHECK(!chrome_logging_redirected_) <<
210    "Attempted to redirect logging when it was already initialized.";
211
212  // Redirect logs to the session log directory, if set.  Otherwise
213  // defaults to the profile dir.
214  FilePath log_path = GetSessionLogFile(command_line);
215
216  // Always force a new symlink when redirecting.
217  FilePath target_path = SetUpSymlinkIfNeeded(log_path, true);
218
219  // ChromeOS always logs through the symlink, so it shouldn't be
220  // deleted if it already exists.
221  if (!InitLogging(log_path.value().c_str(),
222                   DetermineLogMode(command_line),
223                   logging::LOCK_LOG_FILE,
224                   logging::APPEND_TO_OLD_LOG_FILE)) {
225    LOG(ERROR) << "Unable to initialize logging to " << log_path.value();
226    RemoveSymlinkAndLog(log_path, target_path);
227  } else {
228    chrome_logging_redirected_ = true;
229  }
230}
231
232
233#endif
234
235void InitChromeLogging(const CommandLine& command_line,
236                       OldFileDeletionState delete_old_log_file) {
237  DCHECK(!chrome_logging_initialized_) <<
238    "Attempted to initialize logging when it was already initialized.";
239
240#if defined(OS_POSIX) && defined(IPC_MESSAGE_LOG_ENABLED)
241  IPC::Logging::set_log_function_map(&g_log_function_mapping);
242#endif
243
244  FilePath log_path = GetLogFileName();
245
246#if defined(OS_CHROMEOS)
247  // For BWSI (Incognito) logins, we want to put the logs in the user
248  // profile directory that is created for the temporary session instead
249  // of in the system log directory, for privacy reasons.
250  if (command_line.HasSwitch(switches::kGuestSession))
251    log_path = GetSessionLogFile(command_line);
252
253  // On ChromeOS we log to the symlink.  We force creation of a new
254  // symlink if we've been asked to delete the old log, since that
255  // indicates the start of a new session.
256  FilePath target_path = SetUpSymlinkIfNeeded(
257      log_path, delete_old_log_file == logging::DELETE_OLD_LOG_FILE);
258
259  // Because ChromeOS manages the move to a new session by redirecting
260  // the link, it shouldn't remove the old file in the logging code,
261  // since that will remove the newly created link instead.
262  delete_old_log_file = logging::APPEND_TO_OLD_LOG_FILE;
263#endif
264
265  bool success = InitLogging(log_path.value().c_str(),
266                             DetermineLogMode(command_line),
267                             logging::LOCK_LOG_FILE,
268                             delete_old_log_file);
269
270#if defined(OS_CHROMEOS)
271  if (!success) {
272    PLOG(ERROR) << "Unable to initialize logging to " << log_path.value()
273                << " (which should be a link to " << target_path.value() << ")";
274    RemoveSymlinkAndLog(log_path, target_path);
275    return;
276  }
277#else
278  if (!success) {
279    PLOG(ERROR) << "Unable to initialize logging to " << log_path.value();
280    return;
281  }
282#endif
283
284  // Default to showing error dialogs.
285  if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoErrorDialogs))
286    logging::SetShowErrorDialogs(true);
287
288  // we want process and thread IDs because we have a lot of things running
289  logging::SetLogItems(true,  // enable_process_id
290                       true,  // enable_thread_id
291                       false, // enable_timestamp
292                       true); // enable_tickcount
293
294  // We call running in unattended mode "headless", and allow
295  // headless mode to be configured either by the Environment
296  // Variable or by the Command Line Switch.  This is for
297  // automated test purposes.
298  scoped_ptr<base::Environment> env(base::Environment::Create());
299  if (env->HasVar(env_vars::kHeadless) ||
300      command_line.HasSwitch(switches::kNoErrorDialogs))
301    SuppressDialogs();
302
303  // Use a minimum log level if the command line asks for one,
304  // otherwise leave it at the default level (INFO).
305  if (command_line.HasSwitch(switches::kLoggingLevel)) {
306    std::string log_level = command_line.GetSwitchValueASCII(
307        switches::kLoggingLevel);
308    int level = 0;
309    if (base::StringToInt(log_level, &level) &&
310        level >= 0 && level < LOG_NUM_SEVERITIES) {
311      logging::SetMinLogLevel(level);
312    } else {
313      LOG(WARNING) << "Bad log level: " << log_level;
314    }
315  }
316
317#if defined(OS_WIN)
318  // Enable trace control and transport through event tracing for Windows.
319  if (env->HasVar(env_vars::kEtwLogging))
320    logging::LogEventProvider::Initialize(kChromeTraceProviderName);
321#endif
322
323  chrome_logging_initialized_ = true;
324}
325
326// This is a no-op, but we'll keep it around in case
327// we need to do more cleanup in the future.
328void CleanupChromeLogging() {
329  DCHECK(chrome_logging_initialized_) <<
330    "Attempted to clean up logging when it wasn't initialized.";
331
332  CloseLogFile();
333
334  chrome_logging_initialized_ = false;
335  chrome_logging_redirected_ = false;
336}
337
338FilePath GetLogFileName() {
339  std::string filename;
340  scoped_ptr<base::Environment> env(base::Environment::Create());
341  if (env->GetVar(env_vars::kLogFileName, &filename) && !filename.empty()) {
342#if defined(OS_WIN)
343    return FilePath(UTF8ToWide(filename).c_str());
344#elif defined(OS_POSIX)
345    return FilePath(filename.c_str());
346#endif
347  }
348
349  const FilePath log_filename(FILE_PATH_LITERAL("chrome_debug.log"));
350  FilePath log_path;
351
352  if (PathService::Get(chrome::DIR_LOGS, &log_path)) {
353    log_path = log_path.Append(log_filename);
354    return log_path;
355  } else {
356    // error with path service, just use some default file somewhere
357    return log_filename;
358  }
359}
360
361bool DialogsAreSuppressed() {
362  return dialogs_are_suppressed_;
363}
364
365size_t GetFatalAssertions(AssertionList* assertions) {
366  // In this function, we don't assume that assertions is non-null, so
367  // that if you just want an assertion count, you can pass in NULL.
368  if (assertions)
369    assertions->clear();
370  size_t assertion_count = 0;
371
372  std::ifstream log_file;
373  log_file.open(GetLogFileName().value().c_str());
374  if (!log_file.is_open())
375    return 0;
376
377  std::string utf8_line;
378  std::wstring wide_line;
379  while (!log_file.eof()) {
380    getline(log_file, utf8_line);
381    if (utf8_line.find(":FATAL:") != std::string::npos) {
382      wide_line = UTF8ToWide(utf8_line);
383      if (assertions)
384        assertions->push_back(wide_line);
385      ++assertion_count;
386    }
387  }
388  log_file.close();
389
390  return assertion_count;
391}
392
393}  // namespace logging
394