/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include"prenv.h" #ifdef XP_WIN # include <fcntl.h> # include <process.h> #else # include <sys/stat.h> // for umask() # include <sys/types.h> # include <unistd.h> #endif
// NB: Amount determined by performing a typical browsing session and finding // the maximum number of modules instantiated, and padding up to the next // power of 2. const uint32_t kInitialModuleCount = 1024; // When rotate option is added to the modules list, this is the hardcoded // number of files we create and rotate. When there is rotate:40, // we will keep four files per process, each limited to 10MB. Sum is 40MB, // the given limit. // // (Note: When this is changed to be >= 10, SandboxBroker::LaunchApp must add // another rule to allow logfile.?? be written by content processes.) const uint32_t kRotateFilesNumber = 4;
staticconstchar* ToLogStr(LogLevel aLevel) { switch (aLevel) { case LogLevel::Error: return"E"; case LogLevel::Warning: return"W"; case LogLevel::Info: return"I"; case LogLevel::Debug: return"D"; case LogLevel::Verbose: return"V"; case LogLevel::Disabled: default:
MOZ_CRASH("Invalid log level."); return"";
}
}
namespace detail {
/** * A helper class providing reference counting for FILE*. * It encapsulates the following: * - the FILE handle * - the order number it was created for when rotating (actual path) * - number of active references
*/ class LogFile {
FILE* mFile;
uint32_t mFileNum;
// Drop initial lines from the given file until it is less than or equal to the // given size. // // For simplicity and to reduce memory consumption, lines longer than the given // long line size may be broken. // // This function uses `mkstemp` and `rename` on POSIX systems and `_mktemp_s` // and `ReplaceFileA` on Win32 systems. `ReplaceFileA` was introduced in // Windows 7 so it's available. bool LimitFileToLessThanSize(constchar* aFilename, uint32_t aSize,
uint16_t aLongLineSize = 16384) { // `tempFilename` will be further updated below. char tempFilename[2048];
SprintfLiteral(tempFilename, "%s.tempXXXXXX", aFilename);
bool failedToWrite = false;
{ // Scope `file` and `temp`, so that they are definitely closed.
ScopedCloseFile file(fopen(aFilename, "rb")); if (!file) { returnfalse;
}
if (fseek(file.get(), 0, SEEK_END)) { // If we can't seek for some reason, better to just not limit the log at // all and hope to sort out large logs upon further analysis. returnfalse;
}
// `ftell` returns a positive `long`, which might be more than 32 bits.
uint64_t fileSize = static_cast<uint64_t>(ftell(file.get()));
// Coverity would prefer us to set a secure umask before using `mkstemp`. // However, the umask is process-wide, so setting it may lead to difficult // to debug complications; and it is fine for this particular short-lived // temporary file to be insecure. // // coverity[SECURE_TEMP : FALSE] int fd = mkstemp(tempFilename); if (fd == -1) {
NS_WARNING("mkstemp failed!"); returnfalse;
}
temp.reset(fdopen(fd, "ab")); #else # error Donot know how to open named temporary file #endif
if (!temp) {
NS_WARNING(nsPrintfCString("could not open named temporary file %s",
tempFilename)
.get()); returnfalse;
}
// `fgets` always null terminates. If the line is too long, it won't // include a trailing '\n' but will be null-terminated.
UniquePtr<char[]> line = MakeUnique<char[]>(aLongLineSize + 1); while (fgets(line.get(), aLongLineSize + 1, file.get())) { if (numBytesDropped >= minBytesToDrop) { if (fputs(line.get(), temp.get()) < 0) {
NS_WARNING(
nsPrintfCString("fputs failed: ferror %d\n", ferror(temp.get()))
.get());
failedToWrite = true; break;
}
} else { // Binary mode avoids platform-specific wrinkles with text streams. In // particular, on Windows, `\r\n` gets read as `\n` (and the reverse // when writing), complicating this calculation.
numBytesDropped += strlen(line.get());
}
}
}
// At this point, `file` and `temp` are closed, so we can remove and rename. if (failedToWrite) {
remove(tempFilename); returnfalse;
}
namespace { // Helper method that initializes an empty va_list to be empty. void empty_va(va_list* va, ...) {
va_start(*va, va);
va_end(*va);
}
} // namespace
/** * Loads config from command line args or env vars if present, in * this specific order of priority. * * Notes: * * 1) This function is only intended to be called once per session. * 2) None of the functions used in Init should rely on logging.
*/ void Init(int argc, char* argv[]) {
MOZ_DIAGNOSTIC_ASSERT(!mInitialized);
mInitialized = true;
LoggingHandleCommandLineArgs(argc, static_cast<charconst* const*>(argv),
[](nsACString const& env) { // We deliberately set/rewrite the // environment variables so that when child // processes are spawned w/o passing the // arguments they still inherit the logging // settings as well as sandboxing can be // correctly set. Scripts can pass // -MOZ_LOG=$MOZ_LOG,modules as an argument // to merge existing settings, if required.
// PR_SetEnv takes ownership of the string.
PR_SetEnv(ToNewCString(env));
});
bool shouldAppend = false; bool addTimestamp = false; bool isSync = false; bool isRaw = false; bool captureStacks = false;
int32_t rotate = 0;
int32_t maxSize = 0; bool prependHeader = false; constchar* modules = PR_GetEnv("MOZ_LOG"); if (!modules || !modules[0]) {
modules = PR_GetEnv("MOZ_LOG_MODULES"); if (modules) {
NS_WARNING( "MOZ_LOG_MODULES is deprecated." "\nPlease use MOZ_LOG instead.");
}
} if (!modules || !modules[0]) {
modules = PR_GetEnv("NSPR_LOG_MODULES"); if (modules) {
NS_WARNING( "NSPR_LOG_MODULES is deprecated." "\nPlease use MOZ_LOG instead.");
}
}
// Rotate implies timestamp to make the files readable
mAddTimestamp = addTimestamp || rotate > 0;
mIsSync = isSync;
mIsRaw = isRaw;
mRotate = rotate;
mCaptureProfilerStack = captureStacks;
if (rotate > 0 && shouldAppend) {
NS_WARNING("MOZ_LOG: when you rotate the log, you cannot use append!");
}
if (rotate > 0 && maxSize > 0) {
NS_WARNING( "MOZ_LOG: when you rotate the log, you cannot use maxsize! (ignoring " "maxsize)");
maxSize = 0;
}
if (maxSize > 0 && !shouldAppend) {
NS_WARNING( "MOZ_LOG: when you limit the log to maxsize, you must use append! " "(ignorning maxsize)");
maxSize = 0;
}
if (rotate > 0 && prependHeader) {
NS_WARNING( "MOZ_LOG: when you rotate the log, you cannot use prependheader!");
prependHeader = false;
}
if (mRotate > 0) { // Delete all the previously captured files, including non-rotated // log files, so that users don't complain our logs eat space even // after the rotate option has been added and don't happen to send // us old large logs along with the rotated files.
remove(mOutFilePath.get()); for (uint32_t i = 0; i < kRotateFilesNumber; ++i) {
RemoveFile(i);
}
}
void SetLogFile(constchar* aFilename) { // For now we don't allow you to change the file at runtime. if (mSetFromEnv) {
NS_WARNING( "LogModuleManager::SetLogFile - Log file was set from the " "MOZ_LOG_FILE environment variable."); return;
}
// Can't use rotate or maxsize at runtime yet.
MOZ_ASSERT(mRotate == 0, "We don't allow rotate for runtime logfile changes");
mOutFilePath.reset(strdup(filename));
// Exchange mOutFile and set it to be released once all the writes are done.
detail::LogFile* newFile = OpenFile(false, 0);
detail::LogFile* oldFile = mOutFile.exchange(newFile);
// Since we don't allow changing the logfile if MOZ_LOG_FILE is already set, // and we don't allow log rotation when setting it at runtime, // mToReleaseFile will be null, so we're not leaking.
DebugOnly<detail::LogFile*> prevFile = mToReleaseFile.exchange(oldFile);
MOZ_ASSERT(!prevFile, "Should be null because rotation is not allowed");
// If we just need to release a file, we must force print, in order to // trigger the closing and release of mToReleaseFile. if (oldFile) {
va_list va;
empty_va(&va);
Print("Logger", LogLevel::Info, "Flushing old log files\n", va);
}
}
if (charsWritten < 0) { // Print out at least something. We must copy to the local buff, // can't just assign aFmt to buffToWrite, since when // buffToWrite != buff, we try to release it.
MOZ_ASSERT(false, "Probably incorrect format string in LOG?");
strncpy(buff, aFmt, kBuffSize - 1);
buff[kBuffSize - 1] = '\0';
charsWritten = strlen(buff);
} elseif (static_cast<size_t>(charsWritten) >= kBuffSize - 1) { // We may have maxed out, allocate a buffer instead.
allocatedBuff = mozilla::Vsmprintf(aFmt, aArgs);
buffToWrite = allocatedBuff.get();
charsWritten = strlen(buffToWrite);
}
ActuallyLog(aName, aLevel, aStart, aPrepend, buffToWrite, charsWritten);
}
// Determine if a newline needs to be appended to the message. constchar* newline = ""; if (aLogMessageSize == 0 || aLogMessage[aLogMessageSize - 1] != '\n') {
newline = "\n";
}
FILE* out = stderr;
// In case we use rotate, this ensures the FILE is kept alive during // its use. Increased before we load mOutFile.
++mPrintEntryCount;
detail::LogFile* outFile = mOutFile; if (outFile) {
out = outFile->File();
}
// This differs from the NSPR format in that we do not output the // opaque system specific thread pointer (ie pthread_t) cast // to a long. The address of the current PR_Thread continues to be // prefixed. // // Additionally we prefix the output with the abbreviated log level // and the module name.
PRThread* currentThread = PR_GetCurrentThread(); constchar* currentThreadName = (mMainThread == currentThread)
? "Main Thread"
: PR_GetThreadName(currentThread);
// And here is the trick. The current out-file remembers its order // number. When no other thread shifted the global file number yet, // we are the thread to open the next file. if (mOutFileNum.compareExchange(fileNum, nextFileNum)) { // We can work with mToReleaseFile because we are sure the // mPrintEntryCount can't drop to zero now - the condition // to actually delete what's stored in that member. // And also, no other thread can enter this piece of code // because mOutFile is still holding the current file with // the non-shifted number. The compareExchange() above is // a no-op for other threads.
outFile->mNextToRelease = mToReleaseFile;
mToReleaseFile = outFile;
mOutFile = OpenFile(false, nextFileNum);
}
}
}
if (--mPrintEntryCount == 0 && mToReleaseFile) { // We were the last Print() entered, if there is a file to release // do it now. exchange() is atomic and makes sure we release the file // only once on one thread.
detail::LogFile* release = mToReleaseFile.exchange(nullptr); delete release;
}
}
void DisableModules() {
OffTheBooksMutexAutoLock guard(mModulesLock); for (auto& m : mModules) {
(*(m.GetModifiableData()))->SetLevel(LogLevel::Disabled);
}
}
#ifdef DEBUG
Atomic<uint32_t, ReleaseAcquire> mLoggingModuleRegistered; #endif // Print() entry counter, actually reflects concurrent use of the current // output file. ReleaseAcquire ensures that manipulation with mOutFile // and mToReleaseFile is synchronized by manipulation with this value.
Atomic<uint32_t, ReleaseAcquire> mPrintEntryCount; // File to write to. ReleaseAcquire because we need to sync mToReleaseFile // with this.
Atomic<detail::LogFile*, ReleaseAcquire> mOutFile; // File to be released when reference counter drops to zero. This member // is assigned mOutFile when the current file has reached the limit. // It can be Relaxed, since it's synchronized with mPrintEntryCount // manipulation and we do atomic exchange() on it.
Atomic<detail::LogFile*, Relaxed> mToReleaseFile; // The next file number. This is mostly only for synchronization sake. // Can have relaxed ordering, since we only do compareExchange on it which // is atomic regardless ordering.
Atomic<uint32_t, Relaxed> mOutFileNum; // Just keeps the actual file path for further use.
UniqueFreePtr<char[]> mOutFilePath;
LogModule* LogModule::Get(constchar* aName) { // This is just a pass through to the LogModuleManager so // that the LogModuleManager implementation can be kept internal.
MOZ_ASSERT(sLogModuleManager != nullptr); return sLogModuleManager->CreateOrGetModule(aName);
}
// This function is defined in gecko_logger/src/lib.rs // We mirror the level in rust code so we don't get forwarded all of the // rust logging and have to create an LogModule for each rust component. extern"C"void set_rust_log_level(constchar* name, uint8_t level);
// If the log module contains `::` it is likely a rust module, so we // pass the level into the rust code so it will know to forward the logging // to Gecko. if (strstr(mName, "::")) {
set_rust_log_level(mName, static_cast<uint8_t>(level));
}
// The following enables the propagation of runtime-set log levels (for // example configured via `about:logging`) from the Mozilla logging system to // OpenTelemetry's internal logging mechanism. if (strcmp(mName, "opentelemetry") == 0) {
gecko_trace::SetOpenTelemetryInternalLogLevel(level);
}
}
void LogModule::Init(int argc, char* argv[]) { // NB: This method is not threadsafe; it is expected to be called very early // in startup prior to any other threads being run.
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
if (sLogModuleManager) { // Already initialized. return;
}
// NB: We intentionally do not register for ClearOnShutdown as that happens // before all logging is complete. And, yes, that means we leak, but // we're doing that intentionally.
// Don't assign the pointer until after Init is called. This should help us // detect if any of the functions called by Init somehow rely on logging. auto mgr = new LogModuleManager();
mgr->Init(argc, argv);
sLogModuleManager = mgr;
}
// Forward to LogModule manager w/ level and name
sLogModuleManager->PrintFmt(Name(), aLevel, aFmt, aArgs);
}
} // namespace mozilla
extern"C" {
// This function is called by external code (rust) to log to one of our // log modules. void ExternMozLog(constchar* aModule, mozilla::LogLevel aLevel, constchar* aMsg) {
MOZ_ASSERT(mozilla::sLogModuleManager != nullptr);
mozilla::LogModule* m =
mozilla::sLogModuleManager->CreateOrGetModule(aModule); if (MOZ_LOG_TEST(m, aLevel)) {
mozilla::detail::log_print(m, aLevel, "%s", aMsg);
}
}
} // extern "C"
¤ Dauer der Verarbeitung: 0.23 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.