Index: base/logging.cc |
diff --git a/base/logging.cc b/base/logging.cc |
deleted file mode 100644 |
index 928551094087668f78e3143e2bf1a8f058c7872a..0000000000000000000000000000000000000000 |
--- a/base/logging.cc |
+++ /dev/null |
@@ -1,1369 +0,0 @@ |
-// Copyright 2003-2009 Google Inc. |
-// |
-// Licensed under the Apache License, Version 2.0 (the "License"); |
-// you may not use this file except in compliance with the License. |
-// You may obtain a copy of the License at |
-// |
-// http://www.apache.org/licenses/LICENSE-2.0 |
-// |
-// Unless required by applicable law or agreed to in writing, software |
-// distributed under the License is distributed on an "AS IS" BASIS, |
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
-// See the License for the specific language governing permissions and |
-// limitations under the License. |
-// ======================================================================== |
-// |
-// Tracing and logging system. |
-// |
-// The log output goes to "All Users/Application Data/Google/Update/Log/". |
-// The log configuration file is under |
-// "%Program Files%/C:\Program Files\Google\Common\Update". |
-// By default, the logging is on in debug modes and off in opt mode although in |
-// opt mode only the OPT_LOG statements write to the log. |
-// |
-// The log is open to all the users to write to. There is a known vulnerability |
-// where a DOS can be created by holding on to the logging mutex. |
-// |
-// In this module use of ASSERT & REPORT is banned. This is to prevent any |
-// possible recursion issues between logging (logging.h) and |
-// asserting/reporting (debug.h). Both are basement-level systems that need to |
-// work when almost nothing else works and interdependencies are best avoided. |
-// One unavoidable interdependency is that debugASSERT will send messages to |
-// the logger via Logger::OutputMessage - which then broadcasts it to each |
-// LogWriter's OutputMessage. So these methods should be as simple as |
-// possible. (Also, unlike asserting/reporting - this module will not avoid |
-// use of the heap, however the code executed from Logger::OutputMessage |
-// doesn't use the heap (for all the LogWriters in this file).) |
-// |
-// TODO(omaha): implement the minidump handling in terms of breakpad. |
-// Log initialization if full of lazy init. Consider doing |
-// eager init of log and its registered log writers when the |
-// log is created and initialized. |
-// Reimplement in terms of smart handles and locks. |
-// Reimplement without dependency on any other compilation unit |
-// that can call assert, verify, or the log itself. |
-// Redo the history logging feature |
- |
-#include "omaha/base/logging.h" |
- |
-#include <excpt.h> // Microsoft specific: structured exceptions. |
-#include <shlobj.h> |
-#include <shlwapi.h> |
-#include <string.h> |
-#include <atlpath.h> |
-#include <atlsecurity.h> |
-#include "base/basictypes.h" |
-#include "omaha/base/app_util.h" |
-#include "omaha/base/const_debug.h" |
-#include "omaha/base/constants.h" |
-#include "omaha/base/debug.h" |
-#include "omaha/base/etw_log_writer.h" |
-#include "omaha/base/file.h" |
-#include "omaha/base/path.h" |
-#include "omaha/base/string.h" |
-#include "omaha/base/time.h" |
-#include "omaha/base/utils.h" |
- |
-namespace omaha { |
- |
-// enforce ban on ASSERT/REPORT |
-#undef ASSERT |
-#undef REPORT |
- |
-#ifdef LOGGING |
- |
-#define kNumLockRetries (20) |
-#define kLockRetryDelayMs (50) |
- |
-// Circular buffer to log history. |
-static wchar_t history_buffer[kMaxHistoryBufferSize]; |
- |
-// Index into the history buffer to begin writing at. |
-static int history_buffer_next_idx = 0; |
- |
-// Indicates whether the history buffer has ever reached its full capacity. |
-// Once this boolean is true, if never becomes false. |
-static bool history_buffer_full = false; |
- |
-// |
-// Table of category names to categories. |
-// |
-#define LC_ENTRY(lc_value) (L#lc_value), (lc_value) |
-struct { |
- wchar_t* category_name; |
- LogCategory category; |
-} static LogCategoryNames[] = { |
- LC_ENTRY(LC_UTIL), |
- LC_ENTRY(LC_SETUP), |
- LC_ENTRY(LC_SHELL), |
- LC_ENTRY(LC_CORE), |
- LC_ENTRY(LC_JS), |
- LC_ENTRY(LC_PLUGIN), |
- LC_ENTRY(LC_SERVICE), |
- LC_ENTRY(LC_OPT), |
- LC_ENTRY(LC_NET), |
- LC_ENTRY(LC_REPORT), |
-}; |
- |
-COMPILE_ASSERT(arraysize(LogCategoryNames) == LC_MAX_CAT - 1, |
- LogCategoryNames_missing_category); |
- |
-static CString StripBackslashes(const CString& name) { |
- int n = String_FindChar(name, L'\\'); |
- if (n == -1) { |
- return name; |
- } else { |
- CString result; |
- for (int i = 0; i < name.GetLength(); ++i) { |
- if (name[i] != L'\\') { |
- result += name[i]; |
- } |
- } |
- return result; |
- } |
-} |
- |
-static CString GetProcName() { |
- CString proc_name(app_util::GetAppNameWithoutExtension()); |
- CString module_name(app_util::GetCurrentModuleNameWithoutExtension()); |
- CString result(proc_name); |
- if (module_name.CompareNoCase(proc_name) != 0) { |
- result += L":"; |
- result += module_name; |
- } |
- return result; |
-} |
- |
-// Formats a line prefix with the current time min:sec:milisec if wanted, |
-// otherwise just the process:module. |
-static void FormatLinePrefix(bool show_time, |
- const wchar_t* proc_name, |
- CString& result) { |
- if (show_time) { |
- SYSTEMTIME system_time = {0}; |
- GetLocalTime(&system_time); |
- result.Format(L"[%02d/%02d/%02d %02d:%02d:%02d.%03d]", |
- system_time.wMonth, system_time.wDay, system_time.wYear % 100, |
- system_time.wHour, system_time.wMinute, system_time.wSecond, |
- system_time.wMilliseconds); |
- } |
- result.AppendFormat(L"[%s][%u:%u]", |
- proc_name, |
- ::GetCurrentProcessId(), |
- ::GetCurrentThreadId()); |
-} |
- |
-static bool g_logging_valid = false; |
-static Logging g_logging; |
- |
-// Singleton factory for the Logging object. |
-Logging* GetLogging() { |
- return g_logging_valid ? &g_logging : NULL; |
-} |
- |
-// Force the logging system to be initialized during elaboration of static |
-// constructors, while the program is still single-threaded. This global |
-// static object will cause the static logger inside of GetLogging() to be |
-// constructed. However, it might not be the first call to GetLogging() - |
-// another static object in another object file might get constructed first, |
-// and in its constructor call a logging API. Just as long as it is done when |
-// the system is single threaded. (Reason: because the Logger object has a |
-// LLock object which has a Win32 critical section which needs to be |
-// initialized - only once!) |
- |
- |
-Logging::Logging() |
- : logging_initialized_(false), |
- logging_enabled_(true), |
- force_show_time_(false), |
- show_time_(true), |
- log_to_file_(true), |
- log_to_debug_out_(true), |
- append_to_file_(true), |
- logging_shutdown_(false), |
- num_writers_(0), |
- file_log_writer_(NULL), |
- debug_out_writer_(NULL), |
- etw_log_writer_(NULL), |
- is_initializing_(false), |
- log_file_name_(kDefaultLogFileName), |
- config_file_path_(GetConfigurationFilePath()) { |
- g_last_category_check_time = 0; |
- for (int i = 0; i < max_writers; ++i) { |
- writers_[i] = NULL; |
- } |
- proc_name_ = GetProcName(); |
- g_logging_valid = true; |
- |
- // Read initial settings from the config file. |
- ReadLoggingSettings(); |
-} |
- |
-// TODO(omaha): why aren't we using a mutexscope and what if an the code |
-// throws? Will the lock be unlocked? |
-Logging::~Logging() { |
- // Acquire the lock outside the try/except block so we'll always release it |
- lock_.Lock(); |
- |
- __try { |
- // prevent further access to the system |
- // necessary because the static destructors happen |
- // in a non-deterministic order |
- logging_shutdown_ = true; |
- |
- // Delete all registered LogWriters |
- for (int i = 0; i < num_writers_; ++i) { |
- if (writers_[i]) { |
- delete writers_[i]; |
- } |
- } |
- |
- logging_initialized_ = false; |
- } __except(SehNoMinidump(GetExceptionCode(), |
- GetExceptionInformation(), |
- __FILE__, |
- __LINE__, |
- true)) { |
- OutputDebugStringA("Unexpected exception in: " __FUNCTION__ "\r\n"); |
- logging_initialized_ = false; |
- } |
- |
- g_logging_valid = false; |
- lock_.Unlock(); |
-} |
- |
-void Logging::UpdateCatAndLevel(const wchar_t* cat_name, LogCategory cat) { |
- if (cat_name == NULL) { |
- return; |
- } |
- if (cat >= LC_MAX_CAT) { |
- return; |
- } |
- int log_level = kDefaultLogLevel; |
- CString config_file = GetCurrentConfigurationFilePath(); |
- if (!config_file.IsEmpty()) { |
- log_level = GetPrivateProfileInt(kConfigSectionLoggingLevel, |
- cat_name, |
- kDefaultLogLevel, |
- config_file); |
- } |
- category_list_[cat].enabled = (log_level != 0); |
- category_list_[cat].log_level = static_cast<LogLevel>(log_level); |
-} |
- |
-void Logging::ReadLoggingSettings() { |
- CString config_file = GetCurrentConfigurationFilePath(); |
- if (!config_file.IsEmpty()) { |
- logging_enabled_ = ::GetPrivateProfileInt( |
- kConfigSectionLoggingSettings, |
- kConfigAttrEnableLogging, |
- kDefaultLoggingEnabled, |
- config_file) == 0 ? false : true; |
- |
- show_time_ = ::GetPrivateProfileInt( |
- kConfigSectionLoggingSettings, |
- kConfigAttrShowTime, |
- kDefaultShowTime, |
- config_file) == 0 ? false : true; |
- |
- log_to_file_ = ::GetPrivateProfileInt( |
- kConfigSectionLoggingSettings, |
- kConfigAttrLogToFile, |
- kDefaultLogToFile, |
- config_file) == 0 ? false : true; |
- |
- log_to_debug_out_ = ::GetPrivateProfileInt( |
- kConfigSectionLoggingSettings, |
- kConfigAttrLogToOutputDebug, |
- kDefaultLogToOutputDebug, |
- config_file) == 0 ? false : true; |
- |
- append_to_file_ = ::GetPrivateProfileInt( |
- kConfigSectionLoggingSettings, |
- kConfigAttrAppendToFile, |
- kDefaultAppendToFile, |
- config_file) == 0 ? false : true; |
- |
- ::GetPrivateProfileString(kConfigSectionLoggingSettings, |
- kConfigAttrLogFilePath, |
- kDefaultLogFileName, |
- CStrBuf(log_file_name_, MAX_PATH), |
- MAX_PATH, |
- config_file); |
- } else { |
- logging_enabled_ = kDefaultLoggingEnabled; |
- show_time_ = kDefaultShowTime; |
- log_to_file_ = kDefaultLogToFile; |
- log_to_debug_out_ = kDefaultLogToOutputDebug; |
- append_to_file_ = kDefaultAppendToFile; |
- log_file_name_ = kDefaultLogFileName; |
- } |
- |
- if (force_show_time_) { |
- show_time_ = true; |
- } |
- |
- // The "default" category is always enabled. |
- category_list_[LC_LOGGING].enabled = true; |
- category_list_[LC_LOGGING].log_level = LEVEL_ALL; |
- |
- // Read each category from the ini file. |
- for (size_t i = 0; i < arraysize(LogCategoryNames); ++i) { |
- UpdateCatAndLevel(LogCategoryNames[i].category_name, |
- LogCategoryNames[i].category); |
- } |
- |
- g_last_category_check_time = GetCurrent100NSTime(); |
-} |
- |
-CString Logging::GetDefaultLogDirectory() const { |
- CString path; |
- CStrBuf buf(path, MAX_PATH); |
- HRESULT hr = ::SHGetFolderPath(NULL, |
- CSIDL_COMMON_APPDATA, |
- NULL, |
- SHGFP_TYPE_CURRENT, |
- buf); |
- if (FAILED(hr)) { |
- return L""; |
- } |
- if (!::PathAppend(buf, OMAHA_REL_LOG_DIR)) { |
- return L""; |
- } |
- return path; |
-} |
- |
-CString Logging::GetLogFilePath() const { |
- if (log_file_name_.IsEmpty()) { |
- return CString(); |
- } |
- |
- if (!ATLPath::IsRelative(log_file_name_)) { |
- return log_file_name_; |
- } |
- |
- CString path = GetDefaultLogDirectory(); |
- if (path.IsEmpty()) { |
- return CString(); |
- } |
- |
- if (!::PathAppend(CStrBuf(path, MAX_PATH), log_file_name_)) { |
- return CString(); |
- } |
- |
- return path; |
-} |
- |
-void Logging::ConfigureETWLogWriter() { |
- // Always create the ETW log writer, as its log level is controlled |
- // at runtime through Event Tracing for Windows. |
- if (etw_log_writer_ == NULL) { |
- etw_log_writer_ = EtwLogWriter::Create(); |
- if (debug_out_writer_ == NULL) { |
- OutputDebugString(SPRINTF(L"LOG_SYSTEM: [%s]: ERROR - " |
- L"Cannot create ETW log writer", |
- proc_name_)); |
- } |
- } |
- |
- if (etw_log_writer_ != NULL) { |
- InternalRegisterWriter(etw_log_writer_); |
- } |
-} |
- |
-void Logging::ConfigureFileLogWriter() { |
- if (!log_to_file_) { |
- return; |
- } |
- |
- // Create the logging file. |
- if (file_log_writer_ == NULL) { |
- CString path = GetLogFilePath(); |
- if (path.IsEmpty()) { |
- return; |
- } |
- |
- // Extract the final target directory which will not be what |
- // GetDefaultLogDirectory() returns if log_file_name_ is an absolute path. |
- CString log_file_dir = GetDirectoryFromPath(path); |
- if (!File::Exists(log_file_dir)) { |
- if (FAILED(CreateDir(log_file_dir, NULL))) { |
- return; |
- } |
- } |
- file_log_writer_ = FileLogWriter::Create(path, append_to_file_); |
- if (file_log_writer_ == NULL) { |
- OutputDebugString(SPRINTF(L"LOG_SYSTEM: [%s]: ERROR - " |
- L"Cannot create log writer to %s", |
- proc_name_, path)); |
- } |
- } |
- |
- if (file_log_writer_ != NULL) { |
- InternalRegisterWriter(file_log_writer_); |
- } |
-} |
- |
-void Logging::ConfigureDebugOutLogWriter() { |
- if (!log_to_debug_out_) { |
- return; |
- } |
- |
- if (debug_out_writer_ == NULL) { |
- debug_out_writer_ = OutputDebugStringLogWriter::Create(); |
- if (debug_out_writer_ == NULL) { |
- OutputDebugString(SPRINTF(L"LOG_SYSTEM: [%s]: ERROR - " |
- L"Cannot create OutputDebugString log writer", |
- proc_name_)); |
- } |
- } |
- |
- if (debug_out_writer_ != NULL) { |
- InternalRegisterWriter(debug_out_writer_); |
- } |
-} |
- |
-// Configures/unconfigures the log writers for the current settings. |
-bool Logging::ConfigureLogging() { |
- ConfigureETWLogWriter(); |
- ConfigureFileLogWriter(); |
- ConfigureDebugOutLogWriter(); |
- |
- return num_writers_ > 0; |
-} |
- |
-void Logging::UnconfigureLogging() { |
- if (etw_log_writer_ != NULL) { |
- InternalUnregisterWriter(etw_log_writer_); |
- } |
- if (file_log_writer_ != NULL) { |
- InternalUnregisterWriter(file_log_writer_); |
- } |
- if (debug_out_writer_ != NULL) { |
- InternalUnregisterWriter(debug_out_writer_); |
- } |
-} |
- |
-bool Logging::InternalInitialize() { |
- __try { |
- if (logging_shutdown_ == true) { |
- OutputDebugString(SPRINTF(L"LOG_SYSTEM: [%s]: ERROR - " |
- L"Calling the logging system after " |
- L"it has been shut down \n", |
- GetProcName())); |
- return false; |
- } |
- |
- if (logging_initialized_ == true) { |
- return true; |
- } |
- // If something called by this method is attempting to do logging, |
- // just ignore it. The cost/benefit ratio is too high to do otherwise. |
- if (is_initializing_) { |
- return false; |
- } |
- is_initializing_ = true; |
- |
- // Read the initial settings from the config file. |
- ReadLoggingSettings(); |
- |
- // Initialize logging system if enabled at start. |
- if (logging_enabled_) { |
- logging_initialized_ = ConfigureLogging(); |
- } |
- } __except(SehNoMinidump(GetExceptionCode(), |
- GetExceptionInformation(), |
- __FILE__, |
- __LINE__, |
- true)) { |
- OutputDebugStringA("Unexpected exception in: " __FUNCTION__ "\r\n"); |
- logging_initialized_ = false; |
- return false; |
- } |
- |
- is_initializing_ = false; |
- return true; |
-} |
- |
-bool Logging::InitializeLogging() { |
- // Double-checked locking idiom is broken, especially on multicore machines. |
- // TODO(omaha): understand how this works and fix it. |
- if (logging_shutdown_ == true) { |
- OutputDebugString(SPRINTF(L"LOG_SYSTEM: [%s]: ERROR - Calling the logging " |
- L"system after it has been shut down \n", |
- GetProcName())); |
- return false; |
- } |
- |
- if (logging_initialized_ == true) { |
- return true; |
- } |
- |
- // Acquire the lock outside the try/except block so we'll always release it. |
- __mutexScope(lock_); |
- return InternalInitialize(); |
-} |
- |
-// Enables/disables the logging mechanism. Allows turning logging on/off |
-// in mid-run. |
-// TODO(omaha): same comment as for the destructor. |
-void Logging::EnableLogging() { |
- if (!InitializeLogging()) { |
- return; |
- } |
- |
- // Acquire the lock outside the try/except block so we'll always release it. |
- lock_.Lock(); |
- |
- __try { |
- if (!logging_enabled_) { |
- ConfigureLogging(); |
- logging_enabled_ = true; |
- } |
- } __except(SehNoMinidump(GetExceptionCode(), |
- GetExceptionInformation(), |
- __FILE__, |
- __LINE__, |
- true)) { |
- OutputDebugStringA("Unexpected exception in: " __FUNCTION__ "\r\n"); |
- logging_enabled_ = false; |
- } |
- |
- lock_.Unlock(); |
-} |
- |
-void Logging::DisableLogging() { |
- if (!InitializeLogging()) { |
- return; |
- } |
- |
- // Acquire the lock outside the try/except block so we'll always release it. |
- lock_.Lock(); |
- |
- __try { |
- if (logging_enabled_) { |
- logging_enabled_ = false; |
- UnconfigureLogging(); |
- } |
- } __except(SehNoMinidump(GetExceptionCode(), |
- GetExceptionInformation(), |
- __FILE__, |
- __LINE__, |
- true)) { |
- OutputDebugStringA("Unexpected exception in: " __FUNCTION__ "\r\n"); |
- logging_enabled_ = false; |
- } |
- |
- lock_.Unlock(); |
-} |
- |
-// Checks if logging is enabled - and updates logging settings from the |
-// configuration file every kLogSettingsCheckInterval seconds. |
-bool Logging::IsLoggingEnabled() { |
- if (!InitializeLogging()) { |
- return false; |
- } |
- |
- // Dynamic update - including reading a new value of logging_enabled_. |
- bool prev_logging_enabled = logging_enabled_; |
- if (GetCurrent100NSTime() > |
- g_last_category_check_time + kLogSettingsCheckInterval) { |
- ReadLoggingSettings(); |
- } |
- |
- // If enabled state has changed either enable or disable logging. |
- if (prev_logging_enabled != logging_enabled_) { |
- if (logging_enabled_) { |
- EnableLogging(); |
- } else { |
- DisableLogging(); |
- } |
- } |
- |
- return logging_enabled_; |
-} |
- |
-bool Logging::IsLoggingAlreadyEnabled() const { |
- return logging_enabled_; |
-} |
- |
-void Logging::ForceShowTimestamp(bool force_show_time) { |
- force_show_time_ = show_time_ = force_show_time; |
-} |
- |
-// Get category level |
-LogLevel Logging::GetCatLevel(LogCategory category) const { |
- if (!IsLoggingAlreadyEnabled()) { |
- return kDefaultLogLevel; |
- } |
- |
- if (category >= LC_MAX_CAT) { |
- return kDefaultLogLevel; |
- } |
- |
- return category_list_[category].log_level; |
-} |
- |
-// Check if logging is enabled for a given category and level |
-DWORD Logging::IsCatLevelEnabled(LogCategory category, LogLevel level) { |
- if (!IsLoggingEnabled()) { |
- return 0; |
- } |
- |
- if (category >= LC_MAX_CAT) { |
- return 0; |
- } |
- |
- // If the config value is to log: then log to all writers. |
- if (category_list_[category].enabled && |
- level <= category_list_[category].log_level) { |
- return static_cast<DWORD>(all_writers_mask); |
- } |
- |
- // Check each of the registered loggers to see if they want to override the |
- // negative config value. |
- DWORD mask = 0; |
- for (int i = num_writers_ - 1; i >= 0; --i) { |
- mask <<= 1; |
- if (writers_[i] && writers_[i]->IsCatLevelEnabled(category, level)) { |
- mask |= 1; |
- } |
- } |
- |
- return mask; |
-} |
- |
-// TODO(omaha): For now this is hard coded and there is no way to override |
-// writing other log categories into the history. Add a store_in_history |
-// boolean into the CategoryInfo struct that allows reading from the config |
-// file. This will enable other log categories to get buffered in the history. |
-bool Logging::IsCategoryEnabledForBuffering(LogCategory cat) { |
- return cat == LC_REPORT; |
-} |
- |
-void Logging::LogMessage(LogCategory cat, LogLevel level, |
- const wchar_t* fmt, ...) { |
- va_list args; |
- va_start(args, fmt); |
- LogMessageVA(cat, level, fmt, args); |
- va_end(args); |
-} |
- |
-void Logging::LogMessageVA(LogCategory cat, LogLevel level, |
- const wchar_t* fmt, va_list args) { |
- LogMessageMaskedVA(static_cast<DWORD>(all_writers_mask), |
- cat, |
- level, |
- fmt, |
- args); |
-} |
- |
-void Logging::InternalLogMessageMaskedVA(DWORD writer_mask, |
- LogCategory cat, |
- LogLevel level, |
- CString* log_buffer, |
- CString* prefix, |
- const wchar_t* fmt, |
- va_list args) { |
- __try { |
- // Initial buffer size in characters. |
- // It will adjust dynamically if the message is bigger. |
- DWORD buffer_size = 512; |
- |
- // Count of chars / bytes written. |
- int num_chars = 0; |
- bool result = false; |
- |
- // Write the message in the buffer. |
- // Dynamically adjust the size to hold the entire message. |
- |
- while ((num_chars = _vsnwprintf_s( |
- log_buffer->GetBufferSetLength(buffer_size), |
- buffer_size, |
- _TRUNCATE, |
- fmt, |
- args)) == -1) { |
- // Truncate if the message is too big. |
- if (buffer_size >= kMaxLogMessageSize) { |
- num_chars = buffer_size; |
- break; |
- } |
- |
- // Get a buffer that is big enough. |
- buffer_size *= 2; |
- } |
- |
- log_buffer->ReleaseBuffer(num_chars); |
- |
- FormatLinePrefix(show_time_, proc_name_, *prefix); |
- |
- // Log the message. |
- OutputInfo info(cat, level, *prefix, *log_buffer); |
- OutputMessage(writer_mask, &info); |
- } __except(SehSendMinidump(GetExceptionCode(), |
- GetExceptionInformation(), |
- kMinsTo100ns)) { |
- OutputDebugStringA("Unexpected exception in: " __FUNCTION__ "\r\n"); |
- OutputDebugString(fmt); |
- OutputDebugString(L"\n\r"); |
- } |
-} |
- |
-void Logging::LogMessageMaskedVA(DWORD writer_mask, |
- LogCategory cat, |
- LogLevel level, |
- const wchar_t* fmt, |
- va_list args) { |
- if (!fmt) { |
- return; |
- } |
- |
- if (writer_mask == 0 && level > kMaxLevelToStoreInLogHistory) { |
- return; |
- } |
- |
- CString log_buffer; // The buffer for formatted log messages. |
- CString prefix; |
- |
- int i = 0; |
- while (++i <= kNumLockRetries) { |
- if (lock_.Lock(0)) { |
- InternalLogMessageMaskedVA(writer_mask, cat, level, &log_buffer, |
- &prefix, fmt, args); |
- lock_.Unlock(); |
- break; |
- } |
- |
- Sleep(kLockRetryDelayMs); |
- } |
- |
- if (i > kNumLockRetries) { |
- OutputDebugStringA("LOG_SYSTEM: Couldn't acquire lock - "); |
- OutputDebugString(fmt); |
- OutputDebugString(L"\n\r"); |
- } |
-} |
- |
-void Logging::OutputMessage(DWORD writer_mask, LogCategory cat, LogLevel level, |
- const wchar_t* msg1, const wchar_t* msg2) { |
- OutputInfo info(cat, level, msg1, msg2); |
- OutputMessage(writer_mask, &info); |
-} |
- |
-// Store log message in in-memory history buffer. |
-void Logging::StoreInHistory(const OutputInfo* output_info) { |
- AppendToHistory(output_info->msg1); |
- AppendToHistory(output_info->msg2); |
- AppendToHistory(L"\r\n"); |
-} |
- |
-// Append string to in-memory history buffer. |
-// history_buffer_next_idx points to the next index to write at, |
-// thus it should always be in (0 - kHistoryBufferEndIdx). |
-void Logging::AppendToHistory(const wchar_t* msg) { |
- int msg_len = wcslen(msg); |
- if (msg_len == 0) { |
- return; |
- } |
- |
- if (msg_len >= kMaxHistoryBufferSize) { |
- // Write the first kMaxHistoryBufferSize chars. |
- memcpy(history_buffer, msg, kMaxHistoryBufferSize * sizeof(TCHAR)); |
- history_buffer_next_idx = 0; |
- history_buffer_full = true; |
- return; |
- } |
- |
- // Determine if the message fits into the portion of the buffer after |
- // history_buffer_next_idx. |
- if (msg_len + history_buffer_next_idx < kMaxHistoryBufferSize) { |
- memcpy(history_buffer + history_buffer_next_idx, msg, |
- msg_len * sizeof(TCHAR)); |
- history_buffer_next_idx += msg_len; |
- return; |
- } |
- |
- // Have to split the input message into the part that fits in |
- // history_buffer_next_idx to kMaxHistoryBufferSize and the remaining message. |
- int msg_first_part_len = kMaxHistoryBufferSize - history_buffer_next_idx; |
- int msg_second_part_len = msg_len - msg_first_part_len; |
- memcpy(history_buffer + history_buffer_next_idx, |
- msg, |
- msg_first_part_len * sizeof(TCHAR)); |
- |
- history_buffer_full = true; |
- history_buffer_next_idx = msg_second_part_len; |
- if (msg_second_part_len) { |
- memcpy(history_buffer, |
- msg + msg_first_part_len, |
- msg_second_part_len * sizeof(TCHAR)); |
- } |
-} |
- |
-// Retrieve in-memory history buffer. |
-CString Logging::GetHistory() { |
- CString history; |
- |
- if (history_buffer_full) { |
- history.Append(history_buffer + history_buffer_next_idx, |
- kMaxHistoryBufferSize - history_buffer_next_idx); |
- } |
- history.Append(history_buffer, history_buffer_next_idx); |
- |
- // Reset the history buffer to the original state. |
- history_buffer_next_idx = 0; |
- history_buffer_full = false; |
- memset(history_buffer, 0, kMaxHistoryBufferSize * sizeof(TCHAR)); |
- |
- return history; |
-} |
- |
-void Logging::OutputMessage(DWORD writer_mask, |
- const OutputInfo* output_info) { |
- if (output_info->level <= kMaxLevelToStoreInLogHistory && |
- IsCategoryEnabledForBuffering(output_info->category)) { |
- StoreInHistory(output_info); |
- } |
- |
- for (int i = 0; i < num_writers_; ++i) { |
- if (writer_mask & 1) { |
- __try { |
- if (logging_enabled_ || writers_[i]->WantsToLogRegardless()) { |
- writers_[i]->OutputMessage(output_info); |
- } |
- } |
- __except(SehNoMinidump(GetExceptionCode(), |
- GetExceptionInformation(), |
- __FILE__, |
- __LINE__, |
- true)) { |
- // Just eat errors that happen from within the LogWriters. This is |
- // important so that if such an error happens when OutputMessage is |
- // called from debugASSERT we don't go recursively into more |
- // error handling ... |
- } |
- } |
- writer_mask >>= 1; |
- } |
-} |
- |
-bool Logging::InternalRegisterWriter(LogWriter* log_writer) { |
- if (num_writers_ >= max_writers) { |
- return false; |
- } |
- writers_[num_writers_++] = log_writer; |
- return true; |
-} |
- |
-bool Logging::RegisterWriter(LogWriter* log_writer) { |
- if (!InternalRegisterWriter(log_writer)) { |
- return false; |
- } |
- if (log_writer->WantsToLogRegardless()) { |
- EnableLogging(); |
- } |
- return true; |
-} |
- |
-bool Logging::InternalUnregisterWriter(LogWriter* log_writer) { |
- bool result = false; |
- for (int i = 0; i < num_writers_; ++i) { |
- if (writers_[i] == log_writer) { |
- // Replace this entry with last entry in array, then truncate. |
- writers_[i] = writers_[--num_writers_]; |
- result = true; |
- break; |
- } |
- } |
- return result; |
-} |
- |
-bool Logging::UnregisterWriter(LogWriter* log_writer) { |
- if (!InternalUnregisterWriter(log_writer)) { |
- return false; |
- } |
- if (num_writers_ == 0) { |
- DisableLogging(); |
- } |
- return true; |
-} |
- |
-// The primary configuration file under %PROGRAMFILES%\Google\Update is |
-// removed on uninstall. This is midly inconvenient during development |
-// therefore a fallback location for the configuration file is desired. |
-CString Logging::GetCurrentConfigurationFilePath() const { |
- if (!config_file_path_.IsEmpty() && |
- File::Exists(config_file_path_)) { |
- return config_file_path_; |
- } else { |
- return L""; |
- } |
-} |
- |
-CString Logging::GetConfigurationFilePath() const { |
- CString file_path; |
- CString system_drive = GetEnvironmentVariableAsString(_T("SystemDrive")); |
- if (!system_drive.IsEmpty()) { |
- file_path = system_drive; |
- file_path += L"\\"; |
- } |
- return file_path + kLogConfigFileName; |
-} |
- |
-LogWriter::LogWriter() { |
-} |
- |
-LogWriter::~LogWriter() { |
-} |
- |
-void LogWriter::Cleanup() {} |
- |
-bool LogWriter::WantsToLogRegardless() const { return false; } |
- |
-bool LogWriter::IsCatLevelEnabled(LogCategory, LogLevel) const { |
- return false; |
-} |
- |
-void LogWriter::OutputMessage(const OutputInfo*) { } |
- |
-bool LogWriter::Register() { |
- Logging* logger = GetLogging(); |
- if (logger) { |
- return logger->RegisterWriter(this); |
- } else { |
- return false; |
- } |
-} |
- |
-bool LogWriter::Unregister() { |
- Logging* logger = GetLogging(); |
- if (logger) { |
- return logger->RegisterWriter(this); |
- } else { |
- return false; |
- } |
-} |
- |
-// FileLogWriter |
- |
-FileLogWriter* FileLogWriter::Create(const wchar_t* file_name, bool append) { |
- return new FileLogWriter(file_name, append); |
-} |
- |
-FileLogWriter::FileLogWriter(const wchar_t* file_name, bool append) |
- : initialized_(false), |
- valid_(false), |
- file_name_(file_name), |
- log_file_mutex_(NULL), |
- log_file_(NULL), |
- append_(append), |
- max_file_size_(kDefaultMaxLogFileSize), |
- log_file_wide_(kDefaultLogFileWide) { |
- Logging* logger = GetLogging(); |
- if (logger) { |
- CString config_file_path = logger->GetCurrentConfigurationFilePath(); |
- if (!config_file_path.IsEmpty()) { |
- max_file_size_ = ::GetPrivateProfileInt( |
- kConfigSectionLoggingSettings, |
- kConfigAttrMaxLogFileSize, |
- kDefaultMaxLogFileSize, |
- config_file_path); |
- log_file_wide_ = ::GetPrivateProfileInt( |
- kConfigSectionLoggingSettings, |
- kConfigAttrLogFileWide, |
- kDefaultLogFileWide, |
- config_file_path) == 0 ? false : true; |
- } else { |
- max_file_size_ = kDefaultMaxLogFileSize; |
- log_file_wide_ = kDefaultLogFileWide; |
- } |
- proc_name_ = logger->proc_name(); |
- } |
-} |
- |
-FileLogWriter::~FileLogWriter() { |
- // TODO(omaha): Figure out a way to pass the proc_name - and possibly |
- // the show_time var - into here. |
- Logging* logger = GetLogging(); |
- if (logger && logger->IsLoggingAlreadyEnabled()) { |
- // OutputInfo info(LEVEL_WARNING, NULL, kEndOfLogMessage); |
- // OutputMessage(&info); |
- } |
- Cleanup(); |
-} |
- |
-void FileLogWriter::Initialize() { |
- if (initialized_) { |
- return; |
- } |
- |
- initialized_ = true; |
- |
- bool already_created = CreateLoggingMutex(); |
- if (!log_file_mutex_) { |
- return; |
- } |
- |
- if (already_created) { |
- append_ = true; |
- } |
- |
- if (!GetMutex()) { |
- ::OutputDebugString(SPRINTF(L"LOG_SYSTEM: [%s]: " |
- L"Could not acquire logging mutex %s\n", |
- proc_name_, |
- log_file_mutex_name_)); |
- return; |
- } |
- |
- CreateLoggingFile(); |
- if (!log_file_) { |
- ::OutputDebugString(SPRINTF(L"LOG_SYSTEM: [%s]: " |
- L"Could not create logging file %s\n", |
- proc_name_, |
- file_name_)); |
- valid_ = false; |
- } |
- |
- valid_ = true; |
- ReleaseMutex(); |
-} |
- |
-void FileLogWriter::Cleanup() { |
- if (log_file_) { |
- ::CloseHandle(log_file_); |
- } |
- if (log_file_mutex_) { |
- ::ReleaseMutex(log_file_mutex_); |
- ::CloseHandle(log_file_mutex_); |
- } |
-} |
- |
-bool FileLogWriter::CreateLoggingMutex() { |
- log_file_mutex_name_ = StripBackslashes(kLoggingMutexName L"_" + file_name_); |
- // TODO(omaha): I don't see where this class is used, but I guess the |
- // caller is always in the same context. We should use the default security |
- // here. If the caller can be in different contexts (System Service, Usermode |
- // applications, etc), then we should revisit this code to give access only |
- // to those who really need it. What if a malicious piece a code decide |
- // to get the mutex and lock it? If it happens, everytime you try to log |
- // something, the thread would hang for 500ms and then fail to log the |
- // message. |
- CSecurityDesc sd; |
- GetEveryoneDaclSecurityDescriptor(&sd, GENERIC_ALL, GENERIC_ALL); |
- CSecurityAttributes sa(sd); |
- log_file_mutex_ = CreateMutexWithSyncAccess(log_file_mutex_name_, &sa); |
- if (log_file_mutex_) { |
- return ERROR_ALREADY_EXISTS == ::GetLastError(); |
- } |
- return false; |
-} |
- |
-bool FileLogWriter::CreateLoggingFile() { |
- uint32 file_size(0); |
- File::GetFileSizeUnopen(file_name_, &file_size); |
- if (file_size > max_file_size_) { |
- ArchiveLoggingFile(); |
- } |
- log_file_ = ::CreateFile(file_name_, |
- GENERIC_WRITE, |
- FILE_SHARE_WRITE | FILE_SHARE_READ, |
- NULL, |
- append_ ? OPEN_ALWAYS : CREATE_ALWAYS, |
- FILE_ATTRIBUTE_NORMAL, |
- NULL); |
- if (log_file_ == INVALID_HANDLE_VALUE) { |
- // The code in this file is written with the assumption that log_file_ is |
- // NULL on creation errors. The easy fix is to set it to NULL here. The |
- // long term fix should be implementing it in terms of a smart handle. |
- log_file_ = NULL; |
- return false; |
- } |
- |
- // Allow users to read, write, and delete the log file. |
- ACCESS_MASK mask = GENERIC_READ | GENERIC_WRITE | DELETE; |
- CDacl dacl; |
- if (dacl.AddAllowedAce(ATL::Sids::Users(), mask)) { |
- AtlSetDacl(file_name_, SE_FILE_OBJECT, dacl); |
- } |
- |
- // Insert a BOM in the newly created file. |
- if (GetLastError() != ERROR_ALREADY_EXISTS && log_file_wide_) { |
- DWORD num = 0; |
- ::WriteFile(log_file_, &kUnicodeBom, sizeof(kUnicodeBom), &num, NULL); |
- } |
- return true; |
-} |
- |
-bool FileLogWriter::TruncateLoggingFile() { |
- DWORD share_mode = FILE_SHARE_WRITE; |
- HANDLE log_file = ::CreateFile(file_name_, |
- GENERIC_WRITE, |
- FILE_SHARE_WRITE | FILE_SHARE_READ, |
- NULL, |
- TRUNCATE_EXISTING, |
- FILE_ATTRIBUTE_NORMAL, |
- NULL); |
- if (log_file_ == INVALID_HANDLE_VALUE) { |
- return false; |
- } |
- |
- // Insert a BOM in the newly created file. |
- if (log_file_wide_) { |
- DWORD num = 0; |
- ::WriteFile(log_file, &kUnicodeBom, sizeof(kUnicodeBom), &num, NULL); |
- } |
- ::CloseHandle(log_file); |
- return true; |
-} |
- |
-bool FileLogWriter::ArchiveLoggingFile() { |
- ::OutputDebugString(L"LOG_SYSTEM: trying to move log file to backup\n"); |
- CString backup_file_name = file_name_ + L".bak"; |
- HRESULT hr = File::Move(file_name_, backup_file_name, true); |
- if (FAILED(hr)) { |
- ::OutputDebugString(L"LOG_SYSTEM: failed to move log file to backup\n"); |
- |
- // Trying to move the log file when loggers have it open returns |
- // ERROR_SHARING_VIOLATION. Each call to MoveFileAfterReboot inserts the |
- // file into PendingFileRenames list. Moving files at reboot requires the |
- // user to be either the LocalSystem account or in the Administrators |
- // group. |
- if (!IsArchivePending()) { |
- File::MoveAfterReboot(file_name_, backup_file_name); |
- } |
- return false; |
- } |
- return true; |
-} |
- |
-bool FileLogWriter::IsArchivePending() { |
- // We look at the PendingFileRenameOperations to see if our log file is |
- // pending a rename. The list is a REG_MULTI_SZ, which is a sequence of |
- // null-terminated strings, terminated by an empty string "\0". |
- // The strings have the structure: |
- // \??\file1\0!\??\file2\0\file3\0\0...\0\0. where file1 is to be renamed to |
- // file2 and file3 is to be deleted. |
- // It is valid for the PFR list to include an empty string in the middle |
- // of the sequence. |
- const wchar_t sub_key_name[] = L"SYSTEM\\CurrentControlSet\\Control\\" |
- L"Session Manager"; |
- HKEY key = NULL; |
- int res = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, |
- sub_key_name, |
- 0, |
- KEY_READ, |
- &key); |
- if (res != ERROR_SUCCESS) { |
- return false; |
- } |
- DWORD bytes = 0; |
- DWORD type = REG_MULTI_SZ; |
- res = ::RegQueryValueEx(key, |
- L"PendingFileRenameOperations", |
- 0, |
- &type, |
- NULL, |
- &bytes); |
- if (!(res == ERROR_SUCCESS && type == REG_MULTI_SZ)) { |
- return false; |
- } |
- scoped_array<byte> buf(new byte[bytes]); |
- memset(buf.get(), 0, bytes); |
- res = ::RegQueryValueEx(key, |
- L"PendingFileRenameOperations", |
- 0, |
- NULL, |
- buf.get(), |
- &bytes); |
- if (res != ERROR_SUCCESS) { |
- return false; |
- } |
- const wchar_t* multi_str = reinterpret_cast<const wchar_t*>(buf.get()); |
- size_t count = bytes / sizeof(*multi_str); |
- const size_t kMaxRegistryValueLen = 1024 * 1024; // 1MB |
- if (!(count >= 2 && |
- count < kMaxRegistryValueLen && |
- multi_str[count - 2] == L'\0' && |
- multi_str[count - 1] == L'\0')) { |
- return false; |
- } |
- // The file names in the PFR list are prefixed by \??\. |
- CString file_name = L"\\??\\" + file_name_; |
- return FindFirstInMultiString(multi_str, count, file_name) != -1; |
-} |
- |
-int FileLogWriter::FindFirstInMultiString(const wchar_t* multi_str, |
- size_t count, |
- const wchar_t* str) { |
- const wchar_t* p = multi_str; |
- size_t i = 0; |
- while (i < count) { |
- p = multi_str + i; |
- if (lstrcmp(p, str) == 0) { |
- return i; |
- } else { |
- size_t len = lstrlen(p); |
- i += len + 1; |
- } |
- } |
- return -1; |
-} |
- |
-void FileLogWriter::OutputMessage(const OutputInfo* output_info) { |
- if (!initialized_) { |
- Initialize(); |
- } |
- |
- if (!valid_) { |
- return; |
- } |
- |
- // Acquire the mutex. |
- if (!GetMutex()) { |
- return; |
- } |
- |
- // Move to end of file. |
- DWORD pos = ::SetFilePointer(log_file_, 0, NULL, FILE_END); |
- int64 stop_gap_file_size = kStopGapLogFileSizeFactor * |
- static_cast<int64>(max_file_size_); |
- if (pos >= stop_gap_file_size) { |
- if (!TruncateLoggingFile()) { |
- // Logging stops until the log can be archived over since we do not |
- // want to overfill the disk. |
- return; |
- } |
- } |
- pos = ::SetFilePointer(log_file_, 0, NULL, FILE_END); |
- |
- // Write the date, followed by a CRLF |
- DWORD written_size = 0; |
- if (output_info->msg1) { |
- if (log_file_wide_) { |
- ::WriteFile(log_file_, output_info->msg1, |
- lstrlen(output_info->msg1) * sizeof(wchar_t), &written_size, |
- NULL); |
- } else { |
- CStringA msg(WideToAnsiDirect(output_info->msg1)); |
- ::WriteFile(log_file_, msg.GetString(), msg.GetLength(), &written_size, |
- NULL); |
- } |
- } |
- |
- if (output_info->msg2) { |
- if (log_file_wide_) { |
- ::WriteFile(log_file_, output_info->msg2, |
- lstrlen(output_info->msg2) * sizeof(wchar_t), &written_size, |
- NULL); |
- } else { |
- CStringA msg(WideToAnsiDirect(output_info->msg2)); |
- ::WriteFile(log_file_, msg.GetString(), msg.GetLength(), &written_size, |
- NULL); |
- } |
- } |
- |
- if (log_file_wide_) { |
- ::WriteFile(log_file_, L"\r\n", 2 * sizeof(wchar_t), &written_size, NULL); |
- } else { |
- ::WriteFile(log_file_, "\r\n", 2, &written_size, NULL); |
- } |
- |
- ReleaseMutex(); |
-} |
- |
-bool FileLogWriter::GetMutex() { |
- if (!log_file_mutex_) { |
- return false; |
- } |
- |
- DWORD res = ::WaitForSingleObject(log_file_mutex_, kMaxMutexWaitTimeMs); |
- if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { |
- ::OutputDebugString(SPRINTF(L"LOG_SYSTEM: [%s]: " |
- L"Could not acquire logging mutex %s\n", |
- proc_name_, log_file_mutex_name_)); |
- valid_ = false; |
- return false; |
- } |
- |
- return true; |
-} |
- |
-void FileLogWriter::ReleaseMutex() { |
- if (log_file_mutex_) { |
- ::ReleaseMutex(log_file_mutex_); |
- } |
-} |
- |
-// OutputDebugStringLogWriter. |
-OutputDebugStringLogWriter* OutputDebugStringLogWriter::Create() { |
- return new OutputDebugStringLogWriter(); |
-} |
- |
-OutputDebugStringLogWriter::OutputDebugStringLogWriter() {} |
- |
-OutputDebugStringLogWriter::~OutputDebugStringLogWriter() { |
- Logging* logger = GetLogging(); |
- if (logger && logger->IsLoggingAlreadyEnabled()) { |
- // OutputInfo info(LEVEL_WARNING, NULL, kEndOfLogMessage); |
- // OutputMessage(&info); |
- } |
- Cleanup(); |
-} |
- |
-void OutputDebugStringLogWriter::OutputMessage(const OutputInfo* output_info) { |
- // Combine everything into one string so that messages coming from |
- // multiple threads don't get interleaved. |
- ::OutputDebugString(SPRINTF(L"%s%s\n", output_info->msg1, output_info->msg2)); |
-} |
- |
-// OverrideConfigLogWriter. |
-OverrideConfigLogWriter* OverrideConfigLogWriter::Create(LogCategory category, |
- LogLevel level, LogWriter* log_writer, bool force_logging_enabled) { |
- return new OverrideConfigLogWriter(category, |
- level, |
- log_writer, |
- force_logging_enabled); |
-} |
- |
-OverrideConfigLogWriter::OverrideConfigLogWriter(LogCategory category, |
- LogLevel level, |
- LogWriter* log_writer, |
- bool force_logging_enabled) |
- : category_(category), |
- level_(level), |
- log_writer_(log_writer), |
- force_logging_enabled_(force_logging_enabled) {} |
- |
-void OverrideConfigLogWriter::Cleanup() { |
- if (log_writer_) { |
- delete log_writer_; |
- } |
-} |
- |
-bool OverrideConfigLogWriter::WantsToLogRegardless() const { |
- return force_logging_enabled_; |
-} |
- |
-bool OverrideConfigLogWriter::IsCatLevelEnabled(LogCategory category, |
- LogLevel level) const { |
- if (category != category_) { |
- return false; |
- } |
- if (level > level_) { |
- return false; |
- } |
- return true; |
-} |
- |
-void OverrideConfigLogWriter::OutputMessage(const OutputInfo* output_info) { |
- if (log_writer_) { |
- log_writer_->OutputMessage(output_info); |
- } |
- return; |
-} |
- |
-} // namespace omaha |
- |
-#endif // LOGGING |
- |