Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/xpcom/base/Logging.cpp
Line
Count
Source (jump to first uncovered line)
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/. */
6
7
#include "mozilla/Logging.h"
8
9
#include <algorithm>
10
11
#include "mozilla/ClearOnShutdown.h"
12
#include "mozilla/FileUtils.h"
13
#include "mozilla/Mutex.h"
14
#include "mozilla/StaticPtr.h"
15
#include "mozilla/Printf.h"
16
#include "mozilla/Atomics.h"
17
#include "mozilla/Sprintf.h"
18
#include "mozilla/UniquePtrExtensions.h"
19
#include "MainThreadUtils.h"
20
#include "nsClassHashtable.h"
21
#include "nsDebug.h"
22
#include "nsDebugImpl.h"
23
#include "NSPRLogModulesParser.h"
24
#include "LogCommandLineHandler.h"
25
26
#include "prenv.h"
27
#ifdef XP_WIN
28
#include <process.h>
29
#else
30
#include <sys/types.h>
31
#include <unistd.h>
32
#endif
33
34
// NB: Initial amount determined by auditing the codebase for the total amount
35
//     of unique module names and padding up to the next power of 2.
36
const uint32_t kInitialModuleCount = 256;
37
// When rotate option is added to the modules list, this is the hardcoded
38
// number of files we create and rotate.  When there is rotate:40,
39
// we will keep four files per process, each limited to 10MB.  Sum is 40MB,
40
// the given limit.
41
//
42
// (Note: When this is changed to be >= 10, SandboxBroker::LaunchApp must add
43
// another rule to allow logfile.?? be written by content processes.)
44
const uint32_t kRotateFilesNumber = 4;
45
46
namespace mozilla {
47
48
LazyLogModule::operator LogModule*()
49
23.5M
{
50
23.5M
  // NB: The use of an atomic makes the reading and assignment of mLog
51
23.5M
  //     thread-safe. There is a small chance that mLog will be set more
52
23.5M
  //     than once, but that's okay as it will be set to the same LogModule
53
23.5M
  //     instance each time. Also note LogModule::Get is thread-safe.
54
23.5M
  LogModule* tmp = mLog;
55
23.5M
  if (MOZ_UNLIKELY(!tmp)) {
56
75
    tmp = LogModule::Get(mLogName);
57
75
    mLog = tmp;
58
75
  }
59
23.5M
60
23.5M
  mCanary.Check();
61
23.5M
62
23.5M
  return tmp;
63
23.5M
}
64
65
namespace detail {
66
67
void log_print(const LogModule* aModule,
68
               LogLevel aLevel,
69
               const char* aFmt, ...)
70
0
{
71
0
  va_list ap;
72
0
  va_start(ap, aFmt);
73
0
  aModule->Printv(aLevel, aFmt, ap);
74
0
  va_end(ap);
75
0
}
76
77
} // detail
78
79
LogLevel
80
ToLogLevel(int32_t aLevel)
81
0
{
82
0
  aLevel = std::min(aLevel, static_cast<int32_t>(LogLevel::Verbose));
83
0
  aLevel = std::max(aLevel, static_cast<int32_t>(LogLevel::Disabled));
84
0
  return static_cast<LogLevel>(aLevel);
85
0
}
86
87
const char*
88
0
ToLogStr(LogLevel aLevel) {
89
0
  switch (aLevel) {
90
0
    case LogLevel::Error:
91
0
      return "E";
92
0
    case LogLevel::Warning:
93
0
      return "W";
94
0
    case LogLevel::Info:
95
0
      return "I";
96
0
    case LogLevel::Debug:
97
0
      return "D";
98
0
    case LogLevel::Verbose:
99
0
      return "V";
100
0
    case LogLevel::Disabled:
101
0
    default:
102
0
      MOZ_CRASH("Invalid log level.");
103
0
      return "";
104
0
  }
105
0
}
106
107
namespace detail {
108
109
/**
110
 * A helper class providing reference counting for FILE*.
111
 * It encapsulates the following:
112
 *  - the FILE handle
113
 *  - the order number it was created for when rotating (actual path)
114
 *  - number of active references
115
 */
116
class LogFile
117
{
118
  FILE* mFile;
119
  uint32_t mFileNum;
120
121
public:
122
  LogFile(FILE* aFile, uint32_t aFileNum)
123
    : mFile(aFile)
124
    , mFileNum(aFileNum)
125
    , mNextToRelease(nullptr)
126
0
  {
127
0
  }
128
129
  ~LogFile()
130
0
  {
131
0
    fclose(mFile);
132
0
    delete mNextToRelease;
133
0
  }
134
135
0
  FILE* File() const { return mFile; }
136
0
  uint32_t Num() const { return mFileNum; }
137
138
  LogFile* mNextToRelease;
139
};
140
141
const char*
142
ExpandPIDMarker(const char* aFilename, char (&buffer)[2048])
143
0
{
144
0
  MOZ_ASSERT(aFilename);
145
0
  static const char kPIDToken[] = "%PID";
146
0
  const char* pidTokenPtr = strstr(aFilename, kPIDToken);
147
0
  if (pidTokenPtr &&
148
0
    SprintfLiteral(buffer, "%.*s%s%d%s",
149
0
                   static_cast<int>(pidTokenPtr - aFilename), aFilename,
150
0
                   XRE_IsParentProcess() ? "-main." : "-child.",
151
0
                   base::GetCurrentProcId(),
152
0
                   pidTokenPtr + strlen(kPIDToken)) > 0)
153
0
  {
154
0
    return buffer;
155
0
  }
156
0
157
0
  return aFilename;
158
0
}
159
160
} // detail
161
162
namespace {
163
  // Helper method that initializes an empty va_list to be empty.
164
  void empty_va(va_list *va, ...)
165
0
  {
166
0
    va_start(*va, va);
167
0
    va_end(*va);
168
0
  }
169
}
170
171
class LogModuleManager
172
{
173
public:
174
  LogModuleManager()
175
      // As for logging atomics, don't preserve behavior for this lock when
176
      // recording/replaying.
177
    : mModulesLock("logmodules", recordreplay::Behavior::DontPreserve)
178
    , mModules(kInitialModuleCount)
179
    , mPrintEntryCount(0)
180
    , mOutFile(nullptr)
181
    , mToReleaseFile(nullptr)
182
    , mOutFileNum(0)
183
    , mOutFilePath(strdup(""))
184
    , mMainThread(PR_GetCurrentThread())
185
    , mSetFromEnv(false)
186
    , mAddTimestamp(false)
187
    , mIsRaw(false)
188
    , mIsSync(false)
189
    , mRotate(0)
190
    , mInitialized(false)
191
3
  {
192
3
  }
193
194
  ~LogModuleManager()
195
0
  {
196
0
    detail::LogFile* logFile = mOutFile.exchange(nullptr);
197
0
    delete logFile;
198
0
  }
199
200
  /**
201
   * Loads config from command line args or env vars if present, in
202
   * this specific order of priority.
203
   *
204
   * Notes:
205
   *
206
   * 1) This function is only intended to be called once per session.
207
   * 2) None of the functions used in Init should rely on logging.
208
   */
209
  void Init(int argc, char* argv[])
210
3
  {
211
3
    MOZ_DIAGNOSTIC_ASSERT(!mInitialized);
212
3
    mInitialized = true;
213
3
214
3
    LoggingHandleCommandLineArgs(argc, static_cast<char const* const*>(argv),
215
3
                                 [](nsACString const& env) {
216
0
      // We deliberately set/rewrite the environment variables
217
0
      // so that when child processes are spawned w/o passing
218
0
      // the arguments they still inherit the logging settings
219
0
      // as well as sandboxing can be correctly set.
220
0
      // Scripts can pass -MOZ_LOG=$MOZ_LOG,modules as an argument
221
0
      // to merge existing settings, if required.
222
0
223
0
      // PR_SetEnv takes ownership of the string.
224
0
      PR_SetEnv(ToNewCString(env));
225
0
    });
226
3
227
3
    bool shouldAppend = false;
228
3
    bool addTimestamp = false;
229
3
    bool isSync = false;
230
3
    bool isRaw = false;
231
3
    int32_t rotate = 0;
232
3
    const char* modules = PR_GetEnv("MOZ_LOG");
233
3
    if (!modules || !modules[0]) {
234
3
      modules = PR_GetEnv("MOZ_LOG_MODULES");
235
3
      if (modules) {
236
0
        NS_WARNING("MOZ_LOG_MODULES is deprecated."
237
0
            "\nPlease use MOZ_LOG instead.");
238
0
      }
239
3
    }
240
3
    if (!modules || !modules[0]) {
241
3
      modules = PR_GetEnv("NSPR_LOG_MODULES");
242
3
      if (modules) {
243
0
        NS_WARNING("NSPR_LOG_MODULES is deprecated."
244
0
            "\nPlease use MOZ_LOG instead.");
245
0
      }
246
3
    }
247
3
248
3
    // Need to capture `this` since `sLogModuleManager` is not set until after
249
3
    // initialization is complete.
250
3
    NSPRLogModulesParser(modules,
251
3
        [this, &shouldAppend, &addTimestamp, &isSync, &isRaw, &rotate]
252
3
            (const char* aName, LogLevel aLevel, int32_t aValue) mutable {
253
0
          if (strcmp(aName, "append") == 0) {
254
0
            shouldAppend = true;
255
0
          } else if (strcmp(aName, "timestamp") == 0) {
256
0
            addTimestamp = true;
257
0
          } else if (strcmp(aName, "sync") == 0) {
258
0
            isSync = true;
259
0
          } else if (strcmp(aName, "raw") == 0) {
260
0
            isRaw = true;
261
0
          } else if (strcmp(aName, "rotate") == 0) {
262
0
            rotate = (aValue << 20) / kRotateFilesNumber;
263
0
          } else {
264
0
            this->CreateOrGetModule(aName)->SetLevel(aLevel);
265
0
          }
266
0
    });
267
3
268
3
    // Rotate implies timestamp to make the files readable
269
3
    mAddTimestamp = addTimestamp || rotate > 0;
270
3
    mIsSync = isSync;
271
3
    mIsRaw = isRaw;
272
3
    mRotate = rotate;
273
3
274
3
    if (rotate > 0 && shouldAppend) {
275
0
      NS_WARNING("MOZ_LOG: when you rotate the log, you cannot use append!");
276
0
    }
277
3
278
3
    const char* logFile = PR_GetEnv("MOZ_LOG_FILE");
279
3
    if (!logFile || !logFile[0]) {
280
3
      logFile = PR_GetEnv("NSPR_LOG_FILE");
281
3
    }
282
3
283
3
    if (logFile && logFile[0]) {
284
0
      char buf[2048];
285
0
      logFile = detail::ExpandPIDMarker(logFile, buf);
286
0
      mOutFilePath.reset(strdup(logFile));
287
0
288
0
      if (mRotate > 0) {
289
0
        // Delete all the previously captured files, including non-rotated
290
0
        // log files, so that users don't complain our logs eat space even
291
0
        // after the rotate option has been added and don't happen to send
292
0
        // us old large logs along with the rotated files.
293
0
        remove(mOutFilePath.get());
294
0
        for (uint32_t i = 0; i < kRotateFilesNumber; ++i) {
295
0
          RemoveFile(i);
296
0
        }
297
0
      }
298
0
299
0
      mOutFile = OpenFile(shouldAppend, mOutFileNum);
300
0
      mSetFromEnv = true;
301
0
    }
302
3
  }
303
304
  void SetLogFile(const char* aFilename)
305
0
  {
306
0
    // For now we don't allow you to change the file at runtime.
307
0
    if (mSetFromEnv) {
308
0
      NS_WARNING("LogModuleManager::SetLogFile - Log file was set from the "
309
0
                 "MOZ_LOG_FILE environment variable.");
310
0
      return;
311
0
    }
312
0
313
0
    const char * filename = aFilename ? aFilename : "";
314
0
    char buf[2048];
315
0
    filename = detail::ExpandPIDMarker(filename, buf);
316
0
317
0
    // Can't use rotate at runtime yet.
318
0
    MOZ_ASSERT(mRotate == 0, "We don't allow rotate for runtime logfile changes");
319
0
    mOutFilePath.reset(strdup(filename));
320
0
321
0
    // Exchange mOutFile and set it to be released once all the writes are done.
322
0
    detail::LogFile* newFile = OpenFile(false, 0);
323
0
    detail::LogFile* oldFile = mOutFile.exchange(newFile);
324
0
325
0
    // Since we don't allow changing the logfile if MOZ_LOG_FILE is already set,
326
0
    // and we don't allow log rotation when setting it at runtime, mToReleaseFile
327
0
    // will be null, so we're not leaking.
328
0
    DebugOnly<detail::LogFile*> prevFile = mToReleaseFile.exchange(oldFile);
329
0
    MOZ_ASSERT(!prevFile, "Should be null because rotation is not allowed");
330
0
331
0
    // If we just need to release a file, we must force print, in order to
332
0
    // trigger the closing and release of mToReleaseFile.
333
0
    if (oldFile) {
334
0
      va_list va;
335
0
      empty_va(&va);
336
0
      Print("Logger", LogLevel::Info, "Flushing old log files\n", va);
337
0
    }
338
0
  }
339
340
  uint32_t GetLogFile(char *aBuffer, size_t aLength)
341
0
  {
342
0
    uint32_t len = strlen(mOutFilePath.get());
343
0
    if (len + 1 > aLength) {
344
0
      return 0;
345
0
    }
346
0
    snprintf(aBuffer, aLength, "%s", mOutFilePath.get());
347
0
    return len;
348
0
  }
349
350
  void SetIsSync(bool aIsSync)
351
0
  {
352
0
    mIsSync = aIsSync;
353
0
  }
354
355
  void SetAddTimestamp(bool aAddTimestamp)
356
0
  {
357
0
    mAddTimestamp = aAddTimestamp;
358
0
  }
359
360
  detail::LogFile* OpenFile(bool aShouldAppend, uint32_t aFileNum)
361
0
  {
362
0
    FILE* file;
363
0
364
0
    if (mRotate > 0) {
365
0
      char buf[2048];
366
0
      SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum);
367
0
368
0
      // rotate doesn't support append.
369
0
      file = fopen(buf, "w");
370
0
    } else {
371
0
      file = fopen(mOutFilePath.get(), aShouldAppend ? "a" : "w");
372
0
    }
373
0
374
0
    if (!file) {
375
0
      return nullptr;
376
0
    }
377
0
378
0
    return new detail::LogFile(file, aFileNum);
379
0
  }
380
381
  void RemoveFile(uint32_t aFileNum)
382
0
  {
383
0
    char buf[2048];
384
0
    SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum);
385
0
    remove(buf);
386
0
  }
