1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
11 #include "circularlog.h"
13 CircularLog::CircularLog()
18 CircularLog::~CircularLog()
23 bool CircularLog::Init(const WCHAR* logname, const WCHAR* logHeader, DWORD maxSize)
27 m_LogFilename = logname;
28 m_LogFilename.Append(W(".log"));
30 m_LockFilename = logname;
31 m_LockFilename.Append(W(".lock"));
33 m_OldLogFilename = logname;
34 m_OldLogFilename .Append(W(".old.log"));
37 m_LogHeader = logHeader;
46 CheckForLogReset(FALSE);
51 void CircularLog::Shutdown()
56 void CircularLog::Log(const WCHAR* string)
63 HANDLE hLogFile = OpenFile();
64 if (hLogFile == INVALID_HANDLE_VALUE)
69 // Check for file limit only once in a while
70 if ((m_uLogCount % 16) == 0)
72 // First do a quick check without acquiring lock, optimizing for the common case where file is not overflow.
73 LARGE_INTEGER fileSize;
74 if (GetFileSizeEx(hLogFile, &fileSize) && fileSize.QuadPart > m_MaxSize)
76 // Must close existing handle before calling CheckForOverflow, and re-open it afterwards.
77 CloseHandle(hLogFile);
78 CheckForLogReset(TRUE);
79 hLogFile = OpenFile();
80 if (hLogFile == INVALID_HANDLE_VALUE)
88 // Replace \n with \r\n (we're writing to a binary file)
89 NewArrayHolder<WCHAR> pwszConvertedHolder = new WCHAR[wcslen(string)*2 + 1];
90 WCHAR* pD = pwszConvertedHolder;
91 WCHAR previous = W('\0');
92 for (const WCHAR* pS = string ; *pS != W('\0') ; pS++)
94 // We get mixed strings ('\n' and '\r\n'). So attempt to filter
95 // the ones that don't need to have a '\r' added.
96 if (*pS == W('\n') && previous != W('\r'))
110 // Convert to Utf8 to reduce typical log file size
111 SString logString(pwszConvertedHolder);
112 StackScratchBuffer bufUtf8;
113 COUNT_T cBytesUtf8 = 0;
114 const UTF8 * pszUtf8Log = logString.GetUTF8(bufUtf8, &cBytesUtf8);
115 // Remove null terminator from log entry buffer
119 WriteFile(hLogFile, pszUtf8Log, (DWORD)cBytesUtf8, &dwWritten, NULL);
120 CloseHandle(hLogFile);
124 BOOL CircularLog::CheckLogHeader()
126 BOOL fNeedsPushToBackupLog = FALSE;
128 // Check to make sure the header on the log is utf8, if it is not, push the current file to the .bak file and try again.
129 HANDLE hLogFile = WszCreateFile(
130 m_LogFilename.GetUnicode(),
132 FILE_SHARE_READ | FILE_SHARE_WRITE,
135 FILE_ATTRIBUTE_NORMAL,
138 if (hLogFile != INVALID_HANDLE_VALUE)
140 CHAR unicodeHeader []= {(char)0xef, (char)0xbb, (char)0xbf};
141 CHAR unicodeHeaderCheckBuf[_countof(unicodeHeader)];
143 DWORD dwRead = sizeof(unicodeHeaderCheckBuf);
144 fNeedsPushToBackupLog = !ReadFile(hLogFile, &unicodeHeaderCheckBuf, dwRead, &dwRead, NULL);
146 if (!fNeedsPushToBackupLog)
148 // Successfully read from file. Now check to ensure we read the right amount, and that we read the right data
149 if ((dwRead != sizeof(unicodeHeader)) || (0 != memcmp(unicodeHeader, unicodeHeaderCheckBuf, dwRead)))
151 fNeedsPushToBackupLog = TRUE;
154 CloseHandle(hLogFile);
157 return fNeedsPushToBackupLog;
160 #define MOVE_FILE_RETRY_TIME 100
161 #define MOVE_FILE_RETRY_COUNT 10
162 void CircularLog::CheckForLogReset(BOOL fOverflow)
169 for (int i = 0; i < MOVE_FILE_RETRY_COUNT; i++)
172 if (FAILED(lock.AcquireNoThrow(m_LockFilename.GetUnicode())))
174 // FileLockHolder::Acquire already has a retry loop, so don't retry if it fails.
178 BOOL fLogNeedsReset = FALSE;
182 WIN32_FILE_ATTRIBUTE_DATA fileData;
183 if (WszGetFileAttributesEx(
185 GetFileExInfoStandard,
191 unsigned __int64 fileSize =
192 (((unsigned __int64) fileData.nFileSizeHigh) << 32) |
193 ((unsigned __int64) fileData.nFileSizeLow);
196 if (fileSize > (unsigned __int64) m_MaxSize)
198 fLogNeedsReset = TRUE;
203 fLogNeedsReset = CheckLogHeader();
208 // Push current log out to .old file
209 BOOL success = WszMoveFileEx(
210 m_LogFilename.GetUnicode(),
211 m_OldLogFilename.GetUnicode(),
212 MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED);
214 if (success || GetLastError() != ERROR_SHARING_VIOLATION)
221 // Someone else moved the file before we can.
225 // Don't want to hold the lock while sleeping.
227 ClrSleepEx(MOVE_FILE_RETRY_TIME, FALSE);
231 #define OPEN_FILE_RETRY_TIME 100
232 #define OPEN_FILE_RETRY_COUNT 10
233 // Normally we open file with FILE_SHARE_WRITE, to avoid sharing violations between multiple threads or
234 // processes. However, when we create a new file, the Unicode header must be written at the beginning of the
235 // file. This can't be guaranteed with multiple writers, so we require exclusive access while creating a new
236 // file. Our algorithm is first try to open with OPEN_EXISTING and FILE_SHARE_WRITE, and if that fails, try
237 // again with OPEN_ALWAYS and no write sharing.
238 HANDLE CircularLog::OpenFile()
240 for (int i = 0; i < OPEN_FILE_RETRY_COUNT; i++)
242 // First try to open an existing file allowing shared write.
243 HANDLE hLogFile = WszCreateFile(
244 m_LogFilename.GetUnicode(),
246 FILE_SHARE_READ | FILE_SHARE_WRITE,
249 FILE_ATTRIBUTE_NORMAL,
252 if (hLogFile != INVALID_HANDLE_VALUE)
257 if (GetLastError() == ERROR_FILE_NOT_FOUND)
259 // Try to create an new file with exclusive access.
260 HANDLE hLogFile = WszCreateFile(
261 m_LogFilename.GetUnicode(),
266 FILE_ATTRIBUTE_NORMAL,
269 if (hLogFile != INVALID_HANDLE_VALUE)
271 LARGE_INTEGER fileSize;
272 if (! GetFileSizeEx(hLogFile, &fileSize))
274 CloseHandle(hLogFile);
275 return INVALID_HANDLE_VALUE;
278 // If the file size is 0, need to write the Unicode header (utf8 bom).
279 if (fileSize.QuadPart == 0)
281 CHAR unicodeHeader []= {(char)0xef, (char)0xbb, (char)0xbf};
283 WriteFile(hLogFile, &unicodeHeader, sizeof(unicodeHeader), &dwWritten, NULL);
287 // Convert to Utf8 to reduce typical log file size
288 StackScratchBuffer bufUtf8;
289 COUNT_T cBytesUtf8 = 0;
290 const UTF8 * pszUtf8Log = m_LogHeader.GetUTF8(bufUtf8, &cBytesUtf8);
291 // Remove null terminator from log entry buffer
294 WriteFile(hLogFile, pszUtf8Log, (DWORD)cBytesUtf8, &dwWritten, NULL);
301 if (GetLastError() != ERROR_SHARING_VIOLATION)
306 ClrSleepEx(OPEN_FILE_RETRY_TIME, FALSE);
309 return INVALID_HANDLE_VALUE;