From 0932ea583a7b5a86c390415a97c9f6d585d74c32 Mon Sep 17 00:00:00 2001 From: xfc1939 Date: Thu, 21 Dec 2023 23:47:38 +0800 Subject: [PATCH] feature: Support unicode in paths #995 --- src/base/commandlineflags.h | 39 ++ src/cleanup_with_absolute_prefix_unittest.cc | 5 +- src/cleanup_with_relative_prefix_unittest.cc | 5 + src/glog/logging.h.in | 44 +- src/googletest.h | 128 ++++- src/logging.cc | 547 ++++++++++++++++--- src/logging_unittest.cc | 145 ++++- src/utilities.cc | 52 +- src/utilities.h | 12 +- 9 files changed, 893 insertions(+), 84 deletions(-) diff --git a/src/base/commandlineflags.h b/src/base/commandlineflags.h index 66c0dae09..33e914291 100644 --- a/src/base/commandlineflags.h +++ b/src/base/commandlineflags.h @@ -61,6 +61,11 @@ #include "glog/logging.h" +#ifdef GLOG_OS_WINDOWS +#include "Windows.h" +#endif // GLOG_OS_WINDOWS + + #define DECLARE_VARIABLE(type, shorttype, name, tn) \ namespace fL##shorttype { \ extern GLOG_EXPORT type FLAGS_##name; \ @@ -107,6 +112,21 @@ char FLAGS_no##name; \ } \ using fLS::FLAGS_##name + +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +#define DECLARE_wstring(name) \ + namespace fLS { \ + extern GLOG_EXPORT std::wstring& FLAGS_##name; \ + } \ + using fLS::FLAGS_##name +#define DEFINE_wstring(name, value, meaning) \ + namespace fLS { \ + std::wstring FLAGS_##name##_buf(value); \ + GLOG_EXPORT std::wstring& FLAGS_##name = FLAGS_##name##_buf; \ + wchar_t FLAGS_no##name; \ + } \ + using fLS::FLAGS_##name +#endif // #endif // HAVE_LIB_GFLAGS @@ -135,6 +155,25 @@ #define EnvToString(envname, dflt) \ (!getenv(envname) ? (dflt) : getenv(envname)) +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +#define GLOG_DEFINE_wstring(name, value, meaning) \ + DEFINE_wstring(name, EnvToWString(TEXT("GLOG_") #name, value), meaning) + +static std::wstring getenvw(wchar_t *lpName) { + auto size = GetEnvironmentVariableW(lpName, NULL, 0); + if (size == 0) { + return {}; + } + std::wstring value; + value.resize(size); + GetEnvironmentVariableW(lpName, &value[0], size); + return value.substr(0,size-1); +} +#define EnvToWString(envname, dflt) \ + (getenvw(envname).empty() ? dflt : getenvw(envname)) +#endif + + #define EnvToBool(envname, dflt) \ (!getenv(envname) ? (dflt) \ : memchr("tTyY1\0", getenv(envname)[0], 6) != nullptr) diff --git a/src/cleanup_with_absolute_prefix_unittest.cc b/src/cleanup_with_absolute_prefix_unittest.cc index c035a9e98..a4cc8f3fe 100644 --- a/src/cleanup_with_absolute_prefix_unittest.cc +++ b/src/cleanup_with_absolute_prefix_unittest.cc @@ -57,8 +57,11 @@ using namespace GOOGLE_NAMESPACE; TEST(CleanImmediatelyWithAbsolutePrefix, logging) { google::EnableLogCleaner(0); google::SetLogFilenameExtension(".barfoo"); +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + google::SetLogDestination(GLOG_INFO, TEXT("test_cleanup_")); +#else google::SetLogDestination(GLOG_INFO, "test_cleanup_"); - +#endif for (unsigned i = 0; i < 1000; ++i) { LOG(INFO) << "cleanup test"; } diff --git a/src/cleanup_with_relative_prefix_unittest.cc b/src/cleanup_with_relative_prefix_unittest.cc index 194c8e685..b6e798abb 100644 --- a/src/cleanup_with_relative_prefix_unittest.cc +++ b/src/cleanup_with_relative_prefix_unittest.cc @@ -57,7 +57,12 @@ using namespace GOOGLE_NAMESPACE; TEST(CleanImmediatelyWithRelativePrefix, logging) { google::EnableLogCleaner(0); google::SetLogFilenameExtension(".relativefoo"); +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + google::SetLogDestination(GLOG_INFO, TEXT("test_subdir/test_cleanup_")); +#else google::SetLogDestination(GLOG_INFO, "test_subdir/test_cleanup_"); +#endif // (GLOG_OS_WINDOWS) && defined(UNICODE) + for (unsigned i = 0; i < 1000; ++i) { LOG(INFO) << "cleanup test"; diff --git a/src/glog/logging.h.in b/src/glog/logging.h.in index 0a7ebd255..11e39e3d7 100644 --- a/src/glog/logging.h.in +++ b/src/glog/logging.h.in @@ -351,6 +351,9 @@ typedef void(*CustomPrefixCallback)(std::ostream& s, const LogMessageInfo& l, vo #pragma push_macro("DECLARE_VARIABLE") #pragma push_macro("DECLARE_bool") #pragma push_macro("DECLARE_string") +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +#pragma push_macro("DECLARE_wstring") +#endif #pragma push_macro("DECLARE_int32") #pragma push_macro("DECLARE_uint32") @@ -366,6 +369,12 @@ typedef void(*CustomPrefixCallback)(std::ostream& s, const LogMessageInfo& l, vo #undef DECLARE_string #endif +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +#ifdef DECLARE_wstring +#undef DECLARE_wstring +#endif +#endif + #ifdef DECLARE_int32 #undef DECLARE_int32 #endif @@ -402,8 +411,19 @@ typedef void(*CustomPrefixCallback)(std::ostream& s, const LogMessageInfo& l, vo extern GLOG_EXPORT std::string& FLAGS_##name; \ } \ using fLS::FLAGS_##name + +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +#define DECLARE_wstring(name) \ + namespace fLS { \ + extern GLOG_EXPORT std::wstring& FLAGS_##name; \ + } \ + using fLS::FLAGS_##name +#endif + #endif + + // Set whether appending a timestamp to the log file name DECLARE_bool(timestamp_in_logfile_name); @@ -448,7 +468,12 @@ DECLARE_int32(minloglevel); // If specified, logfiles are written into this directory instead of the // default logging directory. + +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +DECLARE_wstring(log_dir); +#else DECLARE_string(log_dir); +#endif // Set the log file mode. DECLARE_int32(logfile_mode); @@ -1574,9 +1599,13 @@ GLOG_EXPORT void FlushLogFilesUnsafe(LogSeverity min_severity); // messages is sent. If base_filename is "", it means "don't log this // severity". Thread-safe. // +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +GLOG_EXPORT void SetLogDestination(LogSeverity severity, + const wchar_t* base_filename); +#else GLOG_EXPORT void SetLogDestination(LogSeverity severity, const char* base_filename); - +#endif // // Set the basename of the symlink to the latest log file at a given // severity. If symlink_basename is empty, do not make a symlink. If @@ -1668,8 +1697,11 @@ GLOG_EXPORT void SetEmailLogging(LogSeverity min_severity, // list of addresses. Thread-safe. GLOG_EXPORT bool SendEmail(const char* dest, const char* subject, const char* body); - +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +GLOG_EXPORT const std::vector& GetLoggingDirectories(); +#else GLOG_EXPORT const std::vector& GetLoggingDirectories(); +#endif // For tests only: Clear the internal [cached] list of logging directories to // force a refresh the next time GetLoggingDirectories is called. @@ -1679,8 +1711,13 @@ void TestOnly_ClearLoggingDirectoriesList(); // Returns a set of existing temporary directories, which will be a // subset of the directories returned by GetLoggingDirectories(). // Thread-safe. +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +GLOG_EXPORT void GetExistingTempDirectories( + std::vector* list); +#else GLOG_EXPORT void GetExistingTempDirectories( std::vector* list); +#endif // Print any fatal message again -- useful to call from signal handler // so that the last thing in the output is the fatal message. @@ -1839,6 +1876,9 @@ GLOG_EXPORT void InstallFailureWriter( #pragma pop_macro("DECLARE_VARIABLE") #pragma pop_macro("DECLARE_bool") #pragma pop_macro("DECLARE_string") +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +#pragma pop_macro("DECLARE_wstring") +#endif #pragma pop_macro("DECLARE_int32") #pragma pop_macro("DECLARE_uint32") diff --git a/src/googletest.h b/src/googletest.h index 0f9609968..bb27012a3 100644 --- a/src/googletest.h +++ b/src/googletest.h @@ -67,6 +67,9 @@ using std::map; using std::string; using std::vector; +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +using std::wstring; +#endif _START_GOOGLE_NAMESPACE_ @@ -77,6 +80,20 @@ _END_GOOGLE_NAMESPACE_ #undef GLOG_EXPORT #define GLOG_EXPORT +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static inline wstring GetTempDir() { + vector temp_directories_list; + google::GetExistingTempDirectories(&temp_directories_list); + + if (temp_directories_list.empty()) { + fprintf(stderr, "No temporary directory found\n"); + exit(EXIT_FAILURE); + } + + // Use first directory from list of existing temporary directories. + return temp_directories_list.front(); +} +#else static inline string GetTempDir() { vector temp_directories_list; google::GetExistingTempDirectories(&temp_directories_list); @@ -89,6 +106,8 @@ static inline string GetTempDir() { // Use first directory from list of existing temporary directories. return temp_directories_list.front(); } +#endif + #if defined(GLOG_OS_WINDOWS) && defined(_MSC_VER) && !defined(TEST_SRC_DIR) // The test will run in glog/vsproject/ @@ -101,9 +120,16 @@ static const char TEST_SRC_DIR[] = "."; static const uint32_t PTR_TEST_VALUE = 0x12345678; +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +DEFINE_wstring(test_tmpdir, GetTempDir(), "Dir we use for temp files"); +#else DEFINE_string(test_tmpdir, GetTempDir(), "Dir we use for temp files"); + +#endif // + DEFINE_string(test_srcdir, TEST_SRC_DIR, "Source-dir root, needed to find glog_unittest_flagfile"); + DEFINE_bool(run_benchmark, false, "If true, run benchmarks"); #ifdef NDEBUG DEFINE_int32(benchmark_iters, 100000000, "Number of iterations per benchmark"); @@ -296,7 +322,11 @@ static inline void RunSpecifiedBenchmarks() { class CapturedStream { public: +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + CapturedStream(int fd, wstring filename) +#else CapturedStream(int fd, string filename) +#endif : fd_(fd), filename_(std::move(filename)) { @@ -317,9 +347,14 @@ class CapturedStream { CHECK(uncaptured_fd_ != -1); // Open file to save stream to +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + int cap_fd = _wopen(filename_.c_str(), O_CREAT | O_TRUNC | O_WRONLY, + S_IRUSR | S_IWUSR); +#else int cap_fd = open(filename_.c_str(), O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR); +#endif CHECK(cap_fd != -1); // Send stdout/stderr to this file @@ -336,28 +371,47 @@ class CapturedStream { CHECK(dup2(uncaptured_fd_, fd_) != -1); } } - +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + const wstring& filename() const { return filename_; } +#else const string & filename() const { return filename_; } - +#endif private: int fd_; // file descriptor being captured int uncaptured_fd_{-1}; // where the stream was originally being sent to +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + wstring filename_; // file where stream is being saved +#else string filename_; // file where stream is being saved +#endif }; static CapturedStream * s_captured_streams[STDERR_FILENO+1]; // Redirect a file descriptor to a file. // fd - Should be STDOUT_FILENO or STDERR_FILENO // filename - File where output should be stored -static inline void CaptureTestOutput(int fd, const string & filename) { + +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static inline void CaptureTestOutput(int fd, const wstring & filename) { +#else +static inline void CaptureTestOutput(int fd, const string& filename) { +#endif CHECK((fd == STDOUT_FILENO) || (fd == STDERR_FILENO)); CHECK(s_captured_streams[fd] == nullptr); s_captured_streams[fd] = new CapturedStream(fd, filename); } static inline void CaptureTestStdout() { +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + CaptureTestOutput(STDOUT_FILENO, FLAGS_test_tmpdir + TEXT("/captured.out")); +#else CaptureTestOutput(STDOUT_FILENO, FLAGS_test_tmpdir + "/captured.out"); +#endif } static inline void CaptureTestStderr() { +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + CaptureTestOutput(STDERR_FILENO, FLAGS_test_tmpdir + TEXT("/captured.err")); +#else CaptureTestOutput(STDERR_FILENO, FLAGS_test_tmpdir + "/captured.err"); +#endif } // Return the size (in bytes) of a file static inline size_t GetFileSize(FILE * file) { @@ -398,7 +452,11 @@ static inline string GetCapturedTestOutput(int fd) { cap->StopCapture(); // Read the captured file. - FILE * const file = fopen(cap->filename().c_str(), "r"); +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + FILE * const file = _wfopen(cap->filename().c_str(), TEXT("r")); +#else + FILE* const file = fopen(cap->filename().c_str(), "r"); +#endif const string content = ReadEntireFile(file); fclose(file); @@ -476,10 +534,16 @@ static inline void StringReplace(string* str, str->replace(pos, oldsub.size(), newsub); } } - +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static inline string Munge(const wstring& filename) { + FILE* fp = _wfopen(filename.c_str(), TEXT("rb")); + CHECK(fp != nullptr) << ConvertWString2String(filename) << ": couldn't open"; +#else static inline string Munge(const string& filename) { FILE* fp = fopen(filename.c_str(), "rb"); CHECK(fp != nullptr) << filename << ": couldn't open"; +#endif + char buf[4096]; string result; while (fgets(buf, 4095, fp)) { @@ -503,13 +567,52 @@ static inline string Munge(const string& filename) { fclose(fp); return result; } - +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static inline void WriteToFile(const string& body, const wstring& file) { + FILE* fp = _wfopen(file.c_str(), TEXT("wb")); + fwrite(body.data(), 1, body.size(), fp); + fclose(fp); +} +#else static inline void WriteToFile(const string& body, const string& file) { FILE* fp = fopen(file.c_str(), "wb"); fwrite(body.data(), 1, body.size(), fp); fclose(fp); } +#endif +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static inline bool MungeAndDiffTest(const wstring& golden_filename, + CapturedStream* cap) { + if (cap == s_captured_streams[STDOUT_FILENO]) { + CHECK(cap) << ": did you forget CaptureTestStdout()?"; + } else { + CHECK(cap) << ": did you forget CaptureTestStderr()?"; + } + + cap->StopCapture(); + // Run munge + const string captured = Munge(cap->filename()); + const string golden = Munge(golden_filename); + if (captured != golden) { + fprintf(stderr, + "Test with golden file failed. We'll try to show the diff:\n"); + wstring munged_golden = golden_filename + TEXT(".munged"); + WriteToFile(golden, munged_golden); + wstring munged_captured = cap->filename() + TEXT(".munged"); + WriteToFile(captured, munged_captured); + wstring diffcmd(TEXT("fc ") + munged_golden + TEXT(" ") + munged_captured); + if (_wsystem(diffcmd.c_str()) != 0) { + fprintf(stderr, "diff command was failed.\n"); + } + _wunlink(munged_golden.c_str()); + _wunlink(munged_captured.c_str()); + return false; + } + LOG(INFO) << "Diff was successful"; + return true; +} +#else static inline bool MungeAndDiffTest(const string& golden_filename, CapturedStream* cap) { if (cap == s_captured_streams[STDOUT_FILENO]) { @@ -545,7 +648,17 @@ static inline bool MungeAndDiffTest(const string& golden_filename, LOG(INFO) << "Diff was successful"; return true; } +#endif + +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static inline bool MungeAndDiffTestStderr(const wstring& golden_filename) { + return MungeAndDiffTest(golden_filename, s_captured_streams[STDERR_FILENO]); +} +static inline bool MungeAndDiffTestStdout(const wstring& golden_filename) { + return MungeAndDiffTest(golden_filename, s_captured_streams[STDOUT_FILENO]); +} +#else static inline bool MungeAndDiffTestStderr(const string& golden_filename) { return MungeAndDiffTest(golden_filename, s_captured_streams[STDERR_FILENO]); } @@ -554,6 +667,9 @@ static inline bool MungeAndDiffTestStdout(const string& golden_filename) { return MungeAndDiffTest(golden_filename, s_captured_streams[STDOUT_FILENO]); } +#endif // + + // Save flags used from logging_unittest.cc. #ifndef HAVE_LIB_GFLAGS struct FlagSaver { diff --git a/src/logging.cc b/src/logging.cc index 8d90e2b83..6d4e5027c 100644 --- a/src/logging.cc +++ b/src/logging.cc @@ -86,6 +86,7 @@ #endif using std::string; +using std::wstring; using std::vector; using std::setw; using std::setfill; @@ -180,6 +181,16 @@ GLOG_DEFINE_string(logmailer, "", "Mailer used to send logging email"); // Compute the default value for --log_dir +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static const std::wstring DefaultLogDir() { + std::wstring env = getenvw(TEXT("GOOGLE_LOG_DIR")); + if (!env.empty()) { + return env; + } + env = getenvw(TEXT("TEST_TMPDIR")); + return env; +} +#else static const char* DefaultLogDir() { const char* env; env = getenv("GOOGLE_LOG_DIR"); @@ -192,12 +203,23 @@ static const char* DefaultLogDir() { } return ""; } +#endif GLOG_DEFINE_int32(logfile_mode, 0664, "Log file mode/permissions."); -GLOG_DEFINE_string(log_dir, DefaultLogDir(), - "If specified, logfiles are written into this directory instead " - "of the default logging directory."); + +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +GLOG_DEFINE_wstring( + log_dir, DefaultLogDir(), + TEXT("If specified, logfiles are written into this directory instead ") + TEXT("of the default logging directory.")); +#else +GLOG_DEFINE_string( + log_dir, DefaultLogDir(), + "If specified, logfiles are written into this directory instead " + "of the default logging directory."); +#endif // GLOG_DEFINE_wstring(log_dir, De) + GLOG_DEFINE_string(log_link, "", "Put additional links to the log " "files in this directory"); @@ -253,15 +275,8 @@ static ssize_t pwrite(int fd, void* buf, size_t count, off_t offset) { } #endif // !HAVE_PWRITE +#ifdef GLOG_OS_WINDOWS static void GetHostName(string* hostname) { -#if defined(HAVE_SYS_UTSNAME_H) - struct utsname buf; - if (uname(&buf) < 0) { - // ensure null termination on failure - *buf.nodename = '\0'; - } - *hostname = buf.nodename; -#elif defined(GLOG_OS_WINDOWS) char buf[MAX_COMPUTERNAME_LENGTH + 1]; DWORD len = MAX_COMPUTERNAME_LENGTH + 1; if (GetComputerNameA(buf, &len)) { @@ -269,11 +284,25 @@ static void GetHostName(string* hostname) { } else { hostname->clear(); } +} +#else +static void GetHostName(string* hostname) { +#if defined(HAVE_SYS_UTSNAME_H) + struct utsname buf; + if (uname(&buf) < 0) { + // ensure null termination on failure + *buf.nodename = '\0'; + } + *hostname = buf.nodename; #else -# warning There is no way to retrieve the host name. +#warning There is no way to retrieve the host name. *hostname = "(unknown)"; #endif } +#endif // GLOG_OS_WINDOWS + + + // Returns true iff terminal supports using colors in output. static bool TerminalSupportsColor() { @@ -440,7 +469,11 @@ namespace { // Encapsulates all file-system related state class LogFileObject : public base::Logger { public: +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + LogFileObject(LogSeverity severity, const wchar_t* base_filename); +#else LogFileObject(LogSeverity severity, const char* base_filename); +#endif ~LogFileObject() override; void Write(bool force_flush, // Should we force a flush here? @@ -448,7 +481,11 @@ class LogFileObject : public base::Logger { const char* message, size_t message_len) override; // Configuration options +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + void SetBasename(const wchar_t* basename); +#else void SetBasename(const char* basename); +#endif void SetExtension(const char* ext); void SetSymlinkBasename(const char* symlink_basename); @@ -472,7 +509,12 @@ class LogFileObject : public base::Logger { Mutex lock_; bool base_filename_selected_; +#if defined (GLOG_OS_WINDOWS) && defined(UNICODE) + wstring base_filename_; + #else string base_filename_; +#endif // ! + string symlink_basename_; string filename_extension_; // option users can specify (eg to add port#) FILE* file_{nullptr}; @@ -501,14 +543,29 @@ class LogCleaner { // update next_cleanup_time_ void UpdateCleanUpTime(); - - void Run(bool base_filename_selected, - const string& base_filename, +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + void Run(bool base_filename_selected, const wstring& base_filename, const string& filename_extension); +#else + void Run(bool base_filename_selected, const string& base_filename, + const string& filename_extension); +#endif + bool enabled() const { return enabled_; } private: +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + vector GetOverdueLogNames(wstring log_directory, unsigned int days, + const wstring& base_filename, + const string& filename_extension) const; + + bool IsLogFromCurrentProject(const wstring& filepath, + const wstring& base_filename, + const string& filename_extension) const; + + bool IsLogLastModifiedOver(const wstring& filepath, unsigned int days) const; +#else vector GetOverdueLogNames(string log_directory, unsigned int days, const string& base_filename, const string& filename_extension) const; @@ -518,6 +575,9 @@ class LogCleaner { const string& filename_extension) const; bool IsLogLastModifiedOver(const string& filepath, unsigned int days) const; +#endif // + + bool enabled_{false}; unsigned int overdue_days_{7}; @@ -535,9 +595,15 @@ class LogDestination { friend base::Logger* base::GetLogger(LogSeverity); friend void base::SetLogger(LogSeverity, base::Logger*); +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) // These methods are just forwarded to by their global versions. static void SetLogDestination(LogSeverity severity, - const char* base_filename); + const wchar_t* base_filename); +#else + // These methods are just forwarded to by their global versions. + static void SetLogDestination(LogSeverity severity, + const char* base_filename); +#endif static void SetLogSymlink(LogSeverity severity, const char* symlink_basename); static void AddLogSink(LogSink *destination); @@ -563,7 +629,12 @@ class LogDestination { static void DeleteLogDestinations(); private: +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + LogDestination(LogSeverity severity, const wchar_t* base_filename); +#else LogDestination(LogSeverity severity, const char* base_filename); +#endif // + ~LogDestination(); // Take a log message of a particular severity and log it to stderr @@ -635,21 +706,27 @@ Mutex LogDestination::sink_mutex_; bool LogDestination::terminal_supports_color_ = TerminalSupportsColor(); /* static */ + + const string& LogDestination::hostname() { if (hostname_.empty()) { GetHostName(&hostname_); if (hostname_.empty()) { - hostname_ = "(unknown)"; + hostname_ = "(unknown)"; } } return hostname_; } - +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +LogDestination::LogDestination(LogSeverity severity, const wchar_t* base_filename) + : fileobject_(severity, base_filename), logger_(&fileobject_) {} +#else LogDestination::LogDestination(LogSeverity severity, const char* base_filename) : fileobject_(severity, base_filename), logger_(&fileobject_) { } +#endif LogDestination::~LogDestination() { ResetLoggerImpl(); @@ -693,14 +770,27 @@ inline void LogDestination::FlushLogFiles(int min_severity) { } } +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +inline void LogDestination::SetLogDestination(LogSeverity severity, + const wchar_t* base_filename) { + assert(severity >= 0 && severity < NUM_SEVERITIES); + // Prevent any subtle race conditions by wrapping a mutex lock around + // all this stuff. + MutexLock l(&log_mutex); + log_destination(severity)->fileobject_.SetBasename(base_filename); +} +#else inline void LogDestination::SetLogDestination(LogSeverity severity, - const char* base_filename) { + const char* base_filename) { assert(severity >= 0 && severity < NUM_SEVERITIES); // Prevent any subtle race conditions by wrapping a mutex lock around // all this stuff. MutexLock l(&log_mutex); log_destination(severity)->fileobject_.SetBasename(base_filename); } +#endif // + + inline void LogDestination::SetLogSymlink(LogSeverity severity, const char* symlink_basename) { @@ -750,7 +840,11 @@ inline void LogDestination::LogToStderr() { // SetLogDestination already do the locking! SetStderrLogging(0); // thus everything is "also" logged to stderr for ( int i = 0; i < NUM_SEVERITIES; ++i ) { - SetLogDestination(i, ""); // "" turns off logging to a logfile +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + SetLogDestination(i, TEXT("")); // "" turns off logging to a logfile +#else + SetLogDestination(i, ""); // "" turns off logging to a logfile +#endif } } @@ -968,6 +1062,8 @@ namespace { // Directory delimiter; Windows supports both forward slashes and backslashes #ifdef GLOG_OS_WINDOWS const char possible_dir_delim[] = {'\\', '/'}; +const TCHAR w_possible_dir_delim[] = {TEXT('\\'), TEXT('/')}; + #else const char possible_dir_delim[] = {'/'}; #endif @@ -982,7 +1078,22 @@ string PrettyDuration(int secs) { result << hours << ':' << setw(2) << mins << ':' << setw(2) << secs; return result.str(); } +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +LogFileObject::LogFileObject(LogSeverity severity, const wchar_t* base_filename) + : base_filename_selected_(base_filename != nullptr), + base_filename_((base_filename != nullptr) ? base_filename : TEXT("")), + symlink_basename_(glog_internal_namespace_::ProgramInvocationShortName()), + filename_extension_(), + + severity_(severity), + rollover_attempt_(kRolloverAttemptFrequency - 1), + + start_time_(WallTime_Now()) { + assert(severity >= 0); + assert(severity < NUM_SEVERITIES); +} +#else LogFileObject::LogFileObject(LogSeverity severity, const char* base_filename) : base_filename_selected_(base_filename != nullptr), base_filename_((base_filename != nullptr) ? base_filename : ""), @@ -997,6 +1108,7 @@ LogFileObject::LogFileObject(LogSeverity severity, const char* base_filename) assert(severity >= 0); assert(severity < NUM_SEVERITIES); } +#endif LogFileObject::~LogFileObject() { MutexLock l(&lock_); @@ -1005,7 +1117,21 @@ LogFileObject::~LogFileObject() { file_ = nullptr; } } - +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +void LogFileObject::SetBasename(const wchar_t* basename) { + MutexLock l(&lock_); + base_filename_selected_ = true; + if (base_filename_ != basename) { + // Get rid of old log file since we are changing names + if (file_ != nullptr) { + fclose(file_); + file_ = nullptr; + rollover_attempt_ = kRolloverAttemptFrequency - 1; + } + base_filename_ = basename; + } +} +#else void LogFileObject::SetBasename(const char* basename) { MutexLock l(&lock_); base_filename_selected_ = true; @@ -1014,11 +1140,14 @@ void LogFileObject::SetBasename(const char* basename) { if (file_ != nullptr) { fclose(file_); file_ = nullptr; - rollover_attempt_ = kRolloverAttemptFrequency-1; + rollover_attempt_ = kRolloverAttemptFrequency - 1; } base_filename_ = basename; } } +#endif // + + void LogFileObject::SetExtension(const char* ext) { MutexLock l(&lock_); @@ -1055,20 +1184,37 @@ void LogFileObject::FlushUnlocked(){ } bool LogFileObject::CreateLogfile(const string& time_pid_string) { +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + wstring string_filename = base_filename_; + if (FLAGS_timestamp_in_logfile_name) { + string_filename += ConvertString2WString(time_pid_string); + } + string_filename += ConvertString2WString(filename_extension_); + const wchar_t* filename = string_filename.c_str(); + //only write to files, create if non-existant. + int flags = O_WRONLY | O_CREAT; + if (FLAGS_timestamp_in_logfile_name) { + //demand that the file is unique for our timestamp (fail if it exists). + flags = flags | O_EXCL; + } + int fd = _wopen(filename, flags, static_cast(FLAGS_logfile_mode)); + if (fd == -1) return false; +#else string string_filename = base_filename_; if (FLAGS_timestamp_in_logfile_name) { string_filename += time_pid_string; } string_filename += filename_extension_; const char* filename = string_filename.c_str(); - //only write to files, create if non-existant. + // only write to files, create if non-existant. int flags = O_WRONLY | O_CREAT; if (FLAGS_timestamp_in_logfile_name) { - //demand that the file is unique for our timestamp (fail if it exists). + // demand that the file is unique for our timestamp (fail if it exists). flags = flags | O_EXCL; } int fd = open(filename, flags, static_cast(FLAGS_logfile_mode)); if (fd == -1) return false; +#endif #ifdef HAVE_FCNTL // Mark the file close-on-exec. We don't really care if this fails fcntl(fd, F_SETFD, FD_CLOEXEC); @@ -1102,7 +1248,13 @@ bool LogFileObject::CreateLogfile(const string& time_pid_string) { if (file_ == nullptr) { // Man, we're screwed! close(fd); if (FLAGS_timestamp_in_logfile_name) { - unlink(filename); // Erase the half-baked evidence: an unusable log file, only if we just created it. +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + _wunlink(filename); +#else + unlink(filename); // Erase the half-baked evidence: an unusable log file, + // only if we just created it. +#endif // + } return false; } @@ -1115,6 +1267,10 @@ bool LogFileObject::CreateLogfile(const string& time_pid_string) { } } #endif + +#if defined(GLOG_OS_WINDOWS) + // TODO(hamaji): Create lnk file on Windows? +#elif defined(HAVE_UNISTD_H) // We try to create a symlink called ., // which is easier to use. (Every time we create a new logfile, // we destroy the old symlink and create a new one, so it always @@ -1124,15 +1280,13 @@ bool LogFileObject::CreateLogfile(const string& time_pid_string) { // take directory from filename const char* slash = strrchr(filename, PATH_SEPARATOR); const string linkname = - symlink_basename_ + '.' + LogSeverityNames[severity_]; + symlink_basename_ + '.' + LogSeverityNames[severity_]; string linkpath; - if ( slash ) linkpath = string(filename, static_cast(slash-filename+1)); // get dirname + if (slash) + linkpath = string( + filename, static_cast(slash - filename + 1)); // get dirname linkpath += linkname; - unlink(linkpath.c_str()); // delete old one if it exists - -#if defined(GLOG_OS_WINDOWS) - // TODO(hamaji): Create lnk file on Windows? -#elif defined(HAVE_UNISTD_H) + unlink(linkpath.c_str()); // delete old one if it exists // We must have unistd.h. // Make the symlink be relative (in the same dir) so that if the // entire log directory gets relocated the link is still valid. @@ -1150,9 +1304,8 @@ bool LogFileObject::CreateLogfile(const string& time_pid_string) { // silently ignore failures } } -#endif } - +#endif return true; // Everything worked } @@ -1222,6 +1375,31 @@ void LogFileObject::Write(bool force_flush, // // Where does the file get put? Successively try the directories // "/tmp", and "." + + +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + wstring stripped_filename( + ConvertString2WString(glog_internal_namespace_::ProgramInvocationShortName())); + string hostname; + GetHostName(&hostname); + + wstring uidname = MyUserName(); + // We should not call CHECK() here because this function can be + // called after holding on to log_mutex. We don't want to + // attempt to hold on to the same mutex, and get into a + // deadlock. Simply use a name like invalid-user. + if (uidname.empty()) uidname = TEXT("invalid-user"); + + stripped_filename = + stripped_filename + TEXT('.') + ConvertString2WString(hostname) + + TEXT('.') + + uidname + TEXT(".log.") + + ConvertString2WString(LogSeverityNames[severity_]) + + TEXT('.'); + + // We're going to (potentially) try to put logs in several different dirs + const vector& log_dirs = GetLoggingDirectories(); +#else string stripped_filename( glog_internal_namespace_::ProgramInvocationShortName()); string hostname; @@ -1234,17 +1412,22 @@ void LogFileObject::Write(bool force_flush, // deadlock. Simply use a name like invalid-user. if (uidname.empty()) uidname = "invalid-user"; - stripped_filename = stripped_filename+'.'+hostname+'.' - +uidname+".log." - +LogSeverityNames[severity_]+'.'; + stripped_filename = stripped_filename + '.' + hostname + '.' + uidname + + ".log." + LogSeverityNames[severity_] + '.'; // We're going to (potentially) try to put logs in several different dirs - const vector & log_dirs = GetLoggingDirectories(); + const vector& log_dirs = GetLoggingDirectories(); + +#endif // // Go through the list of dirs, and try to create the log file in each // until we succeed or run out of options bool success = false; for (const auto& log_dir : log_dirs) { +#if defined(GLOG_OS_WINDOWS) + base_filename_ = log_dir + TEXT("/") + stripped_filename; +#else base_filename_ = log_dir + "/" + stripped_filename; +#endif if ( CreateLogfile(time_pid_string) ) { success = true; break; @@ -1371,9 +1554,43 @@ void LogCleaner::UpdateCleanUpTime() { * 1000000); // in usec next_cleanup_time_ = CycleClock_Now() + UsecToCycles(next); } +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +void LogCleaner::Run(bool base_filename_selected, const wstring& base_filename, + const string& filename_extension) { + assert(enabled_); + assert(!base_filename_selected || !base_filename.empty()); + + // avoid scanning logs too frequently + if (CycleClock_Now() < next_cleanup_time_) { + return; + } + UpdateCleanUpTime(); + + vector dirs; + + if (!base_filename_selected) { + dirs = GetLoggingDirectories(); + } else { + size_t pos = base_filename.find_last_of(w_possible_dir_delim, string::npos, + sizeof(w_possible_dir_delim)); + if (pos != string::npos) { + wstring dir = base_filename.substr(0, pos + 1); + dirs.push_back(dir); + } else { + dirs.emplace_back(TEXT(".")); + } + } -void LogCleaner::Run(bool base_filename_selected, - const string& base_filename, + for (auto& dir : dirs) { + vector logs = GetOverdueLogNames(dir, overdue_days_, base_filename, + filename_extension); + for (auto& log : logs) { + static_cast(_wunlink(log.c_str())); + } + } +} +#else +void LogCleaner::Run(bool base_filename_selected, const string& base_filename, const string& filename_extension) { assert(enabled_); assert(!base_filename_selected || !base_filename.empty()); @@ -1407,7 +1624,48 @@ void LogCleaner::Run(bool base_filename_selected, } } } +#endif // + + +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +vector LogCleaner::GetOverdueLogNames( + wstring log_directory, unsigned int days, const wstring& base_filename, + const string& filename_extension) const { + // The names of overdue logs. + vector overdue_log_names; + + // Try to get all files within log_directory. + WDIR* dir; + struct wdirent* ent; + + if ((dir = wopendir(log_directory.c_str()))) { + while ((ent = wreaddir(dir))) { + if (wcscmp(ent->d_name, TEXT(".")) == 0 || wcscmp(ent->d_name, TEXT("..")) == 0) { + continue; + } + + wstring filepath = ent->d_name; + const wchar_t* const dir_delim_end = + w_possible_dir_delim + sizeof(w_possible_dir_delim); + + if (!log_directory.empty() && + std::find(w_possible_dir_delim, dir_delim_end, + log_directory[log_directory.size() - 1]) != dir_delim_end) { + filepath = log_directory + filepath; + } + + if (IsLogFromCurrentProject(filepath, base_filename, + filename_extension) && + IsLogLastModifiedOver(filepath, days)) { + overdue_log_names.push_back(filepath); + } + } + wclosedir(dir); + } + return overdue_log_names; +} +#else vector LogCleaner::GetOverdueLogNames( string log_directory, unsigned int days, const string& base_filename, const string& filename_extension) const { @@ -1444,7 +1702,96 @@ vector LogCleaner::GetOverdueLogNames( return overdue_log_names; } +#endif + +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +bool LogCleaner::IsLogFromCurrentProject( + const wstring& filepath, const wstring& base_filename, + const string& filename_extension) const { + // We should remove duplicated delimiters from `base_filename`, e.g., + // before: "/tmp//.." + // after: "/tmp/.." + wstring cleaned_base_filename; + + const wchar_t* const dir_delim_end = + w_possible_dir_delim + sizeof(w_possible_dir_delim); + + size_t real_filepath_size = filepath.size(); + for (wchar_t c : base_filename) { + if (cleaned_base_filename.empty()) { + cleaned_base_filename += c; + } else if (std::find(w_possible_dir_delim, dir_delim_end, c) == + dir_delim_end || + (!cleaned_base_filename.empty() && + c != cleaned_base_filename[cleaned_base_filename.size() - 1])) { + cleaned_base_filename += c; + } + } + + // Return early if the filename doesn't start with `cleaned_base_filename`. + if (filepath.find(cleaned_base_filename) != 0) { + return false; + } + + // Check if in the string `filename_extension` is right next to + // `cleaned_base_filename` in `filepath` if the user + // has set a custom filename extension. + if (!filename_extension.empty()) { + if (cleaned_base_filename.size() >= real_filepath_size) { + return false; + } + // for origin version, `filename_extension` is middle of the `filepath`. + wstring ext = filepath.substr(cleaned_base_filename.size(), + filename_extension.size()); + if (ext == ConvertString2WString(filename_extension)) { + cleaned_base_filename += ConvertString2WString(filename_extension); + } else { + // for new version, `filename_extension` is right of the `filepath`. + if (filename_extension.size() >= real_filepath_size) { + return false; + } + real_filepath_size = filepath.size() - filename_extension.size(); + if (filepath.substr(real_filepath_size) != ConvertString2WString(filename_extension)) { + return false; + } + } + } + + // The characters after `cleaned_base_filename` should match the format: + // YYYYMMDD-HHMMSS.pid + for (size_t i = cleaned_base_filename.size(); i < real_filepath_size; i++) { + const wchar_t& c = filepath[i]; + + if (i <= cleaned_base_filename.size() + 7) { // 0 ~ 7 : YYYYMMDD + if (c < TEXT('0') || c >TEXT('9')) { + return false; + } + } else if (i == cleaned_base_filename.size() + 8) { // 8: - + if (c != TEXT('-')) { + return false; + } + + } else if (i <= cleaned_base_filename.size() + 14) { // 9 ~ 14: HHMMSS + if (c < TEXT('0') || c > TEXT('9')) { + return false; + } + + } else if (i == cleaned_base_filename.size() + 15) { // 15: . + if (c != TEXT('.')) { + return false; + } + + } else if (i >= cleaned_base_filename.size() + 16) { // 16+: pid + if (c < TEXT('0') || c > TEXT('9')) { + return false; + } + } + } + + return true; +} +#else bool LogCleaner::IsLogFromCurrentProject(const string& filepath, const string& base_filename, const string& filename_extension) const { @@ -1521,7 +1868,25 @@ bool LogCleaner::IsLogFromCurrentProject(const string& filepath, return true; } +#endif + +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +bool LogCleaner::IsLogLastModifiedOver(const wstring& filepath, + unsigned int days) const { + // Try to get the last modified time of this file. + struct _stat file_stat; + + if (_wstat(filepath.c_str(), &file_stat) == 0) { + const time_t seconds_in_a_day = 60 * 60 * 24; + time_t last_modified_time = file_stat.st_mtime; + time_t current_time = time(nullptr); + return difftime(current_time, last_modified_time) > days * seconds_in_a_day; + } + // If failed to get file stat, don't return true! + return false; +} +#else bool LogCleaner::IsLogLastModifiedOver(const string& filepath, unsigned int days) const { // Try to get the last modified time of this file. @@ -1537,6 +1902,7 @@ bool LogCleaner::IsLogLastModifiedOver(const string& filepath, // If failed to get file stat, don't return true! return false; } +#endif // } // namespace @@ -2065,10 +2431,15 @@ void FlushLogFiles(LogSeverity min_severity) { void FlushLogFilesUnsafe(LogSeverity min_severity) { LogDestination::FlushLogFilesUnsafe(min_severity); } - +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +void SetLogDestination(LogSeverity severity, const wchar_t* base_filename) { + LogDestination::SetLogDestination(severity, base_filename); +} +#else void SetLogDestination(LogSeverity severity, const char* base_filename) { LogDestination::SetLogDestination(severity, base_filename); } +#endif void SetLogSymlink(LogSeverity severity, const char* symlink_basename) { LogDestination::SetLogSymlink(severity, symlink_basename); @@ -2342,32 +2713,39 @@ bool SendEmail(const char*dest, const char *subject, const char*body){ return SendEmailInternal(dest, subject, body, true); } +#ifdef GLOG_OS_WINDOWS +#ifdef UNICODE +static void GetTempDirectories(vector* list) { +#else static void GetTempDirectories(vector* list) { +#endif list->clear(); -#ifdef GLOG_OS_WINDOWS // On windows we'll try to find a directory in this order: // C:/Documents & Settings/whomever/TEMP (or whatever GetTempPath() is) // C:/TMP/ // C:/TEMP/ // C:/WINDOWS/ or C:/WINNT/ // . - char tmp[MAX_PATH]; - if (GetTempPathA(MAX_PATH, tmp)) - list->push_back(tmp); - list->push_back("C:\\tmp\\"); - list->push_back("C:\\temp\\"); + TCHAR tmp[MAX_PATH]; + if (GetTempPath(MAX_PATH, tmp)) list->push_back(tmp); + list->push_back(TEXT("C:\\tmp\\")); + list->push_back(TEXT("C:\\temp\\")); +} #else +static void GetTempDirectories(vector* list) { + list->clear(); // Directories, in order of preference. If we find a dir that // exists, we stop adding other less-preferred dirs - const char * candidates[] = { - // Non-null only during unittest/regtest - getenv("TEST_TMPDIR"), + const char* candidates[] = { + // Non-null only during unittest/regtest + getenv("TEST_TMPDIR"), - // Explicitly-supplied temp dirs - getenv("TMPDIR"), getenv("TMP"), + // Explicitly-supplied temp dirs + getenv("TMPDIR"), + getenv("TMP"), - // If all else fails - "/tmp", + // If all else fails + "/tmp", }; for (auto d : candidates) { @@ -2386,21 +2764,50 @@ static void GetTempDirectories(vector* list) { return; } } - -#endif } +#endif // GLOG_OS_WINDOWS -static vector* logging_directories_list; + + +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static vector* logging_directories_list; +const vector& GetLoggingDirectories() { + // Not strictly thread-safe but we're called early in InitGoogle(). + if (logging_directories_list == nullptr) { + logging_directories_list = new vector; + + if (!FLAGS_log_dir.empty()) { + // Ensure the specified path ends with a directory delimiter. + if (std::find(std::begin(possible_dir_delim), + std::end(possible_dir_delim), + FLAGS_log_dir.back()) == std::end(possible_dir_delim)) { + logging_directories_list->push_back(FLAGS_log_dir + TEXT("/")); + } else { + logging_directories_list->push_back(FLAGS_log_dir); + } + } else { + GetTempDirectories(logging_directories_list); + TCHAR tmp[MAX_PATH]; + if (GetWindowsDirectory(tmp, MAX_PATH)) + logging_directories_list->push_back(tmp); + logging_directories_list->push_back(TEXT(".\\")); + } + } + return *logging_directories_list; +} +#else +static vector* logging_directories_list; const vector& GetLoggingDirectories() { // Not strictly thread-safe but we're called early in InitGoogle(). if (logging_directories_list == nullptr) { logging_directories_list = new vector; - if ( !FLAGS_log_dir.empty() ) { + if (!FLAGS_log_dir.empty()) { // Ensure the specified path ends with a directory delimiter. - if (std::find(std::begin(possible_dir_delim), std::end(possible_dir_delim), - FLAGS_log_dir.back()) == std::end(possible_dir_delim)) { + if (std::find(std::begin(possible_dir_delim), + std::end(possible_dir_delim), + FLAGS_log_dir.back()) == std::end(possible_dir_delim)) { logging_directories_list->push_back(FLAGS_log_dir + "/"); } else { logging_directories_list->push_back(FLAGS_log_dir); @@ -2419,6 +2826,9 @@ const vector& GetLoggingDirectories() { } return *logging_directories_list; } +#endif // + + void TestOnly_ClearLoggingDirectoriesList() { fprintf(stderr, "TestOnly_ClearLoggingDirectoriesList should only be " @@ -2426,7 +2836,21 @@ void TestOnly_ClearLoggingDirectoriesList() { delete logging_directories_list; logging_directories_list = nullptr; } - +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +void GetExistingTempDirectories(vector* list) { + GetTempDirectories(list); + auto i_dir = list->begin(); + while (i_dir != list->end()) { + // zero arg to access means test for existence; no constant + // defined on windows + if (_waccess(i_dir->c_str(), 0)) { + i_dir = list->erase(i_dir); + } else { + ++i_dir; + } + } +} +#else void GetExistingTempDirectories(vector* list) { GetTempDirectories(list); auto i_dir = list->begin(); @@ -2440,6 +2864,7 @@ void GetExistingTempDirectories(vector* list) { } } } +#endif // void TruncateLogFile(const char *path, uint64 limit, uint64 keep) { #if defined(HAVE_UNISTD_H) || defined(HAVE__CHSIZE_S) diff --git a/src/logging_unittest.cc b/src/logging_unittest.cc index ddd434f12..b8861313a 100644 --- a/src/logging_unittest.cc +++ b/src/logging_unittest.cc @@ -264,8 +264,13 @@ int main(int argc, char **argv) { TestSTREQ(); // TODO: The golden test portion of this test is very flakey. +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) EXPECT_TRUE( - MungeAndDiffTestStderr(FLAGS_test_srcdir + "/src/logging_unittest.err")); + MungeAndDiffTestStderr(ConvertString2WString(FLAGS_test_srcdir + "/src/logging_unittest.err"))); +#else + EXPECT_TRUE(MungeAndDiffTestStderr( + FLAGS_test_srcdir + "/src/logging_unittest.err")); +#endif FLAGS_logtostderr = false; @@ -281,8 +286,15 @@ int main(int argc, char **argv) { TestCHECK(); TestDCHECK(); TestSTREQ(); - EXPECT_TRUE( - MungeAndDiffTestStdout(FLAGS_test_srcdir + "/src/logging_unittest.out")); +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + EXPECT_TRUE(MungeAndDiffTestStdout( + ConvertString2WString(FLAGS_test_srcdir + "/src/logging_unittest.out"))); +#else + EXPECT_TRUE(MungeAndDiffTestStdout( + FLAGS_test_srcdir + "/src/logging_unittest.out")); +#endif // + + FLAGS_logtostdout = false; TestBasename(); @@ -699,6 +711,27 @@ TEST(DeathCheckNN, Simple) { } // Get list of file names that match pattern +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static void GetFiles(const wstring& pattern, vector* files) { + files->clear(); + WIN32_FIND_DATAW data; + HANDLE handle = FindFirstFileW(pattern.c_str(), &data); + size_t index = pattern.rfind('\\'); + if (index == string::npos) { + LOG(FATAL) << "No directory separator."; + } + const wstring dirname = pattern.substr(0, index + 1); + if (handle == INVALID_HANDLE_VALUE) { + // Finding no files is OK. + return; + } + do { + files->push_back(dirname + data.cFileName); + } while (FindNextFileW(handle, &data)); + BOOL result = FindClose(handle); + LOG_SYSRESULT(result != 0); +} +#else static void GetFiles(const string& pattern, vector* files) { files->clear(); #if defined(HAVE_GLOB_H) @@ -730,8 +763,18 @@ static void GetFiles(const string& pattern, vector* files) { # error There is no way to do glob. #endif } +#endif // Delete files patching pattern +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static void DeleteFiles(const wstring& pattern) { + vector files; + GetFiles(pattern, &files); + for (auto& file : files) { + CHECK(_wunlink(file.c_str()) == 0) << ": " << strerror(errno); + } +} +#else static void DeleteFiles(const string& pattern) { vector files; GetFiles(pattern, &files); @@ -739,15 +782,30 @@ static void DeleteFiles(const string& pattern) { CHECK(unlink(file.c_str()) == 0) << ": " << strerror(errno); } } +#endif //check string is in file (or is *NOT*, depending on optional checkInFileOrNot) -static void CheckFile(const string& name, const string& expected_string, const bool checkInFileOrNot = true) { +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static void CheckFile(const wstring& name, const string& expected_string, + const bool checkInFileOrNot = true) { + vector files; + GetFiles(name + TEXT("*"), &files); + CHECK_EQ(files.size(), 1UL); + + FILE* file = _wfopen(files[0].c_str(), TEXT("r")); + CHECK(file != nullptr) << ": could not open " << ConvertWString2String(files[0]) ; + +#else +static void CheckFile(const string& name, const string& expected_string, + const bool checkInFileOrNot = true) { vector files; GetFiles(name + "*", &files); CHECK_EQ(files.size(), 1UL); FILE* file = fopen(files[0].c_str(), "r"); CHECK(file != nullptr) << ": could not open " << files[0]; + +#endif char buf[1000]; while (fgets(buf, sizeof(buf), file) != nullptr) { char* first = strstr(buf, expected_string.c_str()); @@ -759,9 +817,30 @@ static void CheckFile(const string& name, const string& expected_string, const b } } fclose(file); - LOG(FATAL) << "Did " << (checkInFileOrNot? "not " : "") << "find " << expected_string << " in " << files[0]; +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + LOG(FATAL) << "Did " << (checkInFileOrNot? "not " : "") << "find " << expected_string << " in " << ConvertWString2String(files[0]); +#else + LOG(FATAL) << "Did " << (checkInFileOrNot ? "not " : "") << "find " + << expected_string << " in " << files[0]; + #endif } +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static void TestBasename() { + fprintf(stderr, "==== Test setting log file basename\n"); + const wstring dest = FLAGS_test_tmpdir + TEXT("/logging_test_basename"); + DeleteFiles(dest + TEXT("*")); + + SetLogDestination(GLOG_INFO, dest.c_str()); + LOG(INFO) << "message to new base"; + FlushLogFiles(GLOG_INFO); + CheckFile(dest, "message to new base"); + + // Release file handle for the destination file to unlock the file in Windows. + LogToStderr(); + DeleteFiles(dest + TEXT("*")); +} +#else static void TestBasename() { fprintf(stderr, "==== Test setting log file basename\n"); const string dest = FLAGS_test_tmpdir + "/logging_test_basename"; @@ -777,7 +856,38 @@ static void TestBasename() { LogToStderr(); DeleteFiles(dest + "*"); } +#endif + +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static void TestBasenameAppendWhenNoTimestamp() { + fprintf(stderr, + "==== Test setting log file basename without timestamp and appending " + "properly\n"); + const wstring dest = + FLAGS_test_tmpdir + TEXT("/logging_test_basename_append_when_no_timestamp"); + DeleteFiles(dest + TEXT("*")); + ofstream out(dest.c_str()); + out << "test preexisting content" << endl; + out.close(); + + CheckFile(dest, "test preexisting content"); + + FLAGS_timestamp_in_logfile_name = false; + SetLogDestination(GLOG_INFO, dest.c_str()); + LOG(INFO) << "message to new base, appending to preexisting file"; + FlushLogFiles(GLOG_INFO); + FLAGS_timestamp_in_logfile_name = true; + + // if the logging overwrites the file instead of appending it will fail. + CheckFile(dest, "test preexisting content"); + CheckFile(dest, "message to new base, appending to preexisting file"); + + // Release file handle for the destination file to unlock the file in Windows. + LogToStderr(); + DeleteFiles(dest + TEXT("*")); +} +#else static void TestBasenameAppendWhenNoTimestamp() { fprintf(stderr, "==== Test setting log file basename without timestamp and appending properly\n"); const string dest = FLAGS_test_tmpdir + "/logging_test_basename_append_when_no_timestamp"; @@ -803,6 +913,7 @@ static void TestBasenameAppendWhenNoTimestamp() { LogToStderr(); DeleteFiles(dest + "*"); } +#endif static void TestTwoProcessesWrite() { // test only implemented for platforms with fork & wait; the actual implementation relies on flock @@ -855,7 +966,30 @@ static void TestSymlink() { DeleteFiles(sym + "*"); #endif } +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static void TestExtension() { + fprintf(stderr, "==== Test setting log file extension\n"); + wstring dest = FLAGS_test_tmpdir + TEXT("/logging_test_extension"); + DeleteFiles(dest + TEXT("*")); + SetLogDestination(GLOG_INFO, dest.c_str()); + SetLogFilenameExtension("specialextension"); + LOG(INFO) << "message to new extension"; + FlushLogFiles(GLOG_INFO); + CheckFile(dest, "message to new extension"); + + // Check that file name ends with extension + vector filenames; + GetFiles(dest + TEXT("*"), &filenames); + CHECK_EQ(filenames.size(), 1UL); + CHECK(wcsstr(filenames[0].c_str(), TEXT("specialextension")) != nullptr); + + // Release file handle for the destination file to unlock the file in Windows. + LogToStderr(); + DeleteFiles(dest + TEXT("*")); +} + +#else static void TestExtension() { fprintf(stderr, "==== Test setting log file extension\n"); string dest = FLAGS_test_tmpdir + "/logging_test_extension"; @@ -877,6 +1011,7 @@ static void TestExtension() { LogToStderr(); DeleteFiles(dest + "*"); } +#endif struct MyLogger : public base::Logger { string data; diff --git a/src/utilities.cc b/src/utilities.cc index f8254704c..95a164a39 100644 --- a/src/utilities.cc +++ b/src/utilities.cc @@ -310,21 +310,35 @@ const char* const_basename(const char* filepath) { #endif return base ? (base+1) : filepath; } - +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +static std::wstring g_my_user_name; +const std::wstring& MyUserName() { return g_my_user_name; } +#else static string g_my_user_name; -const string& MyUserName() { - return g_my_user_name; -} +const string& MyUserName() { return g_my_user_name; } +#endif // + static void MyUserNameInitializer() { // TODO(hamaji): Probably this is not portable. -#if defined(GLOG_OS_WINDOWS) +#ifdef GLOG_OS_WINDOWS +#ifdef UNICODE + std::wstring user = getenvw(TEXT("USERNAME")); + if (!user.empty()) { + g_my_user_name = user; + } +#else const char* user = getenv("USERNAME"); + if (user != nullptr) { + g_my_user_name = user; + } +#endif // UNICODE #else const char* user = getenv("USER"); -#endif if (user != nullptr) { g_my_user_name = user; - } else { + } +#endif + else { #if defined(HAVE_PWD_H) && defined(HAVE_UNISTD_H) struct passwd pwd; struct passwd* result = nullptr; @@ -339,7 +353,12 @@ static void MyUserNameInitializer() { } #endif if (g_my_user_name.empty()) { - g_my_user_name = "invalid-user"; +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) + g_my_user_name = TEXT("invalid-user"); +#else + g_my_user_name = "invalid-user"; +#endif // + } } } @@ -383,6 +402,23 @@ void ShutdownGoogleLoggingUtilities() { closelog(); #endif } +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +std::wstring ConvertString2WString(const std::string& str) { + int iLen = MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.size(), NULL, NULL); + std::wstring wstr; + wstr.resize(iLen); + MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.size(), &wstr[0], wstr.size()); + return wstr; +} +std::string ConvertWString2String(const std::wstring& wstr) { + int iLen = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), NULL, NULL, NULL, NULL); + std::string str; + str.resize(iLen); + WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), &str[0], + str.size(), NULL, NULL); + return str; +} +#endif // } // namespace glog_internal_namespace_ diff --git a/src/utilities.h b/src/utilities.h index ae34d8d4c..29c921f07 100644 --- a/src/utilities.h +++ b/src/utilities.h @@ -153,7 +153,12 @@ bool PidHasChanged(); pid_t GetTID(); +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +const std::wstring& MyUserName(); +#else const std::string& MyUserName(); +#endif // + // Get the part of filepath after the last path separator. // (Doesn't modify filepath, contrary to basename() in libgen.h.) @@ -206,9 +211,14 @@ void SetCrashReason(const CrashReason* r); void InitGoogleLoggingUtilities(const char* argv0); void ShutdownGoogleLoggingUtilities(); - +#if defined(GLOG_OS_WINDOWS) && defined(UNICODE) +std::wstring ConvertString2WString(const std::string& str); +std::string ConvertWString2String(const std::wstring& wstr); +#endif // } // namespace glog_internal_namespace_ + + _END_GOOGLE_NAMESPACE_ using namespace GOOGLE_NAMESPACE::glog_internal_namespace_;