387
388
  LogModule* CreateOrGetModule(const char* aName)
389
75
  {
390
75
    OffTheBooksMutexAutoLock guard(mModulesLock);
391
75
    LogModule* module = nullptr;
392
75
    if (!mModules.Get(aName, &module)) {
393
75
      module = new LogModule(aName, LogLevel::Disabled);
394
75
      mModules.Put(aName, module);
395
75
    }
396
75
397
75
    return module;
398
75
  }
399
400
  void Print(const char* aName, LogLevel aLevel, const char* aFmt, va_list aArgs)
401
    MOZ_FORMAT_PRINTF(4, 0)
402
0
  {
403
0
    // We don't do nuwa-style forking anymore, so our pid can't change.
404
0
    static long pid = static_cast<long>(base::GetCurrentProcId());
405
0
    const size_t kBuffSize = 1024;
406
0
    char buff[kBuffSize];
407
0
408
0
    char* buffToWrite = buff;
409
0
    SmprintfPointer allocatedBuff;
410
0
411
0
    va_list argsCopy;
412
0
    va_copy(argsCopy, aArgs);
413
0
    int charsWritten = VsprintfLiteral(buff, aFmt, argsCopy);
414
0
    va_end(argsCopy);
415
0
416
0
    if (charsWritten < 0) {
417
0
      // Print out at least something.  We must copy to the local buff,
418
0
      // can't just assign aFmt to buffToWrite, since when
419
0
      // buffToWrite != buff, we try to release it.
420
0
      MOZ_ASSERT(false, "Probably incorrect format string in LOG?");
421
0
      strncpy(buff, aFmt, kBuffSize - 1);
422
0
      buff[kBuffSize - 1] = '\0';
423
0
      charsWritten = strlen(buff);
424
0
    } else if (static_cast<size_t>(charsWritten) >= kBuffSize - 1) {
425
0
      // We may have maxed out, allocate a buffer instead.
426
0
      allocatedBuff = mozilla::Vsmprintf(aFmt, aArgs);
427
0
      buffToWrite = allocatedBuff.get();
428
0
      charsWritten = strlen(buffToWrite);
429
0
    }
430
0
431
0
    // Determine if a newline needs to be appended to the message.
432
0
    const char* newline = "";
433
0
    if (charsWritten == 0 || buffToWrite[charsWritten - 1] != '\n') {
434
0
      newline = "\n";
435
0
    }
436
0
437
0
    FILE* out = stderr;
438
0
439
0
    // In case we use rotate, this ensures the FILE is kept alive during
440
0
    // its use.  Increased before we load mOutFile.
441
0
    ++mPrintEntryCount;
442
0
443
0
    detail::LogFile* outFile = mOutFile;
444
0
    if (outFile) {
445
0
      out = outFile->File();
446
0
    }
447
0
448
0
    // This differs from the NSPR format in that we do not output the
449
0
    // opaque system specific thread pointer (ie pthread_t) cast
450
0
    // to a long. The address of the current PR_Thread continues to be
451
0
    // prefixed.
452
0
    //
453
0
    // Additionally we prefix the output with the abbreviated log level
454
0
    // and the module name.
455
0
    PRThread *currentThread = PR_GetCurrentThread();
456
0
    const char *currentThreadName = (mMainThread == currentThread)
457
0
      ? "Main Thread"
458
0
      : PR_GetThreadName(currentThread);
459
0
460
0
    char noNameThread[40];
461
0
    if (!currentThreadName) {
462
0
      SprintfLiteral(noNameThread, "Unnamed thread %p", currentThread);
463
0
      currentThreadName = noNameThread;
464
0
    }
465
0
466
0
    if (!mAddTimestamp) {
467
0
      if (!mIsRaw) {
468
0
        fprintf_stderr(out,
469
0
                      "[%s %ld: %s]: %s/%s %s%s",
470
0
                      nsDebugImpl::GetMultiprocessMode(), pid, currentThreadName, ToLogStr(aLevel),
471
0
                      aName, buffToWrite, newline);
472
0
      } else {
473
0
        fprintf_stderr(out, "%s%s", buffToWrite, newline);
474
0
      }
475
0
    } else {
476
0
      PRExplodedTime now;
477
0
      PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now);
478
0
      fprintf_stderr(
479
0
          out,
480
0
          "%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - [%s %ld: %s]: %s/%s %s%s",
481
0
          now.tm_year, now.tm_month + 1, now.tm_mday,
482
0
          now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec,
483
0
          nsDebugImpl::GetMultiprocessMode(), pid, currentThreadName, ToLogStr(aLevel),
484
0
          aName, buffToWrite, newline);
485
0
    }
