Bug 1932613 - temporarily disable browser_ml_end_to_end.js for permanent failures...
[gecko.git] / xpcom / base / Logging.cpp
blobfdd0c5e64255ef5b66f701974a630121b3268842
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 "base/process_util.h"
10 #include "GeckoProfiler.h"
11 #include "mozilla/ClearOnShutdown.h"
12 #include "mozilla/FileUtils.h"
13 #include "mozilla/LateWriteChecks.h"
14 #include "mozilla/Mutex.h"
15 #include "mozilla/StaticPtr.h"
16 #include "mozilla/Printf.h"
17 #include "mozilla/Atomics.h"
18 #include "mozilla/Sprintf.h"
19 #include "mozilla/UniquePtrExtensions.h"
20 #include "MainThreadUtils.h"
21 #include "nsClassHashtable.h"
22 #include "nsDebug.h"
23 #include "nsDebugImpl.h"
24 #include "nsPrintfCString.h"
25 #include "NSPRLogModulesParser.h"
26 #include "nsXULAppAPI.h"
27 #include "LogCommandLineHandler.h"
28 #include "fmt/format.h"
30 #include "prenv.h"
31 #ifdef XP_WIN
32 # include <fcntl.h>
33 # include <process.h>
34 #else
35 # include <sys/stat.h> // for umask()
36 # include <sys/types.h>
37 # include <unistd.h>
38 #endif
40 // NB: Amount determined by performing a typical browsing session and finding
41 // the maximum number of modules instantiated, and padding up to the next
42 // power of 2.
43 const uint32_t kInitialModuleCount = 1024;
44 // When rotate option is added to the modules list, this is the hardcoded
45 // number of files we create and rotate. When there is rotate:40,
46 // we will keep four files per process, each limited to 10MB. Sum is 40MB,
47 // the given limit.
49 // (Note: When this is changed to be >= 10, SandboxBroker::LaunchApp must add
50 // another rule to allow logfile.?? be written by content processes.)
51 const uint32_t kRotateFilesNumber = 4;
53 namespace mozilla {
55 namespace detail {
57 void log_print(const LogModule* aModule, LogLevel aLevel, const char* aFmt,
58 ...) {
59 va_list ap;
60 va_start(ap, aFmt);
61 aModule->Printv(aLevel, aFmt, ap);
62 va_end(ap);
65 void log_print(const LogModule* aModule, LogLevel aLevel, TimeStamp* aStart,
66 const char* aFmt, ...) {
67 va_list ap;
68 va_start(ap, aFmt);
69 aModule->Printv(aLevel, aStart, aFmt, ap);
70 va_end(ap);
73 } // namespace detail
75 static const char* ToLogStr(LogLevel aLevel) {
76 switch (aLevel) {
77 case LogLevel::Error:
78 return "E";
79 case LogLevel::Warning:
80 return "W";
81 case LogLevel::Info:
82 return "I";
83 case LogLevel::Debug:
84 return "D";
85 case LogLevel::Verbose:
86 return "V";
87 case LogLevel::Disabled:
88 default:
89 MOZ_CRASH("Invalid log level.");
90 return "";
94 namespace detail {
96 /**
97 * A helper class providing reference counting for FILE*.
98 * It encapsulates the following:
99 * - the FILE handle
100 * - the order number it was created for when rotating (actual path)
101 * - number of active references
103 class LogFile {
104 FILE* mFile;
105 uint32_t mFileNum;
107 public:
108 LogFile(FILE* aFile, uint32_t aFileNum)
109 : mFile(aFile), mFileNum(aFileNum), mNextToRelease(nullptr) {}
111 ~LogFile() {
112 fclose(mFile);
113 delete mNextToRelease;
116 FILE* File() const { return mFile; }
117 uint32_t Num() const { return mFileNum; }
119 LogFile* mNextToRelease;
122 static const char* ExpandLogFileName(const char* aFilename,
123 char (&buffer)[2048]) {
124 MOZ_ASSERT(aFilename);
125 static const char kPIDToken[] = MOZ_LOG_PID_TOKEN;
126 static const char kMOZLOGExt[] = MOZ_LOG_FILE_EXTENSION;
128 bool hasMozLogExtension = StringEndsWith(nsDependentCString(aFilename),
129 nsLiteralCString(kMOZLOGExt));
131 const char* pidTokenPtr = strstr(aFilename, kPIDToken);
132 if (pidTokenPtr &&
133 SprintfLiteral(buffer, "%.*s%s%" PRIPID "%s%s",
134 static_cast<int>(pidTokenPtr - aFilename), aFilename,
135 XRE_IsParentProcess() ? "-main." : "-child.",
136 base::GetCurrentProcId(), pidTokenPtr + strlen(kPIDToken),
137 hasMozLogExtension ? "" : kMOZLOGExt) > 0) {
138 return buffer;
141 if (!hasMozLogExtension &&
142 SprintfLiteral(buffer, "%s%s", aFilename, kMOZLOGExt) > 0) {
143 return buffer;
146 return aFilename;
149 // Drop initial lines from the given file until it is less than or equal to the
150 // given size.
152 // For simplicity and to reduce memory consumption, lines longer than the given
153 // long line size may be broken.
155 // This function uses `mkstemp` and `rename` on POSIX systems and `_mktemp_s`
156 // and `ReplaceFileA` on Win32 systems. `ReplaceFileA` was introduced in
157 // Windows 7 so it's available.
158 bool LimitFileToLessThanSize(const char* aFilename, uint32_t aSize,
159 uint16_t aLongLineSize = 16384) {
160 // `tempFilename` will be further updated below.
161 char tempFilename[2048];
162 SprintfLiteral(tempFilename, "%s.tempXXXXXX", aFilename);
164 bool failedToWrite = false;
166 { // Scope `file` and `temp`, so that they are definitely closed.
167 ScopedCloseFile file(fopen(aFilename, "rb"));
168 if (!file) {
169 return false;
172 if (fseek(file.get(), 0, SEEK_END)) {
173 // If we can't seek for some reason, better to just not limit the log at
174 // all and hope to sort out large logs upon further analysis.
175 return false;
178 // `ftell` returns a positive `long`, which might be more than 32 bits.
179 uint64_t fileSize = static_cast<uint64_t>(ftell(file.get()));
181 if (fileSize <= aSize) {
182 return true;
185 uint64_t minBytesToDrop = fileSize - aSize;
186 uint64_t numBytesDropped = 0;
188 if (fseek(file.get(), 0, SEEK_SET)) {
189 // Same as above: if we can't seek, hope for the best.
190 return false;
193 ScopedCloseFile temp;
195 #if defined(XP_WIN)
196 // This approach was cribbed from
197 // https://searchfox.org/mozilla-central/rev/868935867c6241e1302e64cf9be8f56db0fd0d1c/xpcom/build/LateWriteChecks.cpp#158.
198 HANDLE hFile;
199 do {
200 // mkstemp isn't supported so keep trying until we get a file.
201 _mktemp_s(tempFilename, strlen(tempFilename) + 1);
202 hFile = CreateFileA(tempFilename, GENERIC_WRITE, 0, nullptr, CREATE_NEW,
203 FILE_ATTRIBUTE_NORMAL, nullptr);
204 } while (GetLastError() == ERROR_FILE_EXISTS);
206 if (hFile == INVALID_HANDLE_VALUE) {
207 NS_WARNING("INVALID_HANDLE_VALUE");
208 return false;
211 int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND);
212 if (fd == -1) {
213 NS_WARNING("_open_osfhandle failed!");
214 return false;
217 temp.reset(_fdopen(fd, "ab"));
218 #elif defined(XP_UNIX)
220 // Coverity would prefer us to set a secure umask before using `mkstemp`.
221 // However, the umask is process-wide, so setting it may lead to difficult
222 // to debug complications; and it is fine for this particular short-lived
223 // temporary file to be insecure.
225 // coverity[SECURE_TEMP : FALSE]
226 int fd = mkstemp(tempFilename);
227 if (fd == -1) {
228 NS_WARNING("mkstemp failed!");
229 return false;
231 temp.reset(fdopen(fd, "ab"));
232 #else
233 # error Do not know how to open named temporary file
234 #endif
236 if (!temp) {
237 NS_WARNING(nsPrintfCString("could not open named temporary file %s",
238 tempFilename)
239 .get());
240 return false;
243 // `fgets` always null terminates. If the line is too long, it won't
244 // include a trailing '\n' but will be null-terminated.
245 UniquePtr<char[]> line = MakeUnique<char[]>(aLongLineSize + 1);
246 while (fgets(line.get(), aLongLineSize + 1, file.get())) {
247 if (numBytesDropped >= minBytesToDrop) {
248 if (fputs(line.get(), temp.get()) < 0) {
249 NS_WARNING(
250 nsPrintfCString("fputs failed: ferror %d\n", ferror(temp.get()))
251 .get());
252 failedToWrite = true;
253 break;
255 } else {
256 // Binary mode avoids platform-specific wrinkles with text streams. In
257 // particular, on Windows, `\r\n` gets read as `\n` (and the reverse
258 // when writing), complicating this calculation.
259 numBytesDropped += strlen(line.get());
264 // At this point, `file` and `temp` are closed, so we can remove and rename.
265 if (failedToWrite) {
266 remove(tempFilename);
267 return false;
270 #if defined(XP_WIN)
271 if (!::ReplaceFileA(aFilename, tempFilename, nullptr, 0, 0, 0)) {
272 NS_WARNING(
273 nsPrintfCString("ReplaceFileA failed: %lu\n", GetLastError()).get());
274 return false;
276 #elif defined(XP_UNIX)
277 if (rename(tempFilename, aFilename)) {
278 NS_WARNING(
279 nsPrintfCString("rename failed: %s (%d)\n", strerror(errno), errno)
280 .get());
281 return false;
283 #else
284 # error Do not know how to atomically replace file
285 #endif
287 return true;
290 } // namespace detail
292 namespace {
293 // Helper method that initializes an empty va_list to be empty.
294 void empty_va(va_list* va, ...) {
295 va_start(*va, va);
296 va_end(*va);
298 } // namespace
300 class LogModuleManager {
301 public:
302 LogModuleManager()
303 : mModulesLock("logmodules"),
304 mModules(kInitialModuleCount),
305 #ifdef DEBUG
306 mLoggingModuleRegistered(0),
307 #endif
308 mPrintEntryCount(0),
309 mOutFile(nullptr),
310 mToReleaseFile(nullptr),
311 mOutFileNum(0),
312 mOutFilePath(strdup("")),
313 mMainThread(PR_GetCurrentThread()),
314 mSetFromEnv(false),
315 mAddTimestamp(false),
316 mCaptureProfilerStack(false),
317 mIsRaw(false),
318 mIsSync(false),
319 mRotate(0),
320 mInitialized(false) {
323 ~LogModuleManager() {
324 detail::LogFile* logFile = mOutFile.exchange(nullptr);
325 delete logFile;
329 * Loads config from command line args or env vars if present, in
330 * this specific order of priority.
332 * Notes:
334 * 1) This function is only intended to be called once per session.
335 * 2) None of the functions used in Init should rely on logging.
337 void Init(int argc, char* argv[]) {
338 MOZ_DIAGNOSTIC_ASSERT(!mInitialized);
339 mInitialized = true;
341 LoggingHandleCommandLineArgs(argc, static_cast<char const* const*>(argv),
342 [](nsACString const& env) {
343 // We deliberately set/rewrite the
344 // environment variables so that when child
345 // processes are spawned w/o passing the
346 // arguments they still inherit the logging
347 // settings as well as sandboxing can be
348 // correctly set. Scripts can pass
349 // -MOZ_LOG=$MOZ_LOG,modules as an argument
350 // to merge existing settings, if required.
352 // PR_SetEnv takes ownership of the string.
353 PR_SetEnv(ToNewCString(env));
356 bool shouldAppend = false;
357 bool addTimestamp = false;
358 bool isSync = false;
359 bool isRaw = false;
360 bool captureStacks = false;
361 int32_t rotate = 0;
362 int32_t maxSize = 0;
363 bool prependHeader = false;
364 const char* modules = PR_GetEnv("MOZ_LOG");
365 if (!modules || !modules[0]) {
366 modules = PR_GetEnv("MOZ_LOG_MODULES");
367 if (modules) {
368 NS_WARNING(
369 "MOZ_LOG_MODULES is deprecated."
370 "\nPlease use MOZ_LOG instead.");
373 if (!modules || !modules[0]) {
374 modules = PR_GetEnv("NSPR_LOG_MODULES");
375 if (modules) {
376 NS_WARNING(
377 "NSPR_LOG_MODULES is deprecated."
378 "\nPlease use MOZ_LOG instead.");
382 // Need to capture `this` since `sLogModuleManager` is not set until after
383 // initialization is complete.
384 NSPRLogModulesParser(
385 modules,
386 [this, &shouldAppend, &addTimestamp, &isSync, &isRaw, &rotate, &maxSize,
387 &prependHeader, &captureStacks](const char* aName, LogLevel aLevel,
388 int32_t aValue) mutable {
389 if (strcmp(aName, "append") == 0) {
390 shouldAppend = true;
391 } else if (strcmp(aName, "timestamp") == 0) {
392 addTimestamp = true;
393 } else if (strcmp(aName, "sync") == 0) {
394 isSync = true;
395 } else if (strcmp(aName, "raw") == 0) {
396 isRaw = true;
397 } else if (strcmp(aName, "rotate") == 0) {
398 rotate = (aValue << 20) / kRotateFilesNumber;
399 } else if (strcmp(aName, "maxsize") == 0) {
400 maxSize = aValue << 20;
401 } else if (strcmp(aName, "prependheader") == 0) {
402 prependHeader = true;
403 } else if (strcmp(aName, "profilerstacks") == 0) {
404 captureStacks = true;
405 } else {
406 this->CreateOrGetModule(aName)->SetLevel(aLevel);
410 // Rotate implies timestamp to make the files readable
411 mAddTimestamp = addTimestamp || rotate > 0;
412 mIsSync = isSync;
413 mIsRaw = isRaw;
414 mRotate = rotate;
415 mCaptureProfilerStack = captureStacks;
417 if (rotate > 0 && shouldAppend) {
418 NS_WARNING("MOZ_LOG: when you rotate the log, you cannot use append!");
421 if (rotate > 0 && maxSize > 0) {
422 NS_WARNING(
423 "MOZ_LOG: when you rotate the log, you cannot use maxsize! (ignoring "
424 "maxsize)");
425 maxSize = 0;
428 if (maxSize > 0 && !shouldAppend) {
429 NS_WARNING(
430 "MOZ_LOG: when you limit the log to maxsize, you must use append! "
431 "(ignorning maxsize)");
432 maxSize = 0;
435 if (rotate > 0 && prependHeader) {
436 NS_WARNING(
437 "MOZ_LOG: when you rotate the log, you cannot use prependheader!");
438 prependHeader = false;
441 const char* logFile = PR_GetEnv("MOZ_LOG_FILE");
442 if (!logFile || !logFile[0]) {
443 logFile = PR_GetEnv("NSPR_LOG_FILE");
446 if (logFile && logFile[0]) {
447 char buf[2048];
448 logFile = detail::ExpandLogFileName(logFile, buf);
449 mOutFilePath.reset(strdup(logFile));
451 if (mRotate > 0) {
452 // Delete all the previously captured files, including non-rotated
453 // log files, so that users don't complain our logs eat space even
454 // after the rotate option has been added and don't happen to send
455 // us old large logs along with the rotated files.
456 remove(mOutFilePath.get());
457 for (uint32_t i = 0; i < kRotateFilesNumber; ++i) {
458 RemoveFile(i);
462 mOutFile = OpenFile(shouldAppend, mOutFileNum, maxSize);
463 mSetFromEnv = true;
466 if (prependHeader && XRE_IsParentProcess()) {
467 va_list va;
468 empty_va(&va);
469 Print("Logger", LogLevel::Info, nullptr, "\n***\n\n", "Opening log\n",
470 va);
474 void SetLogFile(const char* aFilename) {
475 // For now we don't allow you to change the file at runtime.
476 if (mSetFromEnv) {
477 NS_WARNING(
478 "LogModuleManager::SetLogFile - Log file was set from the "
479 "MOZ_LOG_FILE environment variable.");
480 return;
483 const char* filename = aFilename ? aFilename : "";
484 char buf[2048];
485 filename = detail::ExpandLogFileName(filename, buf);
487 // Can't use rotate or maxsize at runtime yet.
488 MOZ_ASSERT(mRotate == 0,
489 "We don't allow rotate for runtime logfile changes");
490 mOutFilePath.reset(strdup(filename));
492 // Exchange mOutFile and set it to be released once all the writes are done.
493 detail::LogFile* newFile = OpenFile(false, 0);
494 detail::LogFile* oldFile = mOutFile.exchange(newFile);
496 // Since we don't allow changing the logfile if MOZ_LOG_FILE is already set,
497 // and we don't allow log rotation when setting it at runtime,
498 // mToReleaseFile will be null, so we're not leaking.
499 DebugOnly<detail::LogFile*> prevFile = mToReleaseFile.exchange(oldFile);
500 MOZ_ASSERT(!prevFile, "Should be null because rotation is not allowed");
502 // If we just need to release a file, we must force print, in order to
503 // trigger the closing and release of mToReleaseFile.
504 if (oldFile) {
505 va_list va;
506 empty_va(&va);
507 Print("Logger", LogLevel::Info, "Flushing old log files\n", va);
511 uint32_t GetLogFile(char* aBuffer, size_t aLength) {
512 uint32_t len = strlen(mOutFilePath.get());
513 if (len + 1 > aLength) {
514 return 0;
516 snprintf(aBuffer, aLength, "%s", mOutFilePath.get());
517 return len;
520 void SetIsSync(bool aIsSync) { mIsSync = aIsSync; }
522 void SetCaptureStacks(bool aCaptureStacks) {
523 mCaptureProfilerStack = aCaptureStacks;
526 void SetAddTimestamp(bool aAddTimestamp) { mAddTimestamp = aAddTimestamp; }
528 detail::LogFile* OpenFile(bool aShouldAppend, uint32_t aFileNum,
529 uint32_t aMaxSize = 0) {
530 FILE* file;
532 if (mRotate > 0) {
533 char buf[2048];
534 SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum);
536 // rotate doesn't support append (or maxsize).
537 file = fopen(buf, "w");
538 } else if (aShouldAppend && aMaxSize > 0) {
539 detail::LimitFileToLessThanSize(mOutFilePath.get(), aMaxSize >> 1);
540 file = fopen(mOutFilePath.get(), "a");
541 } else {
542 file = fopen(mOutFilePath.get(), aShouldAppend ? "a" : "w");
545 if (!file) {
546 return nullptr;
549 return new detail::LogFile(file, aFileNum);
552 void RemoveFile(uint32_t aFileNum) {
553 char buf[2048];
554 SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum);
555 remove(buf);
558 LogModule* CreateOrGetModule(const char* aName) {
559 OffTheBooksMutexAutoLock guard(mModulesLock);
560 return mModules
561 .LookupOrInsertWith(
562 aName,
563 [&] {
564 #ifdef DEBUG
565 if (++mLoggingModuleRegistered > kInitialModuleCount) {
566 NS_WARNING(
567 "kInitialModuleCount too low, consider increasing its "
568 "value");
570 #endif
571 return UniquePtr<LogModule>(
572 new LogModule{aName, LogLevel::Disabled});
574 .get();
577 void Print(const char* aName, LogLevel aLevel, const char* aFmt,
578 va_list aArgs) MOZ_FORMAT_PRINTF(4, 0) {
579 Print(aName, aLevel, nullptr, "", aFmt, aArgs);
581 void PrintFmt(const char* aName, LogLevel aLevel, fmt::string_view aFmt,
582 fmt::format_args aArgs) {
583 PrintFmt(aName, aLevel, nullptr, "", aFmt, aArgs);
586 void PrintFmt(const char* aName, LogLevel aLevel, const TimeStamp* aStart,
587 const char* aPrepend, fmt::string_view aFmt,
588 fmt::format_args aArgs) {
589 AutoSuspendLateWriteChecks suspendLateWriteChecks;
590 const size_t kBuffSize = 1024;
591 char buff[kBuffSize];
593 char* buffToWrite = buff;
594 UniquePtr<char[]> allocatedBuff;
595 size_t charsWritten;
597 auto [out, size] = fmt::vformat_to_n(buff, kBuffSize - 1, aFmt, aArgs);
598 *out = '\0';
599 charsWritten = size + 1;
601 // We may have maxed out, allocate a buffer and re-format
602 if (charsWritten > kBuffSize) {
603 allocatedBuff = MakeUnique<char[]>(charsWritten + 1); // + final \0
604 auto [out, size] =
605 fmt::vformat_to_n(allocatedBuff.get(), charsWritten, aFmt, aArgs);
606 MOZ_ASSERT(size == charsWritten);
607 *out = '\0';
608 charsWritten++;
609 buffToWrite = allocatedBuff.get();
611 ActuallyLog(aName, aLevel, aStart, aPrepend, buffToWrite, charsWritten);
614 void Print(const char* aName, LogLevel aLevel, const TimeStamp* aStart,
615 const char* aPrepend, const char* aFmt, va_list aArgs)
616 MOZ_FORMAT_PRINTF(6, 0) {
617 AutoSuspendLateWriteChecks suspendLateWriteChecks;
618 const size_t kBuffSize = 1024;
619 char buff[kBuffSize];
621 char* buffToWrite = buff;
622 SmprintfPointer allocatedBuff;
624 va_list argsCopy;
625 va_copy(argsCopy, aArgs);
626 int charsWritten = VsprintfLiteral(buff, aFmt, argsCopy);
627 va_end(argsCopy);
629 if (charsWritten < 0) {
630 // Print out at least something. We must copy to the local buff,
631 // can't just assign aFmt to buffToWrite, since when
632 // buffToWrite != buff, we try to release it.
633 MOZ_ASSERT(false, "Probably incorrect format string in LOG?");
634 strncpy(buff, aFmt, kBuffSize - 1);
635 buff[kBuffSize - 1] = '\0';
636 charsWritten = strlen(buff);
637 } else if (static_cast<size_t>(charsWritten) >= kBuffSize - 1) {
638 // We may have maxed out, allocate a buffer instead.
639 allocatedBuff = mozilla::Vsmprintf(aFmt, aArgs);
640 buffToWrite = allocatedBuff.get();
641 charsWritten = strlen(buffToWrite);
643 ActuallyLog(aName, aLevel, aStart, aPrepend, buffToWrite, charsWritten);
646 void ActuallyLog(const char* aName, LogLevel aLevel, const TimeStamp* aStart,
647 const char* aPrepend, const char* aLogMessage,
648 size_t aLogMessageSize) {
649 long pid = static_cast<long>(base::GetCurrentProcId());
650 if (profiler_thread_is_being_profiled_for_markers()) {
651 struct LogMarker {
652 static constexpr Span<const char> MarkerTypeName() {
653 return MakeStringSpan("Log");
655 static void StreamJSONMarkerData(
656 baseprofiler::SpliceableJSONWriter& aWriter,
657 const ProfilerString8View& aModule,
658 const ProfilerString8View& aText) {
659 aWriter.StringProperty("module", aModule);
660 aWriter.StringProperty("name", aText);
662 static MarkerSchema MarkerTypeDisplay() {
663 using MS = MarkerSchema;
664 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
665 schema.SetTableLabel("({marker.data.module}) {marker.data.name}");
666 schema.AddKeyLabelFormatSearchable("module", "Module",
667 MS::Format::String,
668 MS::Searchable::Searchable);
669 schema.AddKeyLabelFormatSearchable("name", "Name", MS::Format::String,
670 MS::Searchable::Searchable);
671 return schema;
675 profiler_add_marker(
676 "LogMessages", geckoprofiler::category::OTHER,
677 {aStart ? MarkerTiming::IntervalUntilNowFrom(*aStart)
678 : MarkerTiming::InstantNow(),
679 MarkerStack::MaybeCapture(mCaptureProfilerStack)},
680 LogMarker{}, ProfilerString8View::WrapNullTerminatedString(aName),
681 ProfilerString8View::WrapNullTerminatedString(aLogMessage));
684 // Determine if a newline needs to be appended to the message.
685 const char* newline = "";
686 if (aLogMessageSize == 0 || aLogMessage[aLogMessageSize - 1] != '\n') {
687 newline = "\n";
690 FILE* out = stderr;
692 // In case we use rotate, this ensures the FILE is kept alive during
693 // its use. Increased before we load mOutFile.
694 ++mPrintEntryCount;
696 detail::LogFile* outFile = mOutFile;
697 if (outFile) {
698 out = outFile->File();
701 // This differs from the NSPR format in that we do not output the
702 // opaque system specific thread pointer (ie pthread_t) cast
703 // to a long. The address of the current PR_Thread continues to be
704 // prefixed.
706 // Additionally we prefix the output with the abbreviated log level
707 // and the module name.
708 PRThread* currentThread = PR_GetCurrentThread();
709 const char* currentThreadName = (mMainThread == currentThread)
710 ? "Main Thread"
711 : PR_GetThreadName(currentThread);
713 char noNameThread[40];
714 if (!currentThreadName) {
715 SprintfLiteral(noNameThread, "Unnamed thread %p", currentThread);
716 currentThreadName = noNameThread;
719 if (!mAddTimestamp && !aStart) {
720 if (!mIsRaw) {
721 fprintf_stderr(out, "%s[%s %ld: %s]: %s/%s %s%s", aPrepend,
722 nsDebugImpl::GetMultiprocessMode(), pid,
723 currentThreadName, ToLogStr(aLevel), aName, aLogMessage,
724 newline);
725 } else {
726 fprintf_stderr(out, "%s%s%s", aPrepend, aLogMessage, newline);
728 } else {
729 if (aStart) {
730 // XXX is there a reasonable way to convert one to the other? this is
731 // bad
732 PRTime prnow = PR_Now();
733 TimeStamp tmnow = TimeStamp::Now();
734 TimeDuration duration = tmnow - *aStart;
735 PRTime prstart = prnow - duration.ToMicroseconds();
737 PRExplodedTime now;
738 PRExplodedTime start;
739 PR_ExplodeTime(prnow, PR_GMTParameters, &now);
740 PR_ExplodeTime(prstart, PR_GMTParameters, &start);
741 // Ignore that the start time might be in a different day
742 fprintf_stderr(
743 out,
744 "%s%04d-%02d-%02d %02d:%02d:%02d.%06d -> %02d:%02d:%02d.%06d UTC "
745 "(%.1gms)- [%s %ld: %s]: %s/%s %s%s",
746 aPrepend, now.tm_year, now.tm_month + 1, start.tm_mday,
747 start.tm_hour, start.tm_min, start.tm_sec, start.tm_usec,
748 now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec,
749 duration.ToMilliseconds(), nsDebugImpl::GetMultiprocessMode(), pid,
750 currentThreadName, ToLogStr(aLevel), aName, aLogMessage, newline);
751 } else {
752 PRExplodedTime now;
753 PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now);
754 fprintf_stderr(out,
755 "%s%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - [%s %ld: "
756 "%s]: %s/%s %s%s",
757 aPrepend, now.tm_year, now.tm_month + 1, now.tm_mday,
758 now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec,
759 nsDebugImpl::GetMultiprocessMode(), pid,
760 currentThreadName, ToLogStr(aLevel), aName, aLogMessage,
761 newline);
765 if (mIsSync) {
766 fflush(out);
769 if (mRotate > 0 && outFile) {
770 int32_t fileSize = ftell(out);
771 if (fileSize > mRotate) {
772 uint32_t fileNum = outFile->Num();
774 uint32_t nextFileNum = fileNum + 1;
775 if (nextFileNum >= kRotateFilesNumber) {
776 nextFileNum = 0;
779 // And here is the trick. The current out-file remembers its order
780 // number. When no other thread shifted the global file number yet,
781 // we are the thread to open the next file.
782 if (mOutFileNum.compareExchange(fileNum, nextFileNum)) {
783 // We can work with mToReleaseFile because we are sure the
784 // mPrintEntryCount can't drop to zero now - the condition
785 // to actually delete what's stored in that member.
786 // And also, no other thread can enter this piece of code
787 // because mOutFile is still holding the current file with
788 // the non-shifted number. The compareExchange() above is
789 // a no-op for other threads.
790 outFile->mNextToRelease = mToReleaseFile;
791 mToReleaseFile = outFile;
793 mOutFile = OpenFile(false, nextFileNum);
798 if (--mPrintEntryCount == 0 && mToReleaseFile) {
799 // We were the last Print() entered, if there is a file to release
800 // do it now. exchange() is atomic and makes sure we release the file
801 // only once on one thread.
802 detail::LogFile* release = mToReleaseFile.exchange(nullptr);
803 delete release;
807 void DisableModules() {
808 OffTheBooksMutexAutoLock guard(mModulesLock);
809 for (auto& m : mModules) {
810 (*(m.GetModifiableData()))->SetLevel(LogLevel::Disabled);
814 private:
815 OffTheBooksMutex mModulesLock;
816 nsClassHashtable<nsCharPtrHashKey, LogModule> mModules;
818 #ifdef DEBUG
819 Atomic<uint32_t, ReleaseAcquire> mLoggingModuleRegistered;
820 #endif
821 // Print() entry counter, actually reflects concurrent use of the current
822 // output file. ReleaseAcquire ensures that manipulation with mOutFile
823 // and mToReleaseFile is synchronized by manipulation with this value.
824 Atomic<uint32_t, ReleaseAcquire> mPrintEntryCount;
825 // File to write to. ReleaseAcquire because we need to sync mToReleaseFile
826 // with this.
827 Atomic<detail::LogFile*, ReleaseAcquire> mOutFile;
828 // File to be released when reference counter drops to zero. This member
829 // is assigned mOutFile when the current file has reached the limit.
830 // It can be Relaxed, since it's synchronized with mPrintEntryCount
831 // manipulation and we do atomic exchange() on it.
832 Atomic<detail::LogFile*, Relaxed> mToReleaseFile;
833 // The next file number. This is mostly only for synchronization sake.
834 // Can have relaxed ordering, since we only do compareExchange on it which
835 // is atomic regardless ordering.
836 Atomic<uint32_t, Relaxed> mOutFileNum;
837 // Just keeps the actual file path for further use.
838 UniqueFreePtr<char[]> mOutFilePath;
840 PRThread* mMainThread;
841 bool mSetFromEnv;
842 Atomic<bool, Relaxed> mAddTimestamp;
843 Atomic<bool, Relaxed> mCaptureProfilerStack;
844 Atomic<bool, Relaxed> mIsRaw;
845 Atomic<bool, Relaxed> mIsSync;
846 int32_t mRotate;
847 bool mInitialized;
850 StaticAutoPtr<LogModuleManager> sLogModuleManager;
852 LogModule* LogModule::Get(const char* aName) {
853 // This is just a pass through to the LogModuleManager so
854 // that the LogModuleManager implementation can be kept internal.
855 MOZ_ASSERT(sLogModuleManager != nullptr);
856 return sLogModuleManager->CreateOrGetModule(aName);
859 void LogModule::SetLogFile(const char* aFilename) {
860 MOZ_ASSERT(sLogModuleManager);
861 sLogModuleManager->SetLogFile(aFilename);
864 uint32_t LogModule::GetLogFile(char* aBuffer, size_t aLength) {
865 MOZ_ASSERT(sLogModuleManager);
866 return sLogModuleManager->GetLogFile(aBuffer, aLength);
869 void LogModule::SetAddTimestamp(bool aAddTimestamp) {
870 sLogModuleManager->SetAddTimestamp(aAddTimestamp);
873 void LogModule::SetIsSync(bool aIsSync) {
874 sLogModuleManager->SetIsSync(aIsSync);
877 void LogModule::SetCaptureStacks(bool aCaptureStacks) {
878 sLogModuleManager->SetCaptureStacks(aCaptureStacks);
881 void LogModule::DisableModules() { sLogModuleManager->DisableModules(); }
883 // This function is defined in gecko_logger/src/lib.rs
884 // We mirror the level in rust code so we don't get forwarded all of the
885 // rust logging and have to create an LogModule for each rust component.
886 extern "C" void set_rust_log_level(const char* name, uint8_t level);
888 void LogModule::SetLevel(LogLevel level) {
889 mLevel = level;
891 // If the log module contains `::` it is likely a rust module, so we
892 // pass the level into the rust code so it will know to forward the logging
893 // to Gecko.
894 if (strstr(mName, "::")) {
895 set_rust_log_level(mName, static_cast<uint8_t>(level));
899 void LogModule::Init(int argc, char* argv[]) {
900 // NB: This method is not threadsafe; it is expected to be called very early
901 // in startup prior to any other threads being run.
902 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
904 if (sLogModuleManager) {
905 // Already initialized.
906 return;
909 // NB: We intentionally do not register for ClearOnShutdown as that happens
910 // before all logging is complete. And, yes, that means we leak, but
911 // we're doing that intentionally.
913 // Don't assign the pointer until after Init is called. This should help us
914 // detect if any of the functions called by Init somehow rely on logging.
915 auto mgr = new LogModuleManager();
916 mgr->Init(argc, argv);
917 sLogModuleManager = mgr;
920 void LogModule::Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const {
921 MOZ_ASSERT(sLogModuleManager != nullptr);
923 // Forward to LogModule manager w/ level and name
924 sLogModuleManager->Print(Name(), aLevel, aFmt, aArgs);
927 void LogModule::Printv(LogLevel aLevel, const TimeStamp* aStart,
928 const char* aFmt, va_list aArgs) const {
929 MOZ_ASSERT(sLogModuleManager != nullptr);
931 // Forward to LogModule manager w/ level and name
932 sLogModuleManager->Print(Name(), aLevel, aStart, "", aFmt, aArgs);
935 void LogModule::PrintvFmt(LogLevel aLevel, fmt::string_view aFmt,
936 fmt::format_args aArgs) const {
937 MOZ_ASSERT(sLogModuleManager != nullptr);
939 // Forward to LogModule manager w/ level and name
940 sLogModuleManager->PrintFmt(Name(), aLevel, aFmt, aArgs);
943 } // namespace mozilla
945 extern "C" {
947 // This function is called by external code (rust) to log to one of our
948 // log modules.
949 void ExternMozLog(const char* aModule, mozilla::LogLevel aLevel,
950 const char* aMsg) {
951 MOZ_ASSERT(mozilla::sLogModuleManager != nullptr);
953 mozilla::LogModule* m =
954 mozilla::sLogModuleManager->CreateOrGetModule(aModule);
955 if (MOZ_LOG_TEST(m, aLevel)) {
956 mozilla::detail::log_print(m, aLevel, "%s", aMsg);
960 } // extern "C"