/src/mozilla-central/xpcom/base/Logging.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "mozilla/Logging.h" |
8 | | |
9 | | #include <algorithm> |
10 | | |
11 | | #include "mozilla/ClearOnShutdown.h" |
12 | | #include "mozilla/FileUtils.h" |
13 | | #include "mozilla/Mutex.h" |
14 | | #include "mozilla/StaticPtr.h" |
15 | | #include "mozilla/Printf.h" |
16 | | #include "mozilla/Atomics.h" |
17 | | #include "mozilla/Sprintf.h" |
18 | | #include "mozilla/UniquePtrExtensions.h" |
19 | | #include "MainThreadUtils.h" |
20 | | #include "nsClassHashtable.h" |
21 | | #include "nsDebug.h" |
22 | | #include "nsDebugImpl.h" |
23 | | #include "NSPRLogModulesParser.h" |
24 | | #include "LogCommandLineHandler.h" |
25 | | |
26 | | #include "prenv.h" |
27 | | #ifdef XP_WIN |
28 | | #include <process.h> |
29 | | #else |
30 | | #include <sys/types.h> |
31 | | #include <unistd.h> |
32 | | #endif |
33 | | |
34 | | // NB: Initial amount determined by auditing the codebase for the total amount |
35 | | // of unique module names and padding up to the next power of 2. |
36 | | const uint32_t kInitialModuleCount = 256; |
37 | | // When rotate option is added to the modules list, this is the hardcoded |
38 | | // number of files we create and rotate. When there is rotate:40, |
39 | | // we will keep four files per process, each limited to 10MB. Sum is 40MB, |
40 | | // the given limit. |
41 | | // |
42 | | // (Note: When this is changed to be >= 10, SandboxBroker::LaunchApp must add |
43 | | // another rule to allow logfile.?? be written by content processes.) |
44 | | const uint32_t kRotateFilesNumber = 4; |
45 | | |
46 | | namespace mozilla { |
47 | | |
48 | | LazyLogModule::operator LogModule*() |
49 | 23.5M | { |
50 | 23.5M | // NB: The use of an atomic makes the reading and assignment of mLog |
51 | 23.5M | // thread-safe. There is a small chance that mLog will be set more |
52 | 23.5M | // than once, but that's okay as it will be set to the same LogModule |
53 | 23.5M | // instance each time. Also note LogModule::Get is thread-safe. |
54 | 23.5M | LogModule* tmp = mLog; |
55 | 23.5M | if (MOZ_UNLIKELY(!tmp)) { |
56 | 75 | tmp = LogModule::Get(mLogName); |
57 | 75 | mLog = tmp; |
58 | 75 | } |
59 | 23.5M | |
60 | 23.5M | mCanary.Check(); |
61 | 23.5M | |
62 | 23.5M | return tmp; |
63 | 23.5M | } |
64 | | |
65 | | namespace detail { |
66 | | |
67 | | void log_print(const LogModule* aModule, |
68 | | LogLevel aLevel, |
69 | | const char* aFmt, ...) |
70 | 0 | { |
71 | 0 | va_list ap; |
72 | 0 | va_start(ap, aFmt); |
73 | 0 | aModule->Printv(aLevel, aFmt, ap); |
74 | 0 | va_end(ap); |
75 | 0 | } |
76 | | |
77 | | } // detail |
78 | | |
79 | | LogLevel |
80 | | ToLogLevel(int32_t aLevel) |
81 | 0 | { |
82 | 0 | aLevel = std::min(aLevel, static_cast<int32_t>(LogLevel::Verbose)); |
83 | 0 | aLevel = std::max(aLevel, static_cast<int32_t>(LogLevel::Disabled)); |
84 | 0 | return static_cast<LogLevel>(aLevel); |
85 | 0 | } |
86 | | |
87 | | const char* |
88 | 0 | ToLogStr(LogLevel aLevel) { |
89 | 0 | switch (aLevel) { |
90 | 0 | case LogLevel::Error: |
91 | 0 | return "E"; |
92 | 0 | case LogLevel::Warning: |
93 | 0 | return "W"; |
94 | 0 | case LogLevel::Info: |
95 | 0 | return "I"; |
96 | 0 | case LogLevel::Debug: |
97 | 0 | return "D"; |
98 | 0 | case LogLevel::Verbose: |
99 | 0 | return "V"; |
100 | 0 | case LogLevel::Disabled: |
101 | 0 | default: |
102 | 0 | MOZ_CRASH("Invalid log level."); |
103 | 0 | return ""; |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | | namespace detail { |
108 | | |
109 | | /** |
110 | | * A helper class providing reference counting for FILE*. |
111 | | * It encapsulates the following: |
112 | | * - the FILE handle |
113 | | * - the order number it was created for when rotating (actual path) |
114 | | * - number of active references |
115 | | */ |
116 | | class LogFile |
117 | | { |
118 | | FILE* mFile; |
119 | | uint32_t mFileNum; |
120 | | |
121 | | public: |
122 | | LogFile(FILE* aFile, uint32_t aFileNum) |
123 | | : mFile(aFile) |
124 | | , mFileNum(aFileNum) |
125 | | , mNextToRelease(nullptr) |
126 | 0 | { |
127 | 0 | } |
128 | | |
129 | | ~LogFile() |
130 | 0 | { |
131 | 0 | fclose(mFile); |
132 | 0 | delete mNextToRelease; |
133 | 0 | } |
134 | | |
135 | 0 | FILE* File() const { return mFile; } |
136 | 0 | uint32_t Num() const { return mFileNum; } |
137 | | |
138 | | LogFile* mNextToRelease; |
139 | | }; |
140 | | |
141 | | const char* |
142 | | ExpandPIDMarker(const char* aFilename, char (&buffer)[2048]) |
143 | 0 | { |
144 | 0 | MOZ_ASSERT(aFilename); |
145 | 0 | static const char kPIDToken[] = "%PID"; |
146 | 0 | const char* pidTokenPtr = strstr(aFilename, kPIDToken); |
147 | 0 | if (pidTokenPtr && |
148 | 0 | SprintfLiteral(buffer, "%.*s%s%d%s", |
149 | 0 | static_cast<int>(pidTokenPtr - aFilename), aFilename, |
150 | 0 | XRE_IsParentProcess() ? "-main." : "-child.", |
151 | 0 | base::GetCurrentProcId(), |
152 | 0 | pidTokenPtr + strlen(kPIDToken)) > 0) |
153 | 0 | { |
154 | 0 | return buffer; |
155 | 0 | } |
156 | 0 | |
157 | 0 | return aFilename; |
158 | 0 | } |
159 | | |
160 | | } // detail |
161 | | |
162 | | namespace { |
163 | | // Helper method that initializes an empty va_list to be empty. |
164 | | void empty_va(va_list *va, ...) |
165 | 0 | { |
166 | 0 | va_start(*va, va); |
167 | 0 | va_end(*va); |
168 | 0 | } |
169 | | } |
170 | | |
171 | | class LogModuleManager |
172 | | { |
173 | | public: |
174 | | LogModuleManager() |
175 | | // As for logging atomics, don't preserve behavior for this lock when |
176 | | // recording/replaying. |
177 | | : mModulesLock("logmodules", recordreplay::Behavior::DontPreserve) |
178 | | , mModules(kInitialModuleCount) |
179 | | , mPrintEntryCount(0) |
180 | | , mOutFile(nullptr) |
181 | | , mToReleaseFile(nullptr) |
182 | | , mOutFileNum(0) |
183 | | , mOutFilePath(strdup("")) |
184 | | , mMainThread(PR_GetCurrentThread()) |
185 | | , mSetFromEnv(false) |
186 | | , mAddTimestamp(false) |
187 | | , mIsRaw(false) |
188 | | , mIsSync(false) |
189 | | , mRotate(0) |
190 | | , mInitialized(false) |
191 | 3 | { |
192 | 3 | } |
193 | | |
194 | | ~LogModuleManager() |
195 | 0 | { |
196 | 0 | detail::LogFile* logFile = mOutFile.exchange(nullptr); |
197 | 0 | delete logFile; |
198 | 0 | } |
199 | | |
200 | | /** |
201 | | * Loads config from command line args or env vars if present, in |
202 | | * this specific order of priority. |
203 | | * |
204 | | * Notes: |
205 | | * |
206 | | * 1) This function is only intended to be called once per session. |
207 | | * 2) None of the functions used in Init should rely on logging. |
208 | | */ |
209 | | void Init(int argc, char* argv[]) |
210 | 3 | { |
211 | 3 | MOZ_DIAGNOSTIC_ASSERT(!mInitialized); |
212 | 3 | mInitialized = true; |
213 | 3 | |
214 | 3 | LoggingHandleCommandLineArgs(argc, static_cast<char const* const*>(argv), |
215 | 3 | [](nsACString const& env) { |
216 | 0 | // We deliberately set/rewrite the environment variables |
217 | 0 | // so that when child processes are spawned w/o passing |
218 | 0 | // the arguments they still inherit the logging settings |
219 | 0 | // as well as sandboxing can be correctly set. |
220 | 0 | // Scripts can pass -MOZ_LOG=$MOZ_LOG,modules as an argument |
221 | 0 | // to merge existing settings, if required. |
222 | 0 |
|
223 | 0 | // PR_SetEnv takes ownership of the string. |
224 | 0 | PR_SetEnv(ToNewCString(env)); |
225 | 0 | }); |
226 | 3 | |
227 | 3 | bool shouldAppend = false; |
228 | 3 | bool addTimestamp = false; |
229 | 3 | bool isSync = false; |
230 | 3 | bool isRaw = false; |
231 | 3 | int32_t rotate = 0; |
232 | 3 | const char* modules = PR_GetEnv("MOZ_LOG"); |
233 | 3 | if (!modules || !modules[0]) { |
234 | 3 | modules = PR_GetEnv("MOZ_LOG_MODULES"); |
235 | 3 | if (modules) { |
236 | 0 | NS_WARNING("MOZ_LOG_MODULES is deprecated." |
237 | 0 | "\nPlease use MOZ_LOG instead."); |
238 | 0 | } |
239 | 3 | } |
240 | 3 | if (!modules || !modules[0]) { |
241 | 3 | modules = PR_GetEnv("NSPR_LOG_MODULES"); |
242 | 3 | if (modules) { |
243 | 0 | NS_WARNING("NSPR_LOG_MODULES is deprecated." |
244 | 0 | "\nPlease use MOZ_LOG instead."); |
245 | 0 | } |
246 | 3 | } |
247 | 3 | |
248 | 3 | // Need to capture `this` since `sLogModuleManager` is not set until after |
249 | 3 | // initialization is complete. |
250 | 3 | NSPRLogModulesParser(modules, |
251 | 3 | [this, &shouldAppend, &addTimestamp, &isSync, &isRaw, &rotate] |
252 | 3 | (const char* aName, LogLevel aLevel, int32_t aValue) mutable { |
253 | 0 | if (strcmp(aName, "append") == 0) { |
254 | 0 | shouldAppend = true; |
255 | 0 | } else if (strcmp(aName, "timestamp") == 0) { |
256 | 0 | addTimestamp = true; |
257 | 0 | } else if (strcmp(aName, "sync") == 0) { |
258 | 0 | isSync = true; |
259 | 0 | } else if (strcmp(aName, "raw") == 0) { |
260 | 0 | isRaw = true; |
261 | 0 | } else if (strcmp(aName, "rotate") == 0) { |
262 | 0 | rotate = (aValue << 20) / kRotateFilesNumber; |
263 | 0 | } else { |
264 | 0 | this->CreateOrGetModule(aName)->SetLevel(aLevel); |
265 | 0 | } |
266 | 0 | }); |
267 | 3 | |
268 | 3 | // Rotate implies timestamp to make the files readable |
269 | 3 | mAddTimestamp = addTimestamp || rotate > 0; |
270 | 3 | mIsSync = isSync; |
271 | 3 | mIsRaw = isRaw; |
272 | 3 | mRotate = rotate; |
273 | 3 | |
274 | 3 | if (rotate > 0 && shouldAppend) { |
275 | 0 | NS_WARNING("MOZ_LOG: when you rotate the log, you cannot use append!"); |
276 | 0 | } |
277 | 3 | |
278 | 3 | const char* logFile = PR_GetEnv("MOZ_LOG_FILE"); |
279 | 3 | if (!logFile || !logFile[0]) { |
280 | 3 | logFile = PR_GetEnv("NSPR_LOG_FILE"); |
281 | 3 | } |
282 | 3 | |
283 | 3 | if (logFile && logFile[0]) { |
284 | 0 | char buf[2048]; |
285 | 0 | logFile = detail::ExpandPIDMarker(logFile, buf); |
286 | 0 | mOutFilePath.reset(strdup(logFile)); |
287 | 0 |
|
288 | 0 | if (mRotate > 0) { |
289 | 0 | // Delete all the previously captured files, including non-rotated |
290 | 0 | // log files, so that users don't complain our logs eat space even |
291 | 0 | // after the rotate option has been added and don't happen to send |
292 | 0 | // us old large logs along with the rotated files. |
293 | 0 | remove(mOutFilePath.get()); |
294 | 0 | for (uint32_t i = 0; i < kRotateFilesNumber; ++i) { |
295 | 0 | RemoveFile(i); |
296 | 0 | } |
297 | 0 | } |
298 | 0 |
|
299 | 0 | mOutFile = OpenFile(shouldAppend, mOutFileNum); |
300 | 0 | mSetFromEnv = true; |
301 | 0 | } |
302 | 3 | } |
303 | | |
304 | | void SetLogFile(const char* aFilename) |
305 | 0 | { |
306 | 0 | // For now we don't allow you to change the file at runtime. |
307 | 0 | if (mSetFromEnv) { |
308 | 0 | NS_WARNING("LogModuleManager::SetLogFile - Log file was set from the " |
309 | 0 | "MOZ_LOG_FILE environment variable."); |
310 | 0 | return; |
311 | 0 | } |
312 | 0 |
|
313 | 0 | const char * filename = aFilename ? aFilename : ""; |
314 | 0 | char buf[2048]; |
315 | 0 | filename = detail::ExpandPIDMarker(filename, buf); |
316 | 0 |
|
317 | 0 | // Can't use rotate at runtime yet. |
318 | 0 | MOZ_ASSERT(mRotate == 0, "We don't allow rotate for runtime logfile changes"); |
319 | 0 | mOutFilePath.reset(strdup(filename)); |
320 | 0 |
|
321 | 0 | // Exchange mOutFile and set it to be released once all the writes are done. |
322 | 0 | detail::LogFile* newFile = OpenFile(false, 0); |
323 | 0 | detail::LogFile* oldFile = mOutFile.exchange(newFile); |
324 | 0 |
|
325 | 0 | // Since we don't allow changing the logfile if MOZ_LOG_FILE is already set, |
326 | 0 | // and we don't allow log rotation when setting it at runtime, mToReleaseFile |
327 | 0 | // will be null, so we're not leaking. |
328 | 0 | DebugOnly<detail::LogFile*> prevFile = mToReleaseFile.exchange(oldFile); |
329 | 0 | MOZ_ASSERT(!prevFile, "Should be null because rotation is not allowed"); |
330 | 0 |
|
331 | 0 | // If we just need to release a file, we must force print, in order to |
332 | 0 | // trigger the closing and release of mToReleaseFile. |
333 | 0 | if (oldFile) { |
334 | 0 | va_list va; |
335 | 0 | empty_va(&va); |
336 | 0 | Print("Logger", LogLevel::Info, "Flushing old log files\n", va); |
337 | 0 | } |
338 | 0 | } |
339 | | |
340 | | uint32_t GetLogFile(char *aBuffer, size_t aLength) |
341 | 0 | { |
342 | 0 | uint32_t len = strlen(mOutFilePath.get()); |
343 | 0 | if (len + 1 > aLength) { |
344 | 0 | return 0; |
345 | 0 | } |
346 | 0 | snprintf(aBuffer, aLength, "%s", mOutFilePath.get()); |
347 | 0 | return len; |
348 | 0 | } |
349 | | |
350 | | void SetIsSync(bool aIsSync) |
351 | 0 | { |
352 | 0 | mIsSync = aIsSync; |
353 | 0 | } |
354 | | |
355 | | void SetAddTimestamp(bool aAddTimestamp) |
356 | 0 | { |
357 | 0 | mAddTimestamp = aAddTimestamp; |
358 | 0 | } |
359 | | |
360 | | detail::LogFile* OpenFile(bool aShouldAppend, uint32_t aFileNum) |
361 | 0 | { |
362 | 0 | FILE* file; |
363 | 0 |
|
364 | 0 | if (mRotate > 0) { |
365 | 0 | char buf[2048]; |
366 | 0 | SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum); |
367 | 0 |
|
368 | 0 | // rotate doesn't support append. |
369 | 0 | file = fopen(buf, "w"); |
370 | 0 | } else { |
371 | 0 | file = fopen(mOutFilePath.get(), aShouldAppend ? "a" : "w"); |
372 | 0 | } |
373 | 0 |
|
374 | 0 | if (!file) { |
375 | 0 | return nullptr; |
376 | 0 | } |
377 | 0 | |
378 | 0 | return new detail::LogFile(file, aFileNum); |
379 | 0 | } |
380 | | |
381 | | void RemoveFile(uint32_t aFileNum) |
382 | 0 | { |
383 | 0 | char buf[2048]; |
384 | 0 | SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum); |
385 | 0 | remove(buf); |
386 | 0 | } |
387 | | |
388 | | LogModule* CreateOrGetModule(const char* aName) |
389 | 75 | { |
390 | 75 | OffTheBooksMutexAutoLock guard(mModulesLock); |
391 | 75 | LogModule* module = nullptr; |
392 | 75 | if (!mModules.Get(aName, &module)) { |
393 | 75 | module = new LogModule(aName, LogLevel::Disabled); |
394 | 75 | mModules.Put(aName, module); |
395 | 75 | } |
396 | 75 | |
397 | 75 | return module; |
398 | 75 | } |
399 | | |
400 | | void Print(const char* aName, LogLevel aLevel, const char* aFmt, va_list aArgs) |
401 | | MOZ_FORMAT_PRINTF(4, 0) |
402 | 0 | { |
403 | 0 | // We don't do nuwa-style forking anymore, so our pid can't change. |
404 | 0 | static long pid = static_cast<long>(base::GetCurrentProcId()); |
405 | 0 | const size_t kBuffSize = 1024; |
406 | 0 | char buff[kBuffSize]; |
407 | 0 |
|
408 | 0 | char* buffToWrite = buff; |
409 | 0 | SmprintfPointer allocatedBuff; |
410 | 0 |
|
411 | 0 | va_list argsCopy; |
412 | 0 | va_copy(argsCopy, aArgs); |
413 | 0 | int charsWritten = VsprintfLiteral(buff, aFmt, argsCopy); |
414 | 0 | va_end(argsCopy); |
415 | 0 |
|
416 | 0 | if (charsWritten < 0) { |
417 | 0 | // Print out at least something. We must copy to the local buff, |
418 | 0 | // can't just assign aFmt to buffToWrite, since when |
419 | 0 | // buffToWrite != buff, we try to release it. |
420 | 0 | MOZ_ASSERT(false, "Probably incorrect format string in LOG?"); |
421 | 0 | strncpy(buff, aFmt, kBuffSize - 1); |
422 | 0 | buff[kBuffSize - 1] = '\0'; |
423 | 0 | charsWritten = strlen(buff); |
424 | 0 | } else if (static_cast<size_t>(charsWritten) >= kBuffSize - 1) { |
425 | 0 | // We may have maxed out, allocate a buffer instead. |
426 | 0 | allocatedBuff = mozilla::Vsmprintf(aFmt, aArgs); |
427 | 0 | buffToWrite = allocatedBuff.get(); |
428 | 0 | charsWritten = strlen(buffToWrite); |
429 | 0 | } |
430 | 0 |
|
431 | 0 | // Determine if a newline needs to be appended to the message. |
432 | 0 | const char* newline = ""; |
433 | 0 | if (charsWritten == 0 || buffToWrite[charsWritten - 1] != '\n') { |
434 | 0 | newline = "\n"; |
435 | 0 | } |
436 | 0 |
|
437 | 0 | FILE* out = stderr; |
438 | 0 |
|
439 | 0 | // In case we use rotate, this ensures the FILE is kept alive during |
440 | 0 | // its use. Increased before we load mOutFile. |
441 | 0 | ++mPrintEntryCount; |
442 | 0 |
|
443 | 0 | detail::LogFile* outFile = mOutFile; |
444 | 0 | if (outFile) { |
445 | 0 | out = outFile->File(); |
446 | 0 | } |
447 | 0 |
|
448 | 0 | // This differs from the NSPR format in that we do not output the |
449 | 0 | // opaque system specific thread pointer (ie pthread_t) cast |
450 | 0 | // to a long. The address of the current PR_Thread continues to be |
451 | 0 | // prefixed. |
452 | 0 | // |
453 | 0 | // Additionally we prefix the output with the abbreviated log level |
454 | 0 | // and the module name. |
455 | 0 | PRThread *currentThread = PR_GetCurrentThread(); |
456 | 0 | const char *currentThreadName = (mMainThread == currentThread) |
457 | 0 | ? "Main Thread" |
458 | 0 | : PR_GetThreadName(currentThread); |
459 | 0 |
|
460 | 0 | char noNameThread[40]; |
461 | 0 | if (!currentThreadName) { |
462 | 0 | SprintfLiteral(noNameThread, "Unnamed thread %p", currentThread); |
463 | 0 | currentThreadName = noNameThread; |
464 | 0 | } |
465 | 0 |
|
466 | 0 | if (!mAddTimestamp) { |
467 | 0 | if (!mIsRaw) { |
468 | 0 | fprintf_stderr(out, |
469 | 0 | "[%s %ld: %s]: %s/%s %s%s", |
470 | 0 | nsDebugImpl::GetMultiprocessMode(), pid, currentThreadName, ToLogStr(aLevel), |
471 | 0 | aName, buffToWrite, newline); |
472 | 0 | } else { |
473 | 0 | fprintf_stderr(out, "%s%s", buffToWrite, newline); |
474 | 0 | } |
475 | 0 | } else { |
476 | 0 | PRExplodedTime now; |
477 | 0 | PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now); |
478 | 0 | fprintf_stderr( |
479 | 0 | out, |
480 | 0 | "%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - [%s %ld: %s]: %s/%s %s%s", |
481 | 0 | now.tm_year, now.tm_month + 1, now.tm_mday, |
482 | 0 | now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec, |
483 | 0 | nsDebugImpl::GetMultiprocessMode(), pid, currentThreadName, ToLogStr(aLevel), |
484 | 0 | aName, buffToWrite, newline); |
485 | 0 | } |
486 | 0 |
|
487 | 0 | if (mIsSync) { |
488 | 0 | fflush(out); |
489 | 0 | } |
490 | 0 |
|
491 | 0 | if (mRotate > 0 && outFile) { |
492 | 0 | int32_t fileSize = ftell(out); |
493 | 0 | if (fileSize > mRotate) { |
494 | 0 | uint32_t fileNum = outFile->Num(); |
495 | 0 |
|
496 | 0 | uint32_t nextFileNum = fileNum + 1; |
497 | 0 | if (nextFileNum >= kRotateFilesNumber) { |
498 | 0 | nextFileNum = 0; |
499 | 0 | } |
500 | 0 |
|
501 | 0 | // And here is the trick. The current out-file remembers its order |
502 | 0 | // number. When no other thread shifted the global file number yet, |
503 | 0 | // we are the thread to open the next file. |
504 | 0 | if (mOutFileNum.compareExchange(fileNum, nextFileNum)) { |
505 | 0 | // We can work with mToReleaseFile because we are sure the |
506 | 0 | // mPrintEntryCount can't drop to zero now - the condition |
507 | 0 | // to actually delete what's stored in that member. |
508 | 0 | // And also, no other thread can enter this piece of code |
509 | 0 | // because mOutFile is still holding the current file with |
510 | 0 | // the non-shifted number. The compareExchange() above is |
511 | 0 | // a no-op for other threads. |
512 | 0 | outFile->mNextToRelease = mToReleaseFile; |
513 | 0 | mToReleaseFile = outFile; |
514 | 0 |
|
515 | 0 | mOutFile = OpenFile(false, nextFileNum); |
516 | 0 | } |
517 | 0 | } |
518 | 0 | } |
519 | 0 |
|
520 | 0 | if (--mPrintEntryCount == 0 && mToReleaseFile) { |
521 | 0 | // We were the last Print() entered, if there is a file to release |
522 | 0 | // do it now. exchange() is atomic and makes sure we release the file |
523 | 0 | // only once on one thread. |
524 | 0 | detail::LogFile* release = mToReleaseFile.exchange(nullptr); |
525 | 0 | delete release; |
526 | 0 | } |
527 | 0 | } |
528 | | |
529 | | private: |
530 | | OffTheBooksMutex mModulesLock; |
531 | | nsClassHashtable<nsCharPtrHashKey, LogModule> mModules; |
532 | | |
533 | | // Print() entry counter, actually reflects concurrent use of the current |
534 | | // output file. ReleaseAcquire ensures that manipulation with mOutFile |
535 | | // and mToReleaseFile is synchronized by manipulation with this value. |
536 | | Atomic<uint32_t, ReleaseAcquire> mPrintEntryCount; |
537 | | // File to write to. ReleaseAcquire because we need to sync mToReleaseFile |
538 | | // with this. |
539 | | Atomic<detail::LogFile*, ReleaseAcquire> mOutFile; |
540 | | // File to be released when reference counter drops to zero. This member |
541 | | // is assigned mOutFile when the current file has reached the limit. |
542 | | // It can be Relaxed, since it's synchronized with mPrintEntryCount |
543 | | // manipulation and we do atomic exchange() on it. |
544 | | Atomic<detail::LogFile*, Relaxed> mToReleaseFile; |
545 | | // The next file number. This is mostly only for synchronization sake. |
546 | | // Can have relaxed ordering, since we only do compareExchange on it which |
547 | | // is atomic regardless ordering. |
548 | | Atomic<uint32_t, Relaxed> mOutFileNum; |
549 | | // Just keeps the actual file path for further use. |
550 | | UniqueFreePtr<char[]> mOutFilePath; |
551 | | |
552 | | PRThread *mMainThread; |
553 | | bool mSetFromEnv; |
554 | | Atomic<bool, Relaxed> mAddTimestamp; |
555 | | Atomic<bool, Relaxed> mIsRaw; |
556 | | Atomic<bool, Relaxed> mIsSync; |
557 | | int32_t mRotate; |
558 | | bool mInitialized; |
559 | | }; |
560 | | |
561 | | StaticAutoPtr<LogModuleManager> sLogModuleManager; |
562 | | |
563 | | LogModule* |
564 | | LogModule::Get(const char* aName) |
565 | 75 | { |
566 | 75 | // This is just a pass through to the LogModuleManager so |
567 | 75 | // that the LogModuleManager implementation can be kept internal. |
568 | 75 | MOZ_ASSERT(sLogModuleManager != nullptr); |
569 | 75 | return sLogModuleManager->CreateOrGetModule(aName); |
570 | 75 | } |
571 | | |
572 | | void |
573 | | LogModule::SetLogFile(const char* aFilename) |
574 | 0 | { |
575 | 0 | MOZ_ASSERT(sLogModuleManager); |
576 | 0 | sLogModuleManager->SetLogFile(aFilename); |
577 | 0 | } |
578 | | |
579 | | uint32_t |
580 | | LogModule::GetLogFile(char *aBuffer, size_t aLength) |
581 | 0 | { |
582 | 0 | MOZ_ASSERT(sLogModuleManager); |
583 | 0 | return sLogModuleManager->GetLogFile(aBuffer, aLength); |
584 | 0 | } |
585 | | |
586 | | void |
587 | | LogModule::SetAddTimestamp(bool aAddTimestamp) |
588 | 0 | { |
589 | 0 | sLogModuleManager->SetAddTimestamp(aAddTimestamp); |
590 | 0 | } |
591 | | |
592 | | void |
593 | | LogModule::SetIsSync(bool aIsSync) |
594 | 0 | { |
595 | 0 | sLogModuleManager->SetIsSync(aIsSync); |
596 | 0 | } |
597 | | |
598 | | void |
599 | | LogModule::Init(int argc, char* argv[]) |
600 | 6 | { |
601 | 6 | // NB: This method is not threadsafe; it is expected to be called very early |
602 | 6 | // in startup prior to any other threads being run. |
603 | 6 | MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); |
604 | 6 | |
605 | 6 | if (sLogModuleManager) { |
606 | 3 | // Already initialized. |
607 | 3 | return; |
608 | 3 | } |
609 | 3 | |
610 | 3 | // NB: We intentionally do not register for ClearOnShutdown as that happens |
611 | 3 | // before all logging is complete. And, yes, that means we leak, but |
612 | 3 | // we're doing that intentionally. |
613 | 3 | |
614 | 3 | // Don't assign the pointer until after Init is called. This should help us |
615 | 3 | // detect if any of the functions called by Init somehow rely on logging. |
616 | 3 | auto mgr = new LogModuleManager(); |
617 | 3 | mgr->Init(argc, argv); |
618 | 3 | sLogModuleManager = mgr; |
619 | 3 | } |
620 | | |
621 | | void |
622 | | LogModule::Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const |
623 | 0 | { |
624 | 0 | MOZ_ASSERT(sLogModuleManager != nullptr); |
625 | 0 |
|
626 | 0 | // Forward to LogModule manager w/ level and name |
627 | 0 | sLogModuleManager->Print(Name(), aLevel, aFmt, aArgs); |
628 | 0 | } |
629 | | |
630 | | } // namespace mozilla |