486
0
487
0
    if (mIsSync) {
488
0
      fflush(out);
489
0
    }
490
0
491
0
    if (mRotate > 0 && outFile) {
492
0
      int32_t fileSize = ftell(out);
493
0
      if (fileSize > mRotate) {
494
0
        uint32_t fileNum = outFile->Num();
495
0
496
0
        uint32_t nextFileNum = fileNum + 1;
497
0
        if (nextFileNum >= kRotateFilesNumber) {
498
0
          nextFileNum = 0;
499
0
        }
500
0
501
0
        // And here is the trick.  The current out-file remembers its order
502
0
        // number.  When no other thread shifted the global file number yet,
503
0
        // we are the thread to open the next file.
504
0
        if (mOutFileNum.compareExchange(fileNum, nextFileNum)) {
505
0
          // We can work with mToReleaseFile because we are sure the
506
0
          // mPrintEntryCount can't drop to zero now - the condition
507
0
          // to actually delete what's stored in that member.
508
0
          // And also, no other thread can enter this piece of code
509
0
          // because mOutFile is still holding the current file with
510
0
          // the non-shifted number.  The compareExchange() above is
511
0
          // a no-op for other threads.
512
0
          outFile->mNextToRelease = mToReleaseFile;
513
0
          mToReleaseFile = outFile;
514
0
515
0
          mOutFile = OpenFile(false, nextFileNum);
516
0
        }
517
0
      }
518
0
    }
