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"
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"
25 #include "nsDebugImpl.h"
26 #include "nsPrintfCString.h"
27 #include "NSPRLogModulesParser.h"
28 #include "nsXULAppAPI.h"
29 #include "LogCommandLineHandler.h"
36 # include <sys/stat.h> // for umask()
37 # include <sys/types.h>
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
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,
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;
58 void log_print(const LogModule
* aModule
, LogLevel aLevel
, const char* aFmt
,
62 aModule
->Printv(aLevel
, aFmt
, ap
);
66 void log_print(const LogModule
* aModule
, LogLevel aLevel
, TimeStamp
* aStart
,
67 const char* aFmt
, ...) {
70 aModule
->Printv(aLevel
, aStart
, aFmt
, ap
);
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
) {
86 case LogLevel::Warning
:
92 case LogLevel::Verbose
:
94 case LogLevel::Disabled
:
96 MOZ_CRASH("Invalid log level.");
104 * A helper class providing reference counting for FILE*.
105 * It encapsulates the following:
107 * - the order number it was created for when rotating (actual path)
108 * - number of active references
115 LogFile(FILE* aFile
, uint32_t aFileNum
)
116 : mFile(aFile
), mFileNum(aFileNum
), mNextToRelease(nullptr) {}
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
);
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) {
148 if (!hasMozLogExtension
&&
149 SprintfLiteral(buffer
, "%s%s", aFilename
, kMOZLOGExt
) > 0) {
156 // Drop initial lines from the given file until it is less than or equal to the
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"));
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.
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
) {
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.
200 ScopedCloseFile temp
;
203 // This approach was cribbed from
204 // https://searchfox.org/mozilla-central/rev/868935867c6241e1302e64cf9be8f56db0fd0d1c/xpcom/build/LateWriteChecks.cpp#158.
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");
218 int fd
= _open_osfhandle((intptr_t)hFile
, _O_APPEND
);
220 NS_WARNING("_open_osfhandle failed!");
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
);
235 NS_WARNING("mkstemp failed!");
238 temp
.reset(fdopen(fd
, "ab"));
240 # error Do not know how to open named temporary file
244 NS_WARNING(nsPrintfCString("could not open named temporary file %s",
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) {
257 nsPrintfCString("fputs failed: ferror %d\n", ferror(temp
)).get());
258 failedToWrite
= true;
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.
272 remove(tempFilename
);
277 if (!::ReplaceFileA(aFilename
, tempFilename
, nullptr, 0, 0, 0)) {
279 nsPrintfCString("ReplaceFileA failed: %lu\n", GetLastError()).get());
282 #elif defined(XP_UNIX)
283 if (rename(tempFilename
, aFilename
)) {
285 nsPrintfCString("rename failed: %s (%d)\n", strerror(errno
), errno
)
290 # error Do not know how to atomically replace file
296 } // namespace detail
299 // Helper method that initializes an empty va_list to be empty.
300 void empty_va(va_list* va
, ...) {
306 class LogModuleManager
{
309 : mModulesLock("logmodules"),
310 mModules(kInitialModuleCount
),
312 mLoggingModuleRegistered(0),
316 mToReleaseFile(nullptr),
318 mOutFilePath(strdup("")),
319 mMainThread(PR_GetCurrentThread()),
321 mAddTimestamp(false),
322 mCaptureProfilerStack(false),
326 mInitialized(false) {
329 ~LogModuleManager() {
330 detail::LogFile
* logFile
= mOutFile
.exchange(nullptr);
335 * Loads config from command line args or env vars if present, in
336 * this specific order of priority.
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
);
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;
366 bool captureStacks
= false;
369 bool prependHeader
= false;
370 const char* modules
= PR_GetEnv("MOZ_LOG");
371 if (!modules
|| !modules
[0]) {
372 modules
= PR_GetEnv("MOZ_LOG_MODULES");
375 "MOZ_LOG_MODULES is deprecated."
376 "\nPlease use MOZ_LOG instead.");
379 if (!modules
|| !modules
[0]) {
380 modules
= PR_GetEnv("NSPR_LOG_MODULES");
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(
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) {
397 } else if (strcmp(aName
, "timestamp") == 0) {
399 } else if (strcmp(aName
, "sync") == 0) {
401 } else if (strcmp(aName
, "raw") == 0) {
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;
412 this->CreateOrGetModule(aName
)->SetLevel(aLevel
);
416 // Rotate implies timestamp to make the files readable
417 mAddTimestamp
= addTimestamp
|| rotate
> 0;
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) {
429 "MOZ_LOG: when you rotate the log, you cannot use maxsize! (ignoring "
434 if (maxSize
> 0 && !shouldAppend
) {
436 "MOZ_LOG: when you limit the log to maxsize, you must use append! "
437 "(ignorning maxsize)");
441 if (rotate
> 0 && prependHeader
) {
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]) {
454 logFile
= detail::ExpandLogFileName(logFile
, buf
);
455 mOutFilePath
.reset(strdup(logFile
));
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
) {
468 mOutFile
= OpenFile(shouldAppend
, mOutFileNum
, maxSize
);
472 if (prependHeader
&& XRE_IsParentProcess()) {
475 Print("Logger", LogLevel::Info
, nullptr, "\n***\n\n", "Opening log\n",
480 void SetLogFile(const char* aFilename
) {
481 // For now we don't allow you to change the file at runtime.
484 "LogModuleManager::SetLogFile - Log file was set from the "
485 "MOZ_LOG_FILE environment variable.");
489 const char* filename
= aFilename
? aFilename
: "";
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.
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
) {
522 snprintf(aBuffer
, aLength
, "%s", mOutFilePath
.get());
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) {
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");
548 file
= fopen(mOutFilePath
.get(), aShouldAppend
? "a" : "w");
555 return new detail::LogFile(file
, aFileNum
);
558 void RemoveFile(uint32_t aFileNum
) {
560 SprintfLiteral(buf
, "%s.%d", mOutFilePath
.get(), aFileNum
);
564 LogModule
* CreateOrGetModule(const char* aName
) {
565 OffTheBooksMutexAutoLock
guard(mModulesLock
);
571 if (++mLoggingModuleRegistered
> kInitialModuleCount
) {
573 "kInitialModuleCount too low, consider increasing its "
577 return UniquePtr
<LogModule
>(
578 new LogModule
{aName
, LogLevel::Disabled
});
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
;
600 va_copy(argsCopy
, aArgs
);
601 int charsWritten
= VsprintfLiteral(buff
, aFmt
, 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()) {
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",
637 MS::Searchable::Searchable
);
638 schema
.AddKeyLabelFormatSearchable("name", "Name", MS::Format::String
,
639 MS::Searchable::Searchable
);
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') {
661 // In case we use rotate, this ensures the FILE is kept alive during
662 // its use. Increased before we load mOutFile.
665 detail::LogFile
* outFile
= mOutFile
;
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
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
)
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
) {
690 fprintf_stderr(out
, "%s[%s %ld: %s]: %s/%s %s%s", aPrepend
,
691 nsDebugImpl::GetMultiprocessMode(), pid
,
692 currentThreadName
, ToLogStr(aLevel
), aName
, buffToWrite
,
695 fprintf_stderr(out
, "%s%s%s", aPrepend
, buffToWrite
, newline
);
699 // XXX is there a reasonable way to convert one to the other? this is
701 PRTime prnow
= PR_Now();
702 TimeStamp tmnow
= TimeStamp::Now();
703 TimeDuration duration
= tmnow
- *aStart
;
704 PRTime prstart
= prnow
- duration
.ToMicroseconds();
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
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
);
722 PR_ExplodeTime(PR_Now(), PR_GMTParameters
, &now
);
724 "%s%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - [%s %ld: "
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
,
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
) {
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);
777 OffTheBooksMutex mModulesLock
;
778 nsClassHashtable
<nsCharPtrHashKey
, LogModule
> mModules
;
781 Atomic
<uint32_t, ReleaseAcquire
> mLoggingModuleRegistered
;
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
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
;
804 Atomic
<bool, Relaxed
> mAddTimestamp
;
805 Atomic
<bool, Relaxed
> mCaptureProfilerStack
;
806 Atomic
<bool, Relaxed
> mIsRaw
;
807 Atomic
<bool, Relaxed
> mIsSync
;
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
) {
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
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.
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
899 // This function is called by external code (rust) to log to one of our
901 void ExternMozLog(const char* aModule
, mozilla::LogLevel aLevel
,
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
);