Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / xpcom / base / Logging.cpp
blobfa15273465610930f90c5a94e5a2a9c0c7a591a2
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/. */
7 #include "mozilla/Logging.h"
9 #include <algorithm>
11 #include "base/process_util.h"
12 #include "GeckoProfiler.h"
13 #include "mozilla/ClearOnShutdown.h"
14 #include "mozilla/FileUtils.h"
15 #include "mozilla/LateWriteChecks.h"
16 #include "mozilla/Mutex.h"
17 #include "mozilla/StaticPtr.h"
18 #include "mozilla/Printf.h"
19 #include "mozilla/Atomics.h"
20 #include "mozilla/Sprintf.h"
21 #include "mozilla/UniquePtrExtensions.h"
22 #include "MainThreadUtils.h"
23 #include "nsClassHashtable.h"
24 #include "nsDebug.h"
25 #include "nsDebugImpl.h"
26 #include "nsPrintfCString.h"
27 #include "NSPRLogModulesParser.h"
28 #include "nsXULAppAPI.h"
29 #include "LogCommandLineHandler.h"
31 #include "prenv.h"
32 #ifdef XP_WIN
33 # include <fcntl.h>
34 # include <process.h>
35 #else
36 # include <sys/stat.h> // for umask()
37 # include <sys/types.h>
38 # include <unistd.h>
39 #endif
41 // NB: Amount determined by performing a typical browsing session and finding
42 // the maximum number of modules instantiated, and padding up to the next
43 // power of 2.
44 const uint32_t kInitialModuleCount = 256;
45 // When rotate option is added to the modules list, this is the hardcoded
46 // number of files we create and rotate. When there is rotate:40,
47 // we will keep four files per process, each limited to 10MB. Sum is 40MB,
48 // the given limit.
50 // (Note: When this is changed to be >= 10, SandboxBroker::LaunchApp must add
51 // another rule to allow logfile.?? be written by content processes.)
52 const uint32_t kRotateFilesNumber = 4;
54 namespace mozilla {
56 namespace detail {
58 void log_print(const LogModule* aModule, LogLevel aLevel, const char* aFmt,
59 ...) {
60 va_list ap;
61 va_start(ap, aFmt);
62 aModule->Printv(aLevel, aFmt, ap);
63 va_end(ap);
66 void log_print(const LogModule* aModule, LogLevel aLevel, TimeStamp* aStart,
67 const char* aFmt, ...) {
68 va_list ap;
69 va_start(ap, aFmt);
70 aModule->Printv(aLevel, aStart, aFmt, ap);
71 va_end(ap);
74 } // namespace detail
76 LogLevel ToLogLevel(int32_t aLevel) {
77 aLevel = std::min(aLevel, static_cast<int32_t>(LogLevel::Verbose));
78 aLevel = std::max(aLevel, static_cast<int32_t>(LogLevel::Disabled));
79 return static_cast<LogLevel>(aLevel);
82 static const char* ToLogStr(LogLevel aLevel) {
83 switch (aLevel) {
84 case LogLevel::Error:
85 return "E";
86 case LogLevel::Warning:
87 return "W";
88 case LogLevel::Info:
89 return "I";
90 case LogLevel::Debug:
91 return "D";
92 case LogLevel::Verbose:
93 return "V";
94 case LogLevel::Disabled:
95 default:
96 MOZ_CRASH("Invalid log level.");
97 return "";
101 namespace detail {
104 * A helper class providing reference counting for FILE*.
105 * It encapsulates the following:
106 * - the FILE handle
107 * - the order number it was created for when rotating (actual path)
108 * - number of active references
110 class LogFile {
111 FILE* mFile;
112 uint32_t mFileNum;
114 public:
115 LogFile(FILE* aFile, uint32_t aFileNum)
116 : mFile(aFile), mFileNum(aFileNum), mNextToRelease(nullptr) {}
118 ~LogFile() {
119 fclose(mFile);
120 delete mNextToRelease;
123 FILE* File() const { return mFile; }
124 uint32_t Num() const { return mFileNum; }
126 LogFile* mNextToRelease;
129 static const char* ExpandLogFileName(const char* aFilename,
130 char (&buffer)[2048]) {
131 MOZ_ASSERT(aFilename);
132 static const char kPIDToken[] = MOZ_LOG_PID_TOKEN;
133 static const char kMOZLOGExt[] = MOZ_LOG_FILE_EXTENSION;
135 bool hasMozLogExtension = StringEndsWith(nsDependentCString(aFilename),
136 nsLiteralCString(kMOZLOGExt));
138 const char* pidTokenPtr = strstr(aFilename, kPIDToken);
139 if (pidTokenPtr &&
140 SprintfLiteral(buffer, "%.*s%s%" PRIPID "%s%s",
141 static_cast<int>(pidTokenPtr - aFilename), aFilename,
142 XRE_IsParentProcess() ? "-main." : "-child.",
143 base::GetCurrentProcId(), pidTokenPtr + strlen(kPIDToken),
144 hasMozLogExtension ? "" : kMOZLOGExt) > 0) {
145 return buffer;
148 if (!hasMozLogExtension &&
149 SprintfLiteral(buffer, "%s%s", aFilename, kMOZLOGExt) > 0) {
150 return buffer;
153 return aFilename;
156 // Drop initial lines from the given file until it is less than or equal to the
157 // given size.
159 // For simplicity and to reduce memory consumption, lines longer than the given
160 // long line size may be broken.
162 // This function uses `mkstemp` and `rename` on POSIX systems and `_mktemp_s`
163 // and `ReplaceFileA` on Win32 systems. `ReplaceFileA` was introduced in
164 // Windows 7 so it's available.
165 bool LimitFileToLessThanSize(const char* aFilename, uint32_t aSize,
166 uint16_t aLongLineSize = 16384) {
167 // `tempFilename` will be further updated below.
168 char tempFilename[2048];
169 SprintfLiteral(tempFilename, "%s.tempXXXXXX", aFilename);
171 bool failedToWrite = false;
173 { // Scope `file` and `temp`, so that they are definitely closed.
174 ScopedCloseFile file(fopen(aFilename, "rb"));
175 if (!file) {
176 return false;
179 if (fseek(file, 0, SEEK_END)) {
180 // If we can't seek for some reason, better to just not limit the log at
181 // all and hope to sort out large logs upon further analysis.
182 return false;
185 // `ftell` returns a positive `long`, which might be more than 32 bits.
186 uint64_t fileSize = static_cast<uint64_t>(ftell(file));
188 if (fileSize <= aSize) {
189 return true;
192 uint64_t minBytesToDrop = fileSize - aSize;
193 uint64_t numBytesDropped = 0;
195 if (fseek(file, 0, SEEK_SET)) {
196 // Same as above: if we can't seek, hope for the best.
197 return false;
200 ScopedCloseFile temp;
202 #if defined(XP_WIN)
203 // This approach was cribbed from
204 // https://searchfox.org/mozilla-central/rev/868935867c6241e1302e64cf9be8f56db0fd0d1c/xpcom/build/LateWriteChecks.cpp#158.
205 HANDLE hFile;
206 do {
207 // mkstemp isn't supported so keep trying until we get a file.
208 _mktemp_s(tempFilename, strlen(tempFilename) + 1);
209 hFile = CreateFileA(tempFilename, GENERIC_WRITE, 0, nullptr, CREATE_NEW,
210 FILE_ATTRIBUTE_NORMAL, nullptr);
211 } while (GetLastError() == ERROR_FILE_EXISTS);
213 if (hFile == INVALID_HANDLE_VALUE) {
214 NS_WARNING("INVALID_HANDLE_VALUE");
215 return false;
218 int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND);
219 if (fd == -1) {
220 NS_WARNING("_open_osfhandle failed!");
221 return false;
224 temp.reset(_fdopen(fd, "ab"));
225 #elif defined(XP_UNIX)
227 // Coverity would prefer us to set a secure umask before using `mkstemp`.
228 // However, the umask is process-wide, so setting it may lead to difficult
229 // to debug complications; and it is fine for this particular short-lived
230 // temporary file to be insecure.
232 // coverity[SECURE_TEMP : FALSE]
233 int fd = mkstemp(tempFilename);
234 if (fd == -1) {
235 NS_WARNING("mkstemp failed!");
236 return false;
238 temp.reset(fdopen(fd, "ab"));
239 #else
240 # error Do not know how to open named temporary file
241 #endif
243 if (!temp) {
244 NS_WARNING(nsPrintfCString("could not open named temporary file %s",
245 tempFilename)
246 .get());
247 return false;
250 // `fgets` always null terminates. If the line is too long, it won't
251 // include a trailing '\n' but will be null-terminated.
252 UniquePtr<char[]> line = MakeUnique<char[]>(aLongLineSize + 1);
253 while (fgets(line.get(), aLongLineSize + 1, file)) {
254 if (numBytesDropped >= minBytesToDrop) {
255 if (fputs(line.get(), temp) < 0) {
256 NS_WARNING(
257 nsPrintfCString("fputs failed: ferror %d\n", ferror(temp)).get());
258 failedToWrite = true;
259 break;
261 } else {
262 // Binary mode avoids platform-specific wrinkles with text streams. In
263 // particular, on Windows, `\r\n` gets read as `\n` (and the reverse
264 // when writing), complicating this calculation.
265 numBytesDropped += strlen(line.get());
270 // At this point, `file` and `temp` are closed, so we can remove and rename.
271 if (failedToWrite) {
272 remove(tempFilename);
273 return false;
276 #if defined(XP_WIN)
277 if (!::ReplaceFileA(aFilename, tempFilename, nullptr, 0, 0, 0)) {
278 NS_WARNING(
279 nsPrintfCString("ReplaceFileA failed: %lu\n", GetLastError()).get());
280 return false;
282 #elif defined(XP_UNIX)
283 if (rename(tempFilename, aFilename)) {
284 NS_WARNING(
285 nsPrintfCString("rename failed: %s (%d)\n", strerror(errno), errno)
286 .get());
287 return false;
289 #else
290 # error Do not know how to atomically replace file
291 #endif
293 return true;
296 } // namespace detail
298 namespace {
299 // Helper method that initializes an empty va_list to be empty.
300 void empty_va(va_list* va, ...) {
301 va_start(*va, va);
302 va_end(*va);
304 } // namespace
306 class LogModuleManager {
307 public:
308 LogModuleManager()
309 : mModulesLock("logmodules"),
310 mModules(kInitialModuleCount),
311 #ifdef DEBUG
312 mLoggingModuleRegistered(0),
313 #endif
314 mPrintEntryCount(0),
315 mOutFile(nullptr),
316 mToReleaseFile(nullptr),
317 mOutFileNum(0),
318 mOutFilePath(strdup("")),
319 mMainThread(PR_GetCurrentThread()),
320 mSetFromEnv(false),
321 mAddTimestamp(false),
322 mCaptureProfilerStack(false),
323 mIsRaw(false),
324 mIsSync(false),
325 mRotate(0),
326 mInitialized(false) {
329 ~LogModuleManager() {
330 detail::LogFile* logFile = mOutFile.exchange(nullptr);
331 delete logFile;
335 * Loads config from command line args or env vars if present, in
336 * this specific order of priority.
338 * Notes:
340 * 1) This function is only intended to be called once per session.
341 * 2) None of the functions used in Init should rely on logging.
343 void Init(int argc, char* argv[]) {
344 MOZ_DIAGNOSTIC_ASSERT(!mInitialized);
345 mInitialized = true;
347 LoggingHandleCommandLineArgs(argc, static_cast<char const* const*>(argv),
348 [](nsACString const& env) {
349 // We deliberately set/rewrite the
350 // environment variables so that when child
351 // processes are spawned w/o passing the
352 // arguments they still inherit the logging
353 // settings as well as sandboxing can be
354 // correctly set. Scripts can pass
355 // -MOZ_LOG=$MOZ_LOG,modules as an argument
356 // to merge existing settings, if required.
358 // PR_SetEnv takes ownership of the string.
359 PR_SetEnv(ToNewCString(env));
362 bool shouldAppend = false;
363 bool addTimestamp = false;
364 bool isSync = false;
365 bool isRaw = false;
366 bool captureStacks = false;
367 int32_t rotate = 0;
368 int32_t maxSize = 0;
369 bool prependHeader = false;
370 const char* modules = PR_GetEnv("MOZ_LOG");
371 if (!modules || !modules[0]) {
372 modules = PR_GetEnv("MOZ_LOG_MODULES");
373 if (modules) {
374 NS_WARNING(
375 "MOZ_LOG_MODULES is deprecated."
376 "\nPlease use MOZ_LOG instead.");
379 if (!modules || !modules[0]) {
380 modules = PR_GetEnv("NSPR_LOG_MODULES");
381 if (modules) {
382 NS_WARNING(
383 "NSPR_LOG_MODULES is deprecated."
384 "\nPlease use MOZ_LOG instead.");
388 // Need to capture `this` since `sLogModuleManager` is not set until after
389 // initialization is complete.
390 NSPRLogModulesParser(
391 modules,
392 [this, &shouldAppend, &addTimestamp, &isSync, &isRaw, &rotate, &maxSize,
393 &prependHeader, &captureStacks](const char* aName, LogLevel aLevel,
394 int32_t aValue) mutable {
395 if (strcmp(aName, "append") == 0) {
396 shouldAppend = true;
397 } else if (strcmp(aName, "timestamp") == 0) {
398 addTimestamp = true;
399 } else if (strcmp(aName, "sync") == 0) {
400 isSync = true;
401 } else if (strcmp(aName, "raw") == 0) {
402 isRaw = true;
403 } else if (strcmp(aName, "rotate") == 0) {
404 rotate = (aValue << 20) / kRotateFilesNumber;
405 } else if (strcmp(aName, "maxsize") == 0) {
406 maxSize = aValue << 20;
407 } else if (strcmp(aName, "prependheader") == 0) {
408 prependHeader = true;
409 } else if (strcmp(aName, "profilerstacks") == 0) {
410 captureStacks = true;
411 } else {
412 this->CreateOrGetModule(aName)->SetLevel(aLevel);
416 // Rotate implies timestamp to make the files readable
417 mAddTimestamp = addTimestamp || rotate > 0;
418 mIsSync = isSync;
419 mIsRaw = isRaw;
420 mRotate = rotate;
421 mCaptureProfilerStack = captureStacks;
423 if (rotate > 0 && shouldAppend) {
424 NS_WARNING("MOZ_LOG: when you rotate the log, you cannot use append!");
427 if (rotate > 0 && maxSize > 0) {
428 NS_WARNING(
429 "MOZ_LOG: when you rotate the log, you cannot use maxsize! (ignoring "
430 "maxsize)");
431 maxSize = 0;
434 if (maxSize > 0 && !shouldAppend) {
435 NS_WARNING(
436 "MOZ_LOG: when you limit the log to maxsize, you must use append! "
437 "(ignorning maxsize)");
438 maxSize = 0;
441 if (rotate > 0 && prependHeader) {
442 NS_WARNING(
443 "MOZ_LOG: when you rotate the log, you cannot use prependheader!");
444 prependHeader = false;
447 const char* logFile = PR_GetEnv("MOZ_LOG_FILE");
448 if (!logFile || !logFile[0]) {
449 logFile = PR_GetEnv("NSPR_LOG_FILE");
452 if (logFile && logFile[0]) {
453 char buf[2048];
454 logFile = detail::ExpandLogFileName(logFile, buf);
455 mOutFilePath.reset(strdup(logFile));
457 if (mRotate > 0) {
458 // Delete all the previously captured files, including non-rotated
459 // log files, so that users don't complain our logs eat space even
460 // after the rotate option has been added and don't happen to send
461 // us old large logs along with the rotated files.
462 remove(mOutFilePath.get());
463 for (uint32_t i = 0; i < kRotateFilesNumber; ++i) {
464 RemoveFile(i);
468 mOutFile = OpenFile(shouldAppend, mOutFileNum, maxSize);
469 mSetFromEnv = true;
472 if (prependHeader && XRE_IsParentProcess()) {
473 va_list va;
474 empty_va(&va);
475 Print("Logger", LogLevel::Info, nullptr, "\n***\n\n", "Opening log\n",
476 va);
480 void SetLogFile(const char* aFilename) {
481 // For now we don't allow you to change the file at runtime.
482 if (mSetFromEnv) {
483 NS_WARNING(
484 "LogModuleManager::SetLogFile - Log file was set from the "
485 "MOZ_LOG_FILE environment variable.");
486 return;
489 const char* filename = aFilename ? aFilename : "";
490 char buf[2048];
491 filename = detail::ExpandLogFileName(filename, buf);
493 // Can't use rotate or maxsize at runtime yet.
494 MOZ_ASSERT(mRotate == 0,
495 "We don't allow rotate for runtime logfile changes");
496 mOutFilePath.reset(strdup(filename));
498 // Exchange mOutFile and set it to be released once all the writes are done.
499 detail::LogFile* newFile = OpenFile(false, 0);
500 detail::LogFile* oldFile = mOutFile.exchange(newFile);
502 // Since we don't allow changing the logfile if MOZ_LOG_FILE is already set,
503 // and we don't allow log rotation when setting it at runtime,
504 // mToReleaseFile will be null, so we're not leaking.
505 DebugOnly<detail::LogFile*> prevFile = mToReleaseFile.exchange(oldFile);
506 MOZ_ASSERT(!prevFile, "Should be null because rotation is not allowed");
508 // If we just need to release a file, we must force print, in order to
509 // trigger the closing and release of mToReleaseFile.
510 if (oldFile) {
511 va_list va;
512 empty_va(&va);
513 Print("Logger", LogLevel::Info, "Flushing old log files\n", va);
517 uint32_t GetLogFile(char* aBuffer, size_t aLength) {
518 uint32_t len = strlen(mOutFilePath.get());
519 if (len + 1 > aLength) {
520 return 0;
522 snprintf(aBuffer, aLength, "%s", mOutFilePath.get());
523 return len;
526 void SetIsSync(bool aIsSync) { mIsSync = aIsSync; }
528 void SetCaptureStacks(bool aCaptureStacks) {
529 mCaptureProfilerStack = aCaptureStacks;
532 void SetAddTimestamp(bool aAddTimestamp) { mAddTimestamp = aAddTimestamp; }
534 detail::LogFile* OpenFile(bool aShouldAppend, uint32_t aFileNum,
535 uint32_t aMaxSize = 0) {
536 FILE* file;
538 if (mRotate > 0) {
539 char buf[2048];
540 SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum);
542 // rotate doesn't support append (or maxsize).
543 file = fopen(buf, "w");
544 } else if (aShouldAppend && aMaxSize > 0) {
545 detail::LimitFileToLessThanSize(mOutFilePath.get(), aMaxSize >> 1);
546 file = fopen(mOutFilePath.get(), "a");
547 } else {
548 file = fopen(mOutFilePath.get(), aShouldAppend ? "a" : "w");
551 if (!file) {
552 return nullptr;
555 return new detail::LogFile(file, aFileNum);
558 void RemoveFile(uint32_t aFileNum) {
559 char buf[2048];
560 SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum);
561 remove(buf);
564 LogModule* CreateOrGetModule(const char* aName) {
565 OffTheBooksMutexAutoLock guard(mModulesLock);
566 return mModules
567 .LookupOrInsertWith(
568 aName,
569 [&] {
570 #ifdef DEBUG
571 if (++mLoggingModuleRegistered > kInitialModuleCount) {
572 NS_WARNING(
573 "kInitialModuleCount too low, consider increasing its "
574 "value");
576 #endif
577 return UniquePtr<LogModule>(
578 new LogModule{aName, LogLevel::Disabled});
580 .get();
583 void Print(const char* aName, LogLevel aLevel, const char* aFmt,
584 va_list aArgs) MOZ_FORMAT_PRINTF(4, 0) {
585 Print(aName, aLevel, nullptr, "", aFmt, aArgs);
588 void Print(const char* aName, LogLevel aLevel, const TimeStamp* aStart,
589 const char* aPrepend, const char* aFmt, va_list aArgs)
590 MOZ_FORMAT_PRINTF(6, 0) {
591 AutoSuspendLateWriteChecks suspendLateWriteChecks;
592 long pid = static_cast<long>(base::GetCurrentProcId());
593 const size_t kBuffSize = 1024;
594 char buff[kBuffSize];
596 char* buffToWrite = buff;
597 SmprintfPointer allocatedBuff;
599 va_list argsCopy;
600 va_copy(argsCopy, aArgs);
601 int charsWritten = VsprintfLiteral(buff, aFmt, argsCopy);
602 va_end(argsCopy);
604 if (charsWritten < 0) {
605 // Print out at least something. We must copy to the local buff,
606 // can't just assign aFmt to buffToWrite, since when
607 // buffToWrite != buff, we try to release it.
608 MOZ_ASSERT(false, "Probably incorrect format string in LOG?");
609 strncpy(buff, aFmt, kBuffSize - 1);
610 buff[kBuffSize - 1] = '\0';
611 charsWritten = strlen(buff);
612 } else if (static_cast<size_t>(charsWritten) >= kBuffSize - 1) {
613 // We may have maxed out, allocate a buffer instead.
614 allocatedBuff = mozilla::Vsmprintf(aFmt, aArgs);
615 buffToWrite = allocatedBuff.get();
616 charsWritten = strlen(buffToWrite);
619 if (profiler_thread_is_being_profiled_for_markers()) {
620 struct LogMarker {
621 static constexpr Span<const char> MarkerTypeName() {
622 return MakeStringSpan("Log");
624 static void StreamJSONMarkerData(
625 baseprofiler::SpliceableJSONWriter& aWriter,
626 const ProfilerString8View& aModule,
627 const ProfilerString8View& aText) {
628 aWriter.StringProperty("module", aModule);
629 aWriter.StringProperty("name", aText);
631 static MarkerSchema MarkerTypeDisplay() {
632 using MS = MarkerSchema;
633 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
634 schema.SetTableLabel("({marker.data.module}) {marker.data.name}");
635 schema.AddKeyLabelFormatSearchable("module", "Module",
636 MS::Format::String,
637 MS::Searchable::Searchable);
638 schema.AddKeyLabelFormatSearchable("name", "Name", MS::Format::String,
639 MS::Searchable::Searchable);
640 return schema;
644 profiler_add_marker(
645 "LogMessages", geckoprofiler::category::OTHER,
646 {aStart ? MarkerTiming::IntervalUntilNowFrom(*aStart)
647 : MarkerTiming::InstantNow(),
648 MarkerStack::MaybeCapture(mCaptureProfilerStack)},
649 LogMarker{}, ProfilerString8View::WrapNullTerminatedString(aName),
650 ProfilerString8View::WrapNullTerminatedString(buffToWrite));
653 // Determine if a newline needs to be appended to the message.
654 const char* newline = "";
655 if (charsWritten == 0 || buffToWrite[charsWritten - 1] != '\n') {
656 newline = "\n";
659 FILE* out = stderr;
661 // In case we use rotate, this ensures the FILE is kept alive during
662 // its use. Increased before we load mOutFile.
663 ++mPrintEntryCount;
665 detail::LogFile* outFile = mOutFile;
666 if (outFile) {
667 out = outFile->File();
670 // This differs from the NSPR format in that we do not output the
671 // opaque system specific thread pointer (ie pthread_t) cast
672 // to a long. The address of the current PR_Thread continues to be
673 // prefixed.
675 // Additionally we prefix the output with the abbreviated log level
676 // and the module name.
677 PRThread* currentThread = PR_GetCurrentThread();
678 const char* currentThreadName = (mMainThread == currentThread)
679 ? "Main Thread"
680 : PR_GetThreadName(currentThread);
682 char noNameThread[40];
683 if (!currentThreadName) {
684 SprintfLiteral(noNameThread, "Unnamed thread %p", currentThread);
685 currentThreadName = noNameThread;
688 if (!mAddTimestamp && !aStart) {
689 if (!mIsRaw) {
690 fprintf_stderr(out, "%s[%s %ld: %s]: %s/%s %s%s", aPrepend,
691 nsDebugImpl::GetMultiprocessMode(), pid,
692 currentThreadName, ToLogStr(aLevel), aName, buffToWrite,
693 newline);
694 } else {
695 fprintf_stderr(out, "%s%s%s", aPrepend, buffToWrite, newline);
697 } else {
698 if (aStart) {
699 // XXX is there a reasonable way to convert one to the other? this is
700 // bad
701 PRTime prnow = PR_Now();
702 TimeStamp tmnow = TimeStamp::Now();
703 TimeDuration duration = tmnow - *aStart;
704 PRTime prstart = prnow - duration.ToMicroseconds();
706 PRExplodedTime now;
707 PRExplodedTime start;
708 PR_ExplodeTime(prnow, PR_GMTParameters, &now);
709 PR_ExplodeTime(prstart, PR_GMTParameters, &start);
710 // Ignore that the start time might be in a different day
711 fprintf_stderr(
712 out,
713 "%s%04d-%02d-%02d %02d:%02d:%02d.%06d -> %02d:%02d:%02d.%06d UTC "
714 "(%.1gms)- [%s %ld: %s]: %s/%s %s%s",
715 aPrepend, now.tm_year, now.tm_month + 1, start.tm_mday,
716 start.tm_hour, start.tm_min, start.tm_sec, start.tm_usec,
717 now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec,
718 duration.ToMilliseconds(), nsDebugImpl::GetMultiprocessMode(), pid,
719 currentThreadName, ToLogStr(aLevel), aName, buffToWrite, newline);
720 } else {
721 PRExplodedTime now;
722 PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now);
723 fprintf_stderr(out,
724 "%s%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - [%s %ld: "
725 "%s]: %s/%s %s%s",
726 aPrepend, now.tm_year, now.tm_month + 1, now.tm_mday,
727 now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec,
728 nsDebugImpl::GetMultiprocessMode(), pid,
729 currentThreadName, ToLogStr(aLevel), aName, buffToWrite,
730 newline);
734 if (mIsSync) {
735 fflush(out);
738 if (mRotate > 0 && outFile) {
739 int32_t fileSize = ftell(out);
740 if (fileSize > mRotate) {
741 uint32_t fileNum = outFile->Num();
743 uint32_t nextFileNum = fileNum + 1;
744 if (nextFileNum >= kRotateFilesNumber) {
745 nextFileNum = 0;
748 // And here is the trick. The current out-file remembers its order
749 // number. When no other thread shifted the global file number yet,
750 // we are the thread to open the next file.
751 if (mOutFileNum.compareExchange(fileNum, nextFileNum)) {
752 // We can work with mToReleaseFile because we are sure the
753 // mPrintEntryCount can't drop to zero now - the condition
754 // to actually delete what's stored in that member.
755 // And also, no other thread can enter this piece of code
756 // because mOutFile is still holding the current file with
757 // the non-shifted number. The compareExchange() above is
758 // a no-op for other threads.
759 outFile->mNextToRelease = mToReleaseFile;
760 mToReleaseFile = outFile;
762 mOutFile = OpenFile(false, nextFileNum);
767 if (--mPrintEntryCount == 0 && mToReleaseFile) {
768 // We were the last Print() entered, if there is a file to release
769 // do it now. exchange() is atomic and makes sure we release the file
770 // only once on one thread.
771 detail::LogFile* release = mToReleaseFile.exchange(nullptr);
772 delete release;
776 private:
777 OffTheBooksMutex mModulesLock;
778 nsClassHashtable<nsCharPtrHashKey, LogModule> mModules;
780 #ifdef DEBUG
781 Atomic<uint32_t, ReleaseAcquire> mLoggingModuleRegistered;
782 #endif
783 // Print() entry counter, actually reflects concurrent use of the current
784 // output file. ReleaseAcquire ensures that manipulation with mOutFile
785 // and mToReleaseFile is synchronized by manipulation with this value.
786 Atomic<uint32_t, ReleaseAcquire> mPrintEntryCount;
787 // File to write to. ReleaseAcquire because we need to sync mToReleaseFile
788 // with this.
789 Atomic<detail::LogFile*, ReleaseAcquire> mOutFile;
790 // File to be released when reference counter drops to zero. This member
791 // is assigned mOutFile when the current file has reached the limit.
792 // It can be Relaxed, since it's synchronized with mPrintEntryCount
793 // manipulation and we do atomic exchange() on it.
794 Atomic<detail::LogFile*, Relaxed> mToReleaseFile;
795 // The next file number. This is mostly only for synchronization sake.
796 // Can have relaxed ordering, since we only do compareExchange on it which
797 // is atomic regardless ordering.
798 Atomic<uint32_t, Relaxed> mOutFileNum;
799 // Just keeps the actual file path for further use.
800 UniqueFreePtr<char[]> mOutFilePath;
802 PRThread* mMainThread;
803 bool mSetFromEnv;
804 Atomic<bool, Relaxed> mAddTimestamp;
805 Atomic<bool, Relaxed> mCaptureProfilerStack;
806 Atomic<bool, Relaxed> mIsRaw;
807 Atomic<bool, Relaxed> mIsSync;
808 int32_t mRotate;
809 bool mInitialized;
812 StaticAutoPtr<LogModuleManager> sLogModuleManager;
814 LogModule* LogModule::Get(const char* aName) {
815 // This is just a pass through to the LogModuleManager so
816 // that the LogModuleManager implementation can be kept internal.
817 MOZ_ASSERT(sLogModuleManager != nullptr);
818 return sLogModuleManager->CreateOrGetModule(aName);
821 void LogModule::SetLogFile(const char* aFilename) {
822 MOZ_ASSERT(sLogModuleManager);
823 sLogModuleManager->SetLogFile(aFilename);
826 uint32_t LogModule::GetLogFile(char* aBuffer, size_t aLength) {
827 MOZ_ASSERT(sLogModuleManager);
828 return sLogModuleManager->GetLogFile(aBuffer, aLength);
831 void LogModule::SetAddTimestamp(bool aAddTimestamp) {
832 sLogModuleManager->SetAddTimestamp(aAddTimestamp);
835 void LogModule::SetIsSync(bool aIsSync) {
836 sLogModuleManager->SetIsSync(aIsSync);
839 void LogModule::SetCaptureStacks(bool aCaptureStacks) {
840 sLogModuleManager->SetCaptureStacks(aCaptureStacks);
843 // This function is defined in gecko_logger/src/lib.rs
844 // We mirror the level in rust code so we don't get forwarded all of the
845 // rust logging and have to create an LogModule for each rust component.
846 extern "C" void set_rust_log_level(const char* name, uint8_t level);
848 void LogModule::SetLevel(LogLevel level) {
849 mLevel = level;
851 // If the log module contains `::` it is likely a rust module, so we
852 // pass the level into the rust code so it will know to forward the logging
853 // to Gecko.
854 if (strstr(mName, "::")) {
855 set_rust_log_level(mName, static_cast<uint8_t>(level));
859 void LogModule::Init(int argc, char* argv[]) {
860 // NB: This method is not threadsafe; it is expected to be called very early
861 // in startup prior to any other threads being run.
862 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
864 if (sLogModuleManager) {
865 // Already initialized.
866 return;
869 // NB: We intentionally do not register for ClearOnShutdown as that happens
870 // before all logging is complete. And, yes, that means we leak, but
871 // we're doing that intentionally.
873 // Don't assign the pointer until after Init is called. This should help us
874 // detect if any of the functions called by Init somehow rely on logging.
875 auto mgr = new LogModuleManager();
876 mgr->Init(argc, argv);
877 sLogModuleManager = mgr;
880 void LogModule::Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const {
881 MOZ_ASSERT(sLogModuleManager != nullptr);
883 // Forward to LogModule manager w/ level and name
884 sLogModuleManager->Print(Name(), aLevel, aFmt, aArgs);
887 void LogModule::Printv(LogLevel aLevel, const TimeStamp* aStart,
888 const char* aFmt, va_list aArgs) const {
889 MOZ_ASSERT(sLogModuleManager != nullptr);
891 // Forward to LogModule manager w/ level and name
892 sLogModuleManager->Print(Name(), aLevel, aStart, "", aFmt, aArgs);
895 } // namespace mozilla
897 extern "C" {
899 // This function is called by external code (rust) to log to one of our
900 // log modules.
901 void ExternMozLog(const char* aModule, mozilla::LogLevel aLevel,
902 const char* aMsg) {
903 MOZ_ASSERT(mozilla::sLogModuleManager != nullptr);
905 mozilla::LogModule* m =
906 mozilla::sLogModuleManager->CreateOrGetModule(aModule);
907 if (MOZ_LOG_TEST(m, aLevel)) {
908 mozilla::detail::log_print(m, aLevel, "%s", aMsg);
912 } // extern "C"