519
0
520
0
    if (--mPrintEntryCount == 0 && mToReleaseFile) {
521
0
      // We were the last Print() entered, if there is a file to release
522
0
      // do it now.  exchange() is atomic and makes sure we release the file
523
0
      // only once on one thread.
524
0
      detail::LogFile* release = mToReleaseFile.exchange(nullptr);
525
0
      delete release;
526
0
    }
527
0
  }
528
529
private:
530
  OffTheBooksMutex mModulesLock;
531
  nsClassHashtable<nsCharPtrHashKey, LogModule> mModules;
532
533
  // Print() entry counter, actually reflects concurrent use of the current
534
  // output file.  ReleaseAcquire ensures that manipulation with mOutFile
535
  // and mToReleaseFile is synchronized by manipulation with this value.
536
  Atomic<uint32_t, ReleaseAcquire> mPrintEntryCount;
537
  // File to write to.  ReleaseAcquire because we need to sync mToReleaseFile
538
  // with this.
539
  Atomic<detail::LogFile*, ReleaseAcquire> mOutFile;
540
  // File to be released when reference counter drops to zero.  This member
541
  // is assigned mOutFile when the current file has reached the limit.
542
  // It can be Relaxed, since it's synchronized with mPrintEntryCount
543
  // manipulation and we do atomic exchange() on it.
