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"
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"
35 # include <sys/stat.h> // for umask()
36 # include <sys/types.h>
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
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,
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;
57 void log_print(const LogModule
* aModule
, LogLevel aLevel
, const char* aFmt
,
61 aModule
->Printv(aLevel
, aFmt
, ap
);
65 void log_print(const LogModule
* aModule
, LogLevel aLevel
, TimeStamp
* aStart
,
66 const char* aFmt
, ...) {
69 aModule
->Printv(aLevel
, aStart
, aFmt
, ap
);
75 static const char* ToLogStr(LogLevel aLevel
) {
79 case LogLevel::Warning
:
85 case LogLevel::Verbose
:
87 case LogLevel::Disabled
:
89 MOZ_CRASH("Invalid log level.");
97 * A helper class providing reference counting for FILE*.
98 * It encapsulates the following:
100 * - the order number it was created for when rotating (actual path)
101 * - number of active references
108 LogFile(FILE* aFile
, uint32_t aFileNum
)
109 : mFile(aFile
), mFileNum(aFileNum
), mNextToRelease(nullptr) {}
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
);
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) {
141 if (!hasMozLogExtension
&&
142 SprintfLiteral(buffer
, "%s%s", aFilename
, kMOZLOGExt
) > 0) {
149 // Drop initial lines from the given file until it is less than or equal to the
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"));
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.
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
) {
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.
193 ScopedCloseFile temp
;
196 // This approach was cribbed from
197 // https://searchfox.org/mozilla-central/rev/868935867c6241e1302e64cf9be8f56db0fd0d1c/xpcom/build/LateWriteChecks.cpp#158.
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");
211 int fd
= _open_osfhandle((intptr_t)hFile
, _O_APPEND
);
213 NS_WARNING("_open_osfhandle failed!");
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
);
228 NS_WARNING("mkstemp failed!");
231 temp
.reset(fdopen(fd
, "ab"));
233 # error Do not know how to open named temporary file
237 NS_WARNING(nsPrintfCString("could not open named temporary file %s",
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) {
250 nsPrintfCString("fputs failed: ferror %d\n", ferror(temp
.get()))
252 failedToWrite
= true;
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.
266 remove(tempFilename
);
271 if (!::ReplaceFileA(aFilename
, tempFilename
, nullptr, 0, 0, 0)) {
273 nsPrintfCString("ReplaceFileA failed: %lu\n", GetLastError()).get());
276 #elif defined(XP_UNIX)
277 if (rename(tempFilename
, aFilename
)) {
279 nsPrintfCString("rename failed: %s (%d)\n", strerror(errno
), errno
)
284 # error Do not know how to atomically replace file
290 } // namespace detail
293 // Helper method that initializes an empty va_list to be empty.
294 void empty_va(va_list* va
, ...) {
300 class LogModuleManager
{
303 : mModulesLock("logmodules"),
304 mModules(kInitialModuleCount
),
306 mLoggingModuleRegistered(0),
310 mToReleaseFile(nullptr),
312 mOutFilePath(strdup("")),
313 mMainThread(PR_GetCurrentThread()),
315 mAddTimestamp(false),
316 mCaptureProfilerStack(false),
320 mInitialized(false) {
323 ~LogModuleManager() {
324 detail::LogFile
* logFile
= mOutFile
.exchange(nullptr);
329 * Loads config from command line args or env vars if present, in
330 * this specific order of priority.
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
);
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;
360 bool captureStacks
= false;
363 bool prependHeader
= false;
364 const char* modules
= PR_GetEnv("MOZ_LOG");
365 if (!modules
|| !modules
[0]) {
366 modules
= PR_GetEnv("MOZ_LOG_MODULES");
369 "MOZ_LOG_MODULES is deprecated."
370 "\nPlease use MOZ_LOG instead.");
373 if (!modules
|| !modules
[0]) {
374 modules
= PR_GetEnv("NSPR_LOG_MODULES");
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(
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) {
391 } else if (strcmp(aName
, "timestamp") == 0) {
393 } else if (strcmp(aName
, "sync") == 0) {
395 } else if (strcmp(aName
, "raw") == 0) {
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;
406 this->CreateOrGetModule(aName
)->SetLevel(aLevel
);
410 // Rotate implies timestamp to make the files readable
411 mAddTimestamp
= addTimestamp
|| rotate
> 0;
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) {
423 "MOZ_LOG: when you rotate the log, you cannot use maxsize! (ignoring "
428 if (maxSize
> 0 && !shouldAppend
) {
430 "MOZ_LOG: when you limit the log to maxsize, you must use append! "
431 "(ignorning maxsize)");
435 if (rotate
> 0 && prependHeader
) {
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]) {
448 logFile
= detail::ExpandLogFileName(logFile
, buf
);
449 mOutFilePath
.reset(strdup(logFile
));
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
) {
462 mOutFile
= OpenFile(shouldAppend
, mOutFileNum
, maxSize
);
466 if (prependHeader
&& XRE_IsParentProcess()) {
469 Print("Logger", LogLevel::Info
, nullptr, "\n***\n\n", "Opening log\n",
474 void SetLogFile(const char* aFilename
) {
475 // For now we don't allow you to change the file at runtime.
478 "LogModuleManager::SetLogFile - Log file was set from the "
479 "MOZ_LOG_FILE environment variable.");
483 const char* filename
= aFilename
? aFilename
: "";
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.
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
) {
516 snprintf(aBuffer
, aLength
, "%s", mOutFilePath
.get());
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) {
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");
542 file
= fopen(mOutFilePath
.get(), aShouldAppend
? "a" : "w");
549 return new detail::LogFile(file
, aFileNum
);
552 void RemoveFile(uint32_t aFileNum
) {
554 SprintfLiteral(buf
, "%s.%d", mOutFilePath
.get(), aFileNum
);
558 LogModule
* CreateOrGetModule(const char* aName
) {
559 OffTheBooksMutexAutoLock
guard(mModulesLock
);
565 if (++mLoggingModuleRegistered
> kInitialModuleCount
) {
567 "kInitialModuleCount too low, consider increasing its "
571 return UniquePtr
<LogModule
>(
572 new LogModule
{aName
, LogLevel::Disabled
});
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
;
597 auto [out
, size
] = fmt::vformat_to_n(buff
, kBuffSize
- 1, aFmt
, aArgs
);
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
605 fmt::vformat_to_n(allocatedBuff
.get(), charsWritten
, aFmt
, aArgs
);
606 MOZ_ASSERT(size
== 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
;
625 va_copy(argsCopy
, aArgs
);
626 int charsWritten
= VsprintfLiteral(buff
, aFmt
, 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()) {
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",
668 MS::Searchable::Searchable
);
669 schema
.AddKeyLabelFormatSearchable("name", "Name", MS::Format::String
,
670 MS::Searchable::Searchable
);
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') {
692 // In case we use rotate, this ensures the FILE is kept alive during
693 // its use. Increased before we load mOutFile.
696 detail::LogFile
* outFile
= mOutFile
;
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
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
)
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
) {
721 fprintf_stderr(out
, "%s[%s %ld: %s]: %s/%s %s%s", aPrepend
,
722 nsDebugImpl::GetMultiprocessMode(), pid
,
723 currentThreadName
, ToLogStr(aLevel
), aName
, aLogMessage
,
726 fprintf_stderr(out
, "%s%s%s", aPrepend
, aLogMessage
, newline
);
730 // XXX is there a reasonable way to convert one to the other? this is
732 PRTime prnow
= PR_Now();
733 TimeStamp tmnow
= TimeStamp::Now();
734 TimeDuration duration
= tmnow
- *aStart
;
735 PRTime prstart
= prnow
- duration
.ToMicroseconds();
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
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
);
753 PR_ExplodeTime(PR_Now(), PR_GMTParameters
, &now
);
755 "%s%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - [%s %ld: "
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
,
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
) {
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);
807 void DisableModules() {
808 OffTheBooksMutexAutoLock
guard(mModulesLock
);
809 for (auto& m
: mModules
) {
810 (*(m
.GetModifiableData()))->SetLevel(LogLevel::Disabled
);
815 OffTheBooksMutex mModulesLock
;
816 nsClassHashtable
<nsCharPtrHashKey
, LogModule
> mModules
;
819 Atomic
<uint32_t, ReleaseAcquire
> mLoggingModuleRegistered
;
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
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
;
842 Atomic
<bool, Relaxed
> mAddTimestamp
;
843 Atomic
<bool, Relaxed
> mCaptureProfilerStack
;
844 Atomic
<bool, Relaxed
> mIsRaw
;
845 Atomic
<bool, Relaxed
> mIsSync
;
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
) {
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
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.
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
947 // This function is called by external code (rust) to log to one of our
949 void ExternMozLog(const char* aModule
, mozilla::LogLevel aLevel
,
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
);