// 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(); }