544
  Atomic<detail::LogFile*, Relaxed> mToReleaseFile;
545
  // The next file number.  This is mostly only for synchronization sake.
546
  // Can have relaxed ordering, since we only do compareExchange on it which
547
  // is atomic regardless ordering.
548
  Atomic<uint32_t, Relaxed> mOutFileNum;
549
  // Just keeps the actual file path for further use.
550
  UniqueFreePtr<char[]> mOutFilePath;
551
552
  PRThread *mMainThread;
553
  bool mSetFromEnv;
554
  Atomic<bool, Relaxed> mAddTimestamp;
555
  Atomic<bool, Relaxed> mIsRaw;
556
  Atomic<bool, Relaxed> mIsSync;
557
  int32_t mRotate;
558
  bool mInitialized;
559
};
560
561
StaticAutoPtr<LogModuleManager> sLogModuleManager;
562
563
LogModule*
564
LogModule::Get(const char* aName)
565
75
{
566
75
  // This is just a pass through to the LogModuleManager so
567
75
  // that the LogModuleManager implementation can be kept internal.
568
75
  MOZ_ASSERT(sLogModuleManager != nullptr);
569
75
  return sLogModuleManager->CreateOrGetModule(aName);
570
75
}
571
572
void
573
LogModule::SetLogFile(const char* aFilename)
574
0
{
575
0
  MOZ_ASSERT(sLogModuleManager);
576
0
  sLogModuleManager->SetLogFile(aFilename);
577
0
}
578
579
uint32_t
580
LogModule::GetLogFile(char *aBuffer, size_t aLength)
581
0
{
582
0
  MOZ_ASSERT(sLogModuleManager);
583
0
  return sLogModuleManager->GetLogFile(aBuffer, aLength);
584
0
}
585
586
void
587
LogModule::SetAddTimestamp(bool aAddTimestamp)
588
0
{
589
0
  sLogModuleManager->SetAddTimestamp(aAddTimestamp);
590
0
}
591
592
void
593
LogModule::SetIsSync(bool aIsSync)
594
0
{
595
0
  sLogModuleManager->SetIsSync(aIsSync);
596
0
}
597
598
void
599
LogModule::Init(int argc, char* argv[])
600
6
{
601
6
  // NB: This method is not threadsafe; it is expected to be called very early
602
6
  //     in startup prior to any other threads being run.
603
6
  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
604
6
605
6
  if (sLogModuleManager) {
606
3
    // Already initialized.
607
3
    return;
608
3
  }
609
3
610
3
  // NB: We intentionally do not register for ClearOnShutdown as that happens
611
3
  //     before all logging is complete. And, yes, that means we leak, but
612
3
  //     we're doing that intentionally.
613
3
614
3
  // Don't assign the pointer until after Init is called. This should help us
615
3
  // detect if any of the functions called by Init somehow rely on logging.
616
3
  auto mgr = new LogModuleManager();
617
3
  mgr->Init(argc, argv);
618
3
  sLogModuleManager = mgr;
619
3
}
620
621
void
622
LogModule::Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const
623
0
{
624
0
  MOZ_ASSERT(sLogModuleManager != nullptr);
625
0
626
0
  // Forward to LogModule manager w/ level and name
627
0
  sLogModuleManager->Print(Name(), aLevel, aFmt, aArgs);
628
0
}
629
630
} // namespace mozilla