diff options
Diffstat (limited to 'googleurl/base/logging.cc')
-rw-r--r-- | googleurl/base/logging.cc | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/googleurl/base/logging.cc b/googleurl/base/logging.cc new file mode 100644 index 0000000..ab03150 --- /dev/null +++ b/googleurl/base/logging.cc @@ -0,0 +1,380 @@ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <ctime> +#include <iomanip> +#include <cstring> +#include <windows.h> +#include <tchar.h> +#include <algorithm> +#include "base/logging.h" + +namespace logging { + +const char* const log_severity_names[LOG_NUM_SEVERITIES] = { + "INFO", "WARNING", "ERROR", "FATAL" }; + +int min_log_level = 0; +LogLockingState lock_log_file = LOCK_LOG_FILE; +LoggingDestination logging_destination = LOG_ONLY_TO_FILE; + +const int kMaxFilteredLogLevel = LOG_WARNING; +char* log_filter_prefix = NULL; + +// which log file to use? This is initialized by InitLogging or +// will be lazily initialized to the default value when it is +// first needed. +TCHAR log_file_name[MAX_PATH] = { 0 }; + +// this file is lazily opened and the handle may be NULL +HANDLE log_file = NULL; + +// what should be prepended to each message? +bool log_process_id = false; +bool log_thread_id = false; +bool log_timestamp = true; +bool log_tickcount = false; + +// An assert handler override specified by the client to be called instead of +// the debug message dialog. +LogAssertHandlerFunction log_assert_handler = NULL; + +// The critical section is used if log file locking is false. It helps us +// avoid problems with multiple threads writing to the log file at the same +// time. +bool initialized_critical_section = false; +CRITICAL_SECTION log_critical_section; + +// When we don't use a critical section, we are using a global mutex. We +// need to do this because LockFileEx is not thread safe +HANDLE log_mutex = NULL; + +// Called by logging functions to ensure that debug_file is initialized +// and can be used for writing. Returns false if the file could not be +// initialized. debug_file will be NULL in this case. +bool InitializeLogFileHandle() { + if (log_file) + return true; + + if (!log_file_name[0]) { + // nobody has called InitLogging to specify a debug log file, so here we + // initialize the log file name to the default + GetModuleFileName(NULL, log_file_name, MAX_PATH); + TCHAR* last_backslash = _tcsrchr(log_file_name, '\\'); + if (last_backslash) + last_backslash[1] = 0; // name now ends with the backslash + _tcscat_s(log_file_name, _T("debug.log")); + } + + log_file = CreateFile(log_file_name, GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (log_file == INVALID_HANDLE_VALUE || log_file == NULL) { + // try the current directory + log_file = CreateFile(_T(".\\debug.log"), GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (log_file == INVALID_HANDLE_VALUE || log_file == NULL) { + log_file = NULL; + return false; + } + } + SetFilePointer(log_file, 0, 0, FILE_END); + return true; +} + +void InitLogMutex() { + if (!log_mutex) { + // \ is not a legal character in mutex names so we replace \ with / + std::wstring safe_name(log_file_name); + std::replace(safe_name.begin(), safe_name.end(), '\\', '/'); + std::wstring t(L"Global\\"); + t.append(safe_name); + log_mutex = ::CreateMutex(NULL, FALSE, t.c_str()); + } +} + +void InitLogging(const TCHAR* new_log_file, LoggingDestination logging_dest, + LogLockingState lock_log, OldFileDeletionState delete_old) { + if (log_file) { + // calling InitLogging twice or after some log call has already opened the + // default log file will re-initialize to the new options + CloseHandle(log_file); + log_file = NULL; + } + + lock_log_file = lock_log; + logging_destination = logging_dest; + + // ignore file options if logging is only to system + if (logging_destination == LOG_ONLY_TO_SYSTEM_DEBUG_LOG) + return; + + _tcscpy_s(log_file_name, MAX_PATH, new_log_file); + if (delete_old == DELETE_OLD_LOG_FILE) + DeleteFile(log_file_name); + + if (lock_log_file == LOCK_LOG_FILE) { + InitLogMutex(); + } else if (!initialized_critical_section) { + // initialize the critical section + InitializeCriticalSection(&log_critical_section); + initialized_critical_section = true; + } + + InitializeLogFileHandle(); +} + +void SetMinLogLevel(int level) { + min_log_level = level; +} + +void SetLogFilterPrefix(char* filter) { + if (log_filter_prefix) { + delete[] log_filter_prefix; + log_filter_prefix = NULL; + } + + if (filter) { + size_t size = strlen(filter)+1; + log_filter_prefix = new char[size]; + strcpy_s(log_filter_prefix, size, filter); + } +} + +void SetLogItems(bool enable_process_id, bool enable_thread_id, + bool enable_timestamp, bool enable_tickcount) { + log_process_id = enable_process_id; + log_thread_id = enable_thread_id; + log_timestamp = enable_timestamp; + log_tickcount = enable_tickcount; +} + +void SetLogAssertHandler(LogAssertHandlerFunction handler) { + log_assert_handler = handler; +} + +// Displays a message box to the user with the error message in it. For +// Windows programs, it's possible that the message loop is messed up on +// a fatal error, and creating a MessageBox will cause that message loop +// to be run. Instead, we try to spawn another process that displays its +// command line. We look for "Debug Message.exe" in the same directory as +// the application. If it exists, we use it, otherwise, we use a regular +// message box. +void DisplayDebugMessage(const std::string& str) { + if (str.empty()) + return; + + // look for the debug dialog program next to our application + wchar_t prog_name[MAX_PATH]; + GetModuleFileNameW(NULL, prog_name, MAX_PATH); + wchar_t* backslash = wcsrchr(prog_name, '\\'); + if (backslash) + backslash[1] = 0; + wcscat_s(prog_name, MAX_PATH, L"debug_message.exe"); + + // stupid CreateProcess requires a non-const command line and may modify it. + // We also want to use the wide string + int charcount = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0); + if (!charcount) + return; + scoped_array<wchar_t> cmdline(new wchar_t[charcount]); + if (!MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, cmdline.get(), charcount)) + return; + + STARTUPINFO startup_info; + memset(&startup_info, 0, sizeof(startup_info)); + startup_info.cb = sizeof(startup_info); + + PROCESS_INFORMATION process_info; + if (CreateProcessW(prog_name, cmdline.get(), NULL, NULL, false, 0, NULL, + NULL, &startup_info, &process_info)) { + WaitForSingleObject(process_info.hProcess, INFINITE); + CloseHandle(process_info.hThread); + CloseHandle(process_info.hProcess); + } else { + // debug process broken, let's just do a message box + MessageBoxW(NULL, cmdline.get(), L"Fatal error", MB_OK | MB_ICONHAND); + } +} + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity, + int ctr) + : severity_(severity) { + Init(file, line); +} + +LogMessage::LogMessage(const char* file, int line, const CheckOpString& result) + : severity_(LOG_FATAL) { + Init(file, line); + stream_ << "Check failed: " << (*result.str_); +} + +LogMessage::LogMessage(const char* file, int line) + : severity_(LOG_INFO) { + Init(file, line); +} + +LogMessage::LogMessage(const char* file, int line, LogSeverity severity) + : severity_(severity) { + Init(file, line); +} + +// writes the common header info to the stream +void LogMessage::Init(const char* file, int line) { + // log only the filename + const char* last_slash = strrchr(file, '\\'); + if (last_slash) + file = last_slash + 1; + + stream_ << '['; + if (log_process_id) + stream_ << GetCurrentProcessId() << ':'; + if (log_thread_id) + stream_ << GetCurrentThreadId() << ':'; + if (log_timestamp) { + time_t t = time(NULL); + struct tm tm_time; + localtime_s(&tm_time, &t); + stream_ << std::setfill('0') + << std::setw(2) << 1 + tm_time.tm_mon + << std::setw(2) << tm_time.tm_mday + << '/' + << std::setw(2) << tm_time.tm_hour + << std::setw(2) << tm_time.tm_min + << std::setw(2) << tm_time.tm_sec + << ':'; + } + if (log_tickcount) + stream_ << GetTickCount() << ':'; + stream_ << log_severity_names[severity_] << ":" << file << "(" << line << ")] "; + + message_start_ = stream_.pcount(); +} + +LogMessage::~LogMessage() { + if (severity_ < min_log_level) + return; + + std::string str_newline(stream_.str(), stream_.pcount()); + str_newline.append("\r\n"); + + if (log_filter_prefix && severity_ <= kMaxFilteredLogLevel && + str_newline.compare(message_start_, strlen(log_filter_prefix), + log_filter_prefix) != 0) { + goto cleanup; + } + + if (logging_destination != LOG_ONLY_TO_FILE) + OutputDebugStringA(str_newline.c_str()); + + // write to log file + if (logging_destination != LOG_ONLY_TO_SYSTEM_DEBUG_LOG && + InitializeLogFileHandle()) { + // we can have multiple threads and/or processes, so try to prevent them from + // clobbering each other's writes + if (lock_log_file == LOCK_LOG_FILE) { + // Ensure that the mutex is initialized in case the client app did not + // call InitLogging. This is not thread safe. See below + InitLogMutex(); + + DWORD r = ::WaitForSingleObject(log_mutex, INFINITE); + DCHECK(r != WAIT_ABANDONED); + } else { + // use the critical section + if (!initialized_critical_section) { + // The client app did not call InitLogging, and so the critical section + // has not been created. We do this on demand, but if two threads try to + // do this at the same time, there will be a race condition to create + // the critical section. This is why InitLogging should be called from + // the main thread at the beginning of execution. + InitializeCriticalSection(&log_critical_section); + initialized_critical_section = true; + } + EnterCriticalSection(&log_critical_section); + } + + SetFilePointer(log_file, 0, 0, SEEK_END); + DWORD num_written; + WriteFile(log_file, (void*)str_newline.c_str(), (DWORD)str_newline.length(), &num_written, NULL); + + if (lock_log_file == LOCK_LOG_FILE) { + ReleaseMutex(log_mutex); + } else { + LeaveCriticalSection(&log_critical_section); + } + } + + if (severity_ == LOG_FATAL) { + // display a message or break into the debugger on a fatal error + if (::IsDebuggerPresent()) { + DebugBreak(); + } else { + if (log_assert_handler) { + log_assert_handler(std::string(stream_.str(), stream_.pcount())); + } else { + // don't use the string with the newline, get a fresh version to send to + // the debug message process + DisplayDebugMessage(std::string(stream_.str(), stream_.pcount())); + TerminateProcess(GetCurrentProcess(), 1); + } + } + } + +cleanup: + // Calling stream_.str() freezes the stream buffer. A frozen buffer will + // not be freed during strstreambuf destruction. + stream_.freeze(false); +} + +void CloseLogFile() { + if (!log_file) + return; + + CloseHandle(log_file); + log_file = NULL; +} + +} // namespace logging + +std::ostream& operator<<(std::ostream& out, const wchar_t* wstr) { + if (!wstr || !wstr[0]) + return out; + + // compute the length of the buffer we'll need + int charcount = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, + NULL, 0, NULL, NULL); + if (charcount == 0) + return out; + + // convert + scoped_array<char> buf(new char[charcount]); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, buf.get(), charcount, NULL, NULL); + return out << buf.get(); +} |