xpcom/base/Logging.cpp
author Chris Peterson <cpeterson@mozilla.com>
Sun, 24 Feb 2019 17:35:59 -0800
changeset 464363 5ff84e853d700af894616574f581551a88a93dc7
parent 461644 739c3b30b230fe1df1b3b94ae9b6a7df9467f31a
child 467734 88c2a60599669ee7899764812730b90ce98a1450
permissions -rw-r--r--
Bug 1534878 - xpcom: Make some global functions static. r=erahm clang's -Wmissing-prototypes option identifies global functions that can be made static (because they're only called from one compilation unit) or removed (if they're never called). xpcom/base/Logging.cpp:85:13 [-Wmissing-prototypes] no previous prototype for function 'ToLogStr' xpcom/base/Logging.cpp:132:13 [-Wmissing-prototypes] no previous prototype for function 'ExpandPIDMarker' xpcom/base/LogModulePrefWatcher.cpp:37:6 [-Wmissing-prototypes] no previous prototype for function 'ResetExistingPrefs' xpcom/base/LogModulePrefWatcher.cpp:109:6 [-Wmissing-prototypes] no previous prototype for function 'LoadExistingPrefs' xpcom/base/nsCycleCollector.cpp:212:6 [-Wmissing-prototypes] no previous prototype for function 'SuspectUsingNurseryPurpleBuffer' xpcom/components/nsComponentManager.cpp:421:31 [-Wmissing-prototypes] no previous prototype for function 'begin' xpcom/components/nsComponentManager.cpp:427:31 [-Wmissing-prototypes] no previous prototype for function 'end' xpcom/ds/Dafsa.cpp:23:6 [-Wmissing-prototypes] no previous prototype for function 'GetNextOffset' xpcom/ds/Dafsa.cpp:55:6 [-Wmissing-prototypes] no previous prototype for function 'IsEOL' xpcom/ds/Dafsa.cpp:62:6 [-Wmissing-prototypes] no previous prototype for function 'IsMatch' xpcom/ds/Dafsa.cpp:70:6 [-Wmissing-prototypes] no previous prototype for function 'IsEndCharMatch' xpcom/ds/Dafsa.cpp:78:6 [-Wmissing-prototypes] no previous prototype for function 'GetReturnValue' xpcom/ds/Dafsa.cpp:91:5 [-Wmissing-prototypes] no previous prototype for function 'LookupString' xpcom/io/CocoaFileUtils.mm:195:13 [-Wmissing-prototypes] no previous prototype for function 'GetQuarantinePropKey' xpcom/io/CocoaFileUtils.mm:203:24 [-Wmissing-prototypes] no previous prototype for function 'CreateQuarantineDictionary' xpcom/rust/gtest/bench-collections/Bench.cpp:65:11 [-Wmissing-prototypes] no previous prototype for function 'MyRand' xpcom/rust/gtest/bench-collections/Bench.cpp:85:6 [-Wmissing-prototypes] no previous prototype for function 'Bench_Cpp_unordered_set' xpcom/rust/gtest/bench-collections/Bench.cpp:125:6 [-Wmissing-prototypes] no previous prototype for function 'Bench_Cpp_PLDHashTable' xpcom/rust/gtest/bench-collections/Bench.cpp:166:6 [-Wmissing-prototypes] no previous prototype for function 'Bench_Cpp_MozHashSet' xpcom/tests/gtest/TestAtoms.cpp:114:6 [-Wmissing-prototypes] no previous prototype for function 'isStaticAtom' xpcom/tests/gtest/TestCallTemplates.cpp:72:6 [-Wmissing-prototypes] no previous prototype for function 'JustTestingCompilation' xpcom/tests/gtest/TestCOMPtr.cpp:87:10 [-Wmissing-prototypes] no previous prototype for function 'CreateIFoo' xpcom/tests/gtest/TestCOMPtr.cpp:98:6 [-Wmissing-prototypes] no previous prototype for function 'set_a_IFoo' xpcom/tests/gtest/TestCOMPtr.cpp:105:16 [-Wmissing-prototypes] no previous prototype for function 'return_a_IFoo' xpcom/tests/gtest/TestCOMPtr.cpp:164:10 [-Wmissing-prototypes] no previous prototype for function 'CreateIBar' xpcom/tests/gtest/TestCOMPtr.cpp:175:6 [-Wmissing-prototypes] no previous prototype for function 'AnIFooPtrPtrContext' xpcom/tests/gtest/TestCOMPtr.cpp:177:6 [-Wmissing-prototypes] no previous prototype for function 'AVoidPtrPtrContext' xpcom/tests/gtest/TestCOMPtr.cpp:179:6 [-Wmissing-prototypes] no previous prototype for function 'AnISupportsPtrPtrContext' xpcom/tests/gtest/TestCOMPtr.cpp:263:6 [-Wmissing-prototypes] no previous prototype for function 'Comparison' xpcom/tests/gtest/TestCOMPtr.cpp:298:6 [-Wmissing-prototypes] no previous prototype for function 'DontAddRef' xpcom/tests/gtest/TestCRT.cpp:17:5 [-Wmissing-prototypes] no previous prototype for function 'sign' xpcom/tests/gtest/TestDeadlockDetector.cpp:62:6 [-Wmissing-prototypes] no previous prototype for function 'DisableCrashReporter' xpcom/tests/gtest/TestDeadlockDetector.cpp:74:5 [-Wmissing-prototypes] no previous prototype for function 'Sanity_Child' xpcom/tests/gtest/TestDeadlockDetector.cpp:95:5 [-Wmissing-prototypes] no previous prototype for function 'Sanity2_Child' xpcom/tests/gtest/TestDeadlockDetector.cpp:159:5 [-Wmissing-prototypes] no previous prototype for function 'Sanity4_Child' xpcom/tests/gtest/TestDeadlockDetector.cpp:182:5 [-Wmissing-prototypes] no previous prototype for function 'Sanity5_Child' xpcom/tests/gtest/TestDeadlockDetector.cpp:303:5 [-Wmissing-prototypes] no previous prototype for function 'ContentionNoDeadlock_Child' xpcom/tests/gtest/TestHashtables.cpp:88:6 [-Wmissing-prototypes] no previous prototype for function 'testTHashtable' xpcom/tests/gtest/TestHashtables.cpp:205:10 [-Wmissing-prototypes] no previous prototype for function 'CreateIFoo' xpcom/tests/gtest/TestMoveString.cpp:25:6 [-Wmissing-prototypes] no previous prototype for function 'SetAsOwned' xpcom/tests/gtest/TestMoveString.cpp:34:6 [-Wmissing-prototypes] no previous prototype for function 'ExpectTruncated' xpcom/tests/gtest/TestMoveString.cpp:40:6 [-Wmissing-prototypes] no previous prototype for function 'ExpectNew' xpcom/tests/gtest/TestMruCache.cpp:52:11 [-Wmissing-prototypes] no previous prototype for function 'MakeStringKey' xpcom/tests/gtest/TestMultiplexInputStream.cpp:106:34 [-Wmissing-prototypes] no previous prototype for function 'CreateStreamHelper' xpcom/tests/gtest/TestNonBlockingAsyncInputStream.cpp:62:10 [-Wmissing-prototypes] no previous prototype for function 'ReadSegmentsFunction' xpcom/tests/gtest/TestNsDeque.cpp:240:6 [-Wmissing-prototypes] no previous prototype for function 'CheckIfQueueEmpty' xpcom/tests/gtest/TestNsRefPtr.cpp:105:10 [-Wmissing-prototypes] no previous prototype for function 'CreateFoo' xpcom/tests/gtest/TestNsRefPtr.cpp:116:6 [-Wmissing-prototypes] no previous prototype for function 'set_a_Foo' xpcom/tests/gtest/TestNsRefPtr.cpp:123:13 [-Wmissing-prototypes] no previous prototype for function 'return_a_Foo' xpcom/tests/gtest/TestNsRefPtr.cpp:391:6 [-Wmissing-prototypes] no previous prototype for function 'AnFooPtrPtrContext' xpcom/tests/gtest/TestNsRefPtr.cpp:392:6 [-Wmissing-prototypes] no previous prototype for function 'AVoidPtrPtrContext' xpcom/tests/gtest/TestPLDHash.cpp:33:6 [-Wmissing-prototypes] no previous prototype for function 'TestCrashyOperation' xpcom/tests/gtest/TestPipes.cpp:98:10 [-Wmissing-prototypes] no previous prototype for function 'TestPipe' xpcom/tests/gtest/TestPipes.cpp:212:10 [-Wmissing-prototypes] no previous prototype for function 'TestShortWrites' xpcom/tests/gtest/TestPipes.cpp:354:6 [-Wmissing-prototypes] no previous prototype for function 'RunTests' xpcom/tests/gtest/TestPLDHash.cpp:90:6 [-Wmissing-prototypes] no previous prototype for function 'InitCapacityOk_InitialLengthTooBig' xpcom/tests/gtest/TestPLDHash.cpp:95:6 [-Wmissing-prototypes] no previous prototype for function 'InitCapacityOk_InitialEntryStoreTooBig' xpcom/tests/gtest/TestPLDHash.cpp:102:6 [-Wmissing-prototypes] no previous prototype for function 'InitCapacityOk_EntrySizeTooBig' xpcom/tests/gtest/TestSlicedInputStream.cpp:111:20 [-Wmissing-prototypes] no previous prototype for function 'CreateSeekableStreams' xpcom/tests/gtest/TestSlicedInputStream.cpp:125:20 [-Wmissing-prototypes] no previous prototype for function 'CreateNonSeekableStreams' xpcom/tests/gtest/TestStrings.cpp:471:6 [-Wmissing-prototypes] no previous prototype for function 'test_assign_helper' xpcom/tests/gtest/TestTArray.cpp:60:22 [-Wmissing-prototypes] no previous prototype for function 'DummyArray' xpcom/tests/gtest/TestTArray.cpp:72:22 [-Wmissing-prototypes] no previous prototype for function 'FakeHugeArray' xpcom/tests/gtest/TestThrottledEventQueue.cpp:96:6 [-Wmissing-prototypes] no previous prototype for function 'Enqueue' xpcom/threads/BlockingResourceBase.cpp:86:6 [-Wmissing-prototypes] no previous prototype for function 'PrintCycle' xpcom/threads/CPUUsageWatcher.cpp:41:10 [-Wmissing-prototypes] no previous prototype for function 'GetMicroseconds' xpcom/threads/CPUUsageWatcher.cpp:46:10 [-Wmissing-prototypes] no previous prototype for function 'GetMicroseconds' xpcom/threads/CPUUsageWatcher.cpp:51:40 [-Wmissing-prototypes] no previous prototype for function 'GetProcessCPUStats' xpcom/threads/CPUUsageWatcher.cpp:80:40 [-Wmissing-prototypes] no previous prototype for function 'GetGlobalCPUStats' xpcom/threads/nsTimerImpl.cpp:196:21 [-Wmissing-prototypes] no previous prototype for function 'GetTimerFiringsLog' Differential Revision: https://phabricator.services.mozilla.com/D23264

/* -*- 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 "mozilla/Logging.h"

#include <algorithm>

#include "mozilla/ClearOnShutdown.h"
#include "mozilla/FileUtils.h"
#include "mozilla/Mutex.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/Printf.h"
#include "mozilla/Atomics.h"
#include "mozilla/Sprintf.h"
#include "mozilla/UniquePtrExtensions.h"
#include "MainThreadUtils.h"
#include "nsClassHashtable.h"
#include "nsDebug.h"
#include "nsDebugImpl.h"
#include "NSPRLogModulesParser.h"
#include "LogCommandLineHandler.h"
#ifdef MOZ_GECKO_PROFILER
#  include "ProfilerMarkerPayload.h"
#endif

#include "prenv.h"
#ifdef XP_WIN
#  include <process.h>
#else
#  include <sys/types.h>
#  include <unistd.h>
#endif

// NB: Initial amount determined by auditing the codebase for the total amount
//     of unique module names and padding up to the next power of 2.
const uint32_t kInitialModuleCount = 256;
// 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;

namespace mozilla {

LazyLogModule::operator LogModule*() {
  // NB: The use of an atomic makes the reading and assignment of mLog
  //     thread-safe. There is a small chance that mLog will be set more
  //     than once, but that's okay as it will be set to the same LogModule
  //     instance each time. Also note LogModule::Get is thread-safe.
  LogModule* tmp = mLog;
  if (MOZ_UNLIKELY(!tmp)) {
    tmp = LogModule::Get(mLogName);
    mLog = tmp;
  }

  return tmp;
}

namespace detail {

void log_print(const LogModule* aModule, LogLevel aLevel, const char* aFmt,
               ...) {
  va_list ap;
  va_start(ap, aFmt);
  aModule->Printv(aLevel, aFmt, ap);
  va_end(ap);
}

}  // namespace detail

LogLevel ToLogLevel(int32_t aLevel) {
  aLevel = std::min(aLevel, static_cast<int32_t>(LogLevel::Verbose));
  aLevel = std::max(aLevel, static_cast<int32_t>(LogLevel::Disabled));
  return static_cast<LogLevel>(aLevel);
}

static const char* 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;

 public:
  LogFile(FILE* aFile, uint32_t aFileNum)
      : mFile(aFile), mFileNum(aFileNum), mNextToRelease(nullptr) {}

  ~LogFile() {
    fclose(mFile);
    delete mNextToRelease;
  }

  FILE* File() const { return mFile; }
  uint32_t Num() const { return mFileNum; }

  LogFile* mNextToRelease;
};

static const char* ExpandPIDMarker(const char* aFilename,
                                   char (&buffer)[2048]) {
  MOZ_ASSERT(aFilename);
  static const char kPIDToken[] = "%PID";
  const char* pidTokenPtr = strstr(aFilename, kPIDToken);
  if (pidTokenPtr &&
      SprintfLiteral(
          buffer, "%.*s%s%d%s", static_cast<int>(pidTokenPtr - aFilename),
          aFilename, XRE_IsParentProcess() ? "-main." : "-child.",
          base::GetCurrentProcId(), pidTokenPtr + strlen(kPIDToken)) > 0) {
    return buffer;
  }

  return aFilename;
}

}  // namespace detail

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

class LogModuleManager {
 public:
  LogModuleManager()
      // As for logging atomics, don't preserve behavior for this lock when
      // recording/replaying.
      : mModulesLock("logmodules", recordreplay::Behavior::DontPreserve),
        mModules(kInitialModuleCount),
        mPrintEntryCount(0),
        mOutFile(nullptr),
        mToReleaseFile(nullptr),
        mOutFileNum(0),
        mOutFilePath(strdup("")),
        mMainThread(PR_GetCurrentThread()),
        mSetFromEnv(false),
        mAddTimestamp(false),
        mAddProfilerMarker(false),
        mIsRaw(false),
        mIsSync(false),
        mRotate(0),
        mInitialized(false) {}

  ~LogModuleManager() {
    detail::LogFile* logFile = mOutFile.exchange(nullptr);
    delete logFile;
  }

  /**
   * 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<char const* 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 isMarkers = false;
    int32_t rotate = 0;
    const char* 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.");
      }
    }

    // Need to capture `this` since `sLogModuleManager` is not set until after
    // initialization is complete.
    NSPRLogModulesParser(
        modules, [this, &shouldAppend, &addTimestamp, &isSync, &isRaw, &rotate,
                  &isMarkers](const char* aName, LogLevel aLevel,
                              int32_t aValue) mutable {
          if (strcmp(aName, "append") == 0) {
            shouldAppend = true;
          } else if (strcmp(aName, "timestamp") == 0) {
            addTimestamp = true;
          } else if (strcmp(aName, "sync") == 0) {
            isSync = true;
          } else if (strcmp(aName, "raw") == 0) {
            isRaw = true;
          } else if (strcmp(aName, "rotate") == 0) {
            rotate = (aValue << 20) / kRotateFilesNumber;
          } else if (strcmp(aName, "profilermarkers") == 0) {
            isMarkers = true;
          } else {
            this->CreateOrGetModule(aName)->SetLevel(aLevel);
          }
        });

    // Rotate implies timestamp to make the files readable
    mAddTimestamp = addTimestamp || rotate > 0;
    mIsSync = isSync;
    mIsRaw = isRaw;
    mRotate = rotate;
    mAddProfilerMarker = isMarkers;

    if (rotate > 0 && shouldAppend) {
      NS_WARNING("MOZ_LOG: when you rotate the log, you cannot use append!");
    }

    const char* logFile = PR_GetEnv("MOZ_LOG_FILE");
    if (!logFile || !logFile[0]) {
      logFile = PR_GetEnv("NSPR_LOG_FILE");
    }

    if (logFile && logFile[0]) {
      char buf[2048];
      logFile = detail::ExpandPIDMarker(logFile, buf);
      mOutFilePath.reset(strdup(logFile));

      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);
        }
      }

      mOutFile = OpenFile(shouldAppend, mOutFileNum);
      mSetFromEnv = true;
    }
  }

  void SetLogFile(const char* 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;
    }

    const char* filename = aFilename ? aFilename : "";
    char buf[2048];
    filename = detail::ExpandPIDMarker(filename, buf);

    // Can't use rotate 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);
    }
  }

  uint32_t GetLogFile(char* aBuffer, size_t aLength) {
    uint32_t len = strlen(mOutFilePath.get());
    if (len + 1 > aLength) {
      return 0;
    }
    snprintf(aBuffer, aLength, "%s", mOutFilePath.get());
    return len;
  }

  void SetIsSync(bool aIsSync) { mIsSync = aIsSync; }

  void SetAddTimestamp(bool aAddTimestamp) { mAddTimestamp = aAddTimestamp; }

  detail::LogFile* OpenFile(bool aShouldAppend, uint32_t aFileNum) {
    FILE* file;

    if (mRotate > 0) {
      char buf[2048];
      SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum);

      // rotate doesn't support append.
      file = fopen(buf, "w");
    } else {
      file = fopen(mOutFilePath.get(), aShouldAppend ? "a" : "w");
    }

    if (!file) {
      return nullptr;
    }

    return new detail::LogFile(file, aFileNum);
  }

  void RemoveFile(uint32_t aFileNum) {
    char buf[2048];
    SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum);
    remove(buf);
  }

  LogModule* CreateOrGetModule(const char* aName) {
    OffTheBooksMutexAutoLock guard(mModulesLock);
    LogModule* module = nullptr;
    if (!mModules.Get(aName, &module)) {
      module = new LogModule(aName, LogLevel::Disabled);
      mModules.Put(aName, module);
    }

    return module;
  }

  void Print(const char* aName, LogLevel aLevel, const char* aFmt,
             va_list aArgs) MOZ_FORMAT_PRINTF(4, 0) {
    // We don't do nuwa-style forking anymore, so our pid can't change.
    static long pid = static_cast<long>(base::GetCurrentProcId());
    const size_t kBuffSize = 1024;
    char buff[kBuffSize];

    char* buffToWrite = buff;
    SmprintfPointer allocatedBuff;

    va_list argsCopy;
    va_copy(argsCopy, aArgs);
    int charsWritten = VsprintfLiteral(buff, aFmt, argsCopy);
    va_end(argsCopy);

    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);
    } else if (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);
    }

#ifdef MOZ_GECKO_PROFILER
    if (mAddProfilerMarker && profiler_is_active()) {
      profiler_add_marker(
          "LogMessages", JS::ProfilingCategoryPair::OTHER,
          MakeUnique<LogMarkerPayload>(aName, buffToWrite, TimeStamp::Now()));
    }
#endif

    // Determine if a newline needs to be appended to the message.
    const char* newline = "";
    if (charsWritten == 0 || buffToWrite[charsWritten - 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();
    const char* currentThreadName = (mMainThread == currentThread)
                                        ? "Main Thread"
                                        : PR_GetThreadName(currentThread);

    char noNameThread[40];
    if (!currentThreadName) {
      SprintfLiteral(noNameThread, "Unnamed thread %p", currentThread);
      currentThreadName = noNameThread;
    }

    if (!mAddTimestamp) {
      if (!mIsRaw) {
        fprintf_stderr(out, "[%s %ld: %s]: %s/%s %s%s",
                       nsDebugImpl::GetMultiprocessMode(), pid,
                       currentThreadName, ToLogStr(aLevel), aName, buffToWrite,
                       newline);
      } else {
        fprintf_stderr(out, "%s%s", buffToWrite, newline);
      }
    } else {
      PRExplodedTime now;
      PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now);
      fprintf_stderr(
          out,
          "%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - [%s %ld: %s]: %s/%s %s%s",
          now.tm_year, now.tm_month + 1, now.tm_mday, now.tm_hour, now.tm_min,
          now.tm_sec, now.tm_usec, nsDebugImpl::GetMultiprocessMode(), pid,
          currentThreadName, ToLogStr(aLevel), aName, buffToWrite, newline);
    }

    if (mIsSync) {
      fflush(out);
    }

    if (mRotate > 0 && outFile) {
      int32_t fileSize = ftell(out);
      if (fileSize > mRotate) {
        uint32_t fileNum = outFile->Num();

        uint32_t nextFileNum = fileNum + 1;
        if (nextFileNum >= kRotateFilesNumber) {
          nextFileNum = 0;
        }

        // 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;
    }
  }

 private:
  OffTheBooksMutex mModulesLock;
  nsClassHashtable<nsCharPtrHashKey, LogModule> mModules;

  // 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;

  PRThread* mMainThread;
  bool mSetFromEnv;
  Atomic<bool, Relaxed> mAddTimestamp;
  Atomic<bool, Relaxed> mAddProfilerMarker;
  Atomic<bool, Relaxed> mIsRaw;
  Atomic<bool, Relaxed> mIsSync;
  int32_t mRotate;
  bool mInitialized;
};

StaticAutoPtr<LogModuleManager> sLogModuleManager;

LogModule* LogModule::Get(const char* 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);
}

void LogModule::SetLogFile(const char* aFilename) {
  MOZ_ASSERT(sLogModuleManager);
  sLogModuleManager->SetLogFile(aFilename);
}

uint32_t LogModule::GetLogFile(char* aBuffer, size_t aLength) {
  MOZ_ASSERT(sLogModuleManager);
  return sLogModuleManager->GetLogFile(aBuffer, aLength);
}

void LogModule::SetAddTimestamp(bool aAddTimestamp) {
  sLogModuleManager->SetAddTimestamp(aAddTimestamp);
}

void LogModule::SetIsSync(bool aIsSync) {
  sLogModuleManager->SetIsSync(aIsSync);
}

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;
}

void LogModule::Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const {
  MOZ_ASSERT(sLogModuleManager != nullptr);

  // Forward to LogModule manager w/ level and name
  sLogModuleManager->Print(Name(), aLevel, aFmt, aArgs);
}

}  // namespace mozilla