diff --git a/mozilla/xpcom/base/nsILoggingService.idl b/mozilla/xpcom/base/nsILoggingService.idl new file mode 100644 index 00000000000..3785f4ceb3f --- /dev/null +++ b/mozilla/xpcom/base/nsILoggingService.idl @@ -0,0 +1,440 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + */ + +/** + * LOGGING SERVICE + * + * We all know and love PR_LOG, but let's face it, it has some deficiencies: + * - can't direct output for different logs to different places, + * - isn't scriptable, + * - can't control printing format, including indentation, + * - no facilities for interval timing. + * We've solved these problems with NS_LOG, the new modern equivalent. Here's + * how you use it: + * + * // First declare a new log: + * NS_DECL_LOG(Foo); + * + * void main() { + * nsresult rv; + * + * // Initialize the log somewhere in your startup code: + * NS_INIT_LOG(Foo, &rv); + * + * // Then use it like this: + * NS_LOG(Foo, OUT, ("hello world")); + * } + * + * The above log statement will print something like this: + * + * a33e50 Foo hello world + * + * The hex number at the beginning of the line indicates the ID of the thread + * executing the NS_LOG statement. The name of the log appears next. And if + * the constant ERROR, WARN, or DBG had appeared instead of OUT in the NS_LOG + * statement, the character 'E', 'W' or 'D' would have appeared before the + * message to indicate the type of log statement. The level displayed can + * be controlled by calling SetLevel on the log. + * + * // You can also cause it to indent its output to help you log recursive execution: + * int fact(int n) { + * NS_LOG_WITH_INDENT(Foo, "fact"); + * NS_LOG(Main, ERROR, ("calling fact of %d\n", n)); + * if (n == 0) return 1; + * int result = n * fact(n - 1); + * NS_LOG(Foo, DBG, ("fact of %d is %d\n", n, result)); + * return result; + * } + * + * Calling fact(3) will produce a log that looks like this: + * + * a33e50 Foo D | calling fact of 3 + * a33e50 Foo D | | calling fact of 2 + * a33e50 Foo D | | | calling fact of 1 + * a33e50 Foo D | | | | calling fact of 0 + * a33e50 Foo D | | | fact of 1 is 1 + * a33e50 Foo D | | fact of 2 is 2 + * a33e50 Foo D | fact of 3 is 6 + * + * You can compute elapsed time for a series of runs, and then ask the log for + * statistical information on them: + * + * void TestTiming() { + * for (int i = 0; i < 100; i++) { + * NS_LOG_BEGIN_TIMING(Foo); + * CallTestToTime(); + * PRIntervalTime elapsed; + * NS_LOG_END_TIMING(Foo, &elapsed); + * NS_LOG(Foo, OUT, ("time for this run: %f\n", elapsed)); + * } + * #ifdef NS_ENABLE_LOGGING + * PRUint32 samples; + * double mean, stdDev; + * Foo->GetTimingStats(&samples, &mean, &stdDev); + * printf("samples: %d, mean: %f, standard deviation: %f\n", + * samples, mean, stdDev); + * #endif + * } + * + * You can control where the output of the log goes by setting its + * log event sink to an appropriate nsILogEventSink. By default the + * standard log event sink does the following: + * - outputs to stderr + * - outputs to the platform's debug output for errors + * New log event sinks can be instantiated via the component manager: + * + * nsComponentManager::CreateInstance(kStandardLogEventSinkCID, + * nsnull, NS_GET_IID(nsIStandardLogEventSink), + * &logEventSink); + * logEventSink->Init(stdout, // stdout instead of stderr + * nsILog::LEVEL_WARN); // level to log to debug output + * Foo->SetLogEventSink(logEventSink); + * + * or the application can implement its own nsILogEventSink. + */ + +//////////////////////////////////////////////////////////////////////////////// + +#include "nsISupports.idl" + +%{C++ + +#include "prlog.h" // include prlog.h because we're going to override it's ancient macros +#include "prprf.h" +#include "prinrval.h" +#include "nsDebug.h" +#include "nsIServiceManager.h" +#include + +#if (defined(DEBUG) || defined(NS_DEBUG) || defined(FORCE_PR_LOG)) && !defined(WIN16) +// enable logging by default +#define NS_ENABLE_LOGGING +#endif + +#ifdef NS_DISABLE_LOGGING +// override, if you want DEBUG, but *not* logging (for some reason) +#undef NS_ENABLE_LOGGING +#endif + +#ifdef NS_ENABLE_LOGGING + +class nsLogEvent; + +%} + +[ref] native nsLogEvent(nsLogEvent); + +typedef unsigned long PRIntervalTime; + +interface nsILog; + +//////////////////////////////////////////////////////////////////////////////// + +[scriptable, uuid(28efb190-b114-11d3-93b6-00104ba0fd40)] +interface nsILogEventSink : nsISupports +{ + [noscript] void printEvent(in nsLogEvent event); + void flush(); + readonly attribute string destinationName; +}; + +[ptr] native FILE(FILE); + +interface nsIStandardLogEventSink : nsILogEventSink +{ + /** + * @param filePath - a native path to a log file, + * or if "1", stdout, + * or if "2", stderr. + */ + void init(in string filePath, + in unsigned long levelForDebugOutput); + [noscript] void initFromFILE(in string name, + in FILE filePtr, + in unsigned long levelForDebugOutput); +}; + +%{C++ +#define NS_STANDARDLOGEVENTSINK_CID \ +{ /* 0ebdebe0-b14a-11d3-93b6-00104ba0fd40 */ \ + 0x0ebdebe0, \ + 0xb14a, \ + 0x11d3, \ + {0x93, 0xb6, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40} \ +} + +#define NS_STANDARDLOGEVENTSINK_PROGID "component://netscape/standard-log-event-sink" +#define NS_STANDARDLOGEVENTSINK_CLASSNAME "Standard Log Event Sink" +%} + +//////////////////////////////////////////////////////////////////////////////// + +[scriptable, uuid(399d2370-b114-11d3-93b6-00104ba0fd40)] +interface nsILoggingService : nsISupports +{ + nsILog getLog(in string name); + attribute unsigned long defaultControlFlags; + attribute nsILogEventSink defaultLogEventSink; + void describeLogs(in nsILog output); + void describeTimings(in nsILog output); +}; + +%{C++ +#define NS_LOGGINGSERVICE_CID \ +{ /* 4f290320-b11c-11d3-93b6-00104ba0fd40 */ \ + 0x4f290320, \ + 0xb11c, \ + 0x11d3, \ + {0x93, 0xb6, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40} \ +} + +#define NS_LOGGINGSERVICE_PROGID "component://netscape/logging-service" +#define NS_LOGGINGSERVICE_CLASSNAME "Logging Service" +%} + +//////////////////////////////////////////////////////////////////////////////// + +[scriptable, uuid(3cddf0a0-b114-11d3-93b6-00104ba0fd40)] +interface nsILog : nsISupports +{ + readonly attribute string name; + + const unsigned long LEVEL_NEVER = 0; + const unsigned long LEVEL_ERROR = 1; + const unsigned long LEVEL_WARN = 2; + const unsigned long LEVEL_STDOUT = 3; + const unsigned long LEVEL_DBG = 4; + + attribute unsigned long level; + + // Backward compatibility with PR_LOG -- don't use explicitly! + const unsigned long LEVEL_PR_LOG_NONE = LEVEL_NEVER; + const unsigned long LEVEL_PR_LOG_ERROR = LEVEL_ERROR; + const unsigned long LEVEL_PR_LOG_WARNING = LEVEL_WARN; + const unsigned long LEVEL_PR_LOG_ALWAYS = LEVEL_STDOUT; + const unsigned long LEVEL_PR_LOG_DEBUG = LEVEL_DBG; + + //////////////////////////////////////////////////////////////////////////// + // Printing Routines + + boolean enabled(in unsigned long level); + void print(in unsigned long level, in wstring message); + void flush(); + + [noscript] void printEvent(in nsLogEvent event); + + void increaseIndent(); + void decreaseIndent(); + readonly attribute unsigned long indentLevel; + + void describe(in nsILog outLog); + + //////////////////////////////////////////////////////////////////////////// + // Timing Routines + + void beginTiming(); + PRIntervalTime endTiming(); + void getTimingStats(out unsigned long sampleSize, + out double meanTime, + out double stdDevTime); + void describeTiming(in nsILog outLog, in string msg); + void resetTiming(); + + //////////////////////////////////////////////////////////////////////////// + // Control Routines + + const unsigned long PRINT_THREAD_ID = 1 << 0; + const unsigned long PRINT_LOG_NAME = 1 << 1; + const unsigned long PRINT_LEVEL = 1 << 2; + const unsigned long TIMING_PER_THREAD = 1 << 3; + + attribute unsigned long controlFlags; + + attribute nsILogEventSink logEventSink; + + //////////////////////////////////////////////////////////////////////////// + + // What's this? Why is this sort of thing in an interface definition? + // The reason is that testing whether a log is enabled from C++ + // programs must be efficient so as not to impact the execution + // of time-critical operations, yet still allow for logging them + // in order to detect problems. (We're basically forcing every + // implementation to implement this part.) +%{C++ + public: + PRBool Test(PRUint32 level) { return mEnabledLevel >= level; } + protected: + PRUint32 mEnabledLevel; +%} +}; + +//////////////////////////////////////////////////////////////////////////////// + +%{C++ + +/** + * nsLogEvent: This little closure class is used to capture the log + * level so that the NS_LOG macro is more manageable. + */ +class NS_COM nsLogEvent +{ +public: + nsLogEvent(nsILog* log, PRUint32 level) + : mLog(log), mLevel(level), mMessage(nsnull) { + } + ~nsLogEvent() { + if (mMessage) PR_smprintf_free(mMessage); + } + + nsresult Printf(const char* format, ...); + nsresult Vprintf(const char* format, va_list args); + + nsILog* GetLog() { return mLog; } + PRUint32 GetLevel() { return mLevel; } + const char* GetMsg() { return mMessage; } + +protected: + nsILog* mLog; + PRUint32 mLevel; + char* mMessage; +}; + +/** + * nsLogIndent: This class allows indentation to occur in a block scope. The + * automatic destructor takes care of resetting the indentation. Use the + * NS_LOG_WITH_INDENT macro. + */ +class NS_COM nsLogIndent +{ +public: + nsLogIndent(nsILog* log, const char* msg) : mLog(log), mMsg(msg) { + nsLogEvent(mLog, nsILog::LEVEL_STDOUT).Printf("[ Begin %s", mMsg); + mLog->IncreaseIndent(); + } + ~nsLogIndent() { + mLog->DecreaseIndent(); + nsLogEvent(mLog, nsILog::LEVEL_STDOUT).Printf("] End %s", mMsg); + } +protected: + nsILog* mLog; + const char* mMsg; +}; + +/** + * nsLogTiming: This class allows timing to occur in a block scope. The + * automatic destructor takes care of stopping the timing. Use the + * NS_LOG_WITH_TIMING macro. + */ +class NS_COM nsLogTiming +{ +public: + nsLogTiming(nsILog* log) : mLog(log) { + mLog->BeginTiming(); + } + ~nsLogTiming() { + PRIntervalTime elapsed; + mLog->EndTiming(&elapsed); + } +protected: + nsILog* mLog; +}; + +//////////////////////////////////////////////////////////////////////////////// + +#define NS_DECL_LOG(_log) \ + nsILog* _log + +#define NS_INIT_LOG(_log) \ + PR_BEGIN_MACRO \ + if (_log == nsnull) { \ + nsresult _rv; \ + static NS_DEFINE_CID(kLoggingServiceCID, NS_LOGGINGSERVICE_CID); \ + NS_WITH_SERVICE(nsILoggingService, _serv, kLoggingServiceCID, &_rv); \ + if (NS_SUCCEEDED(_rv)) { \ + _rv = _serv->GetLog(#_log, &_log); \ + PR_ASSERT(NS_SUCCEEDED(_rv)); \ + } \ + } \ + PR_END_MACRO + +#define NS_LOG_TEST(_log, _level) \ + ((_log)->Test(nsILog::LEVEL_##_level)) + +#define NS_LOG(_log, _level, _printfArgs) \ + PR_BEGIN_MACRO \ + if (NS_LOG_TEST(_log, _level)) { \ + nsLogEvent(_log, nsILog::LEVEL_##_level) \ + .Printf _printfArgs; \ + } \ + PR_END_MACRO + +#define NS_DEFINE_LOG(_log, _level) \ + (!NS_LOG_TEST(_log, _level)) \ + ? NS_OK \ + : nsLogEvent(_log, nsILog::LEVEL_##_level).Printf + +#define NS_LOG_FLUSH(_log) ((_log)->Flush()) + +#define NS_LOG_WITH_INDENT(_log, _msg) nsLogIndent _indent_##_log(_log, _msg) +#define NS_LOG_BEGIN_INDENT(_log) ((_log)->IncreaseIndent()) +#define NS_LOG_END_INDENT(_log) ((_log)->DecreaseIndent()) + +#define NS_LOG_WITH_TIMING(_log) nsLogTiming _timing_##_log(_log) +#define NS_LOG_BEGIN_TIMING(_log) ((_log)->BeginTiming()) +#define NS_LOG_END_TIMING(_log, _elapsed) ((_log)->EndTiming(_elapsed)) +#define NS_LOG_DESCRIBE_TIMING(_log,_msg) ((_log)->DescribeTiming(_msg)) +#define NS_LOG_RESET_TIMING(_log) ((_log)->ResetTiming()) + +#else // !NS_ENABLE_LOGGING + +#define NS_DECL_LOG(_log) void _not_used() // something that can be used with extern +#define NS_INIT_LOG(_log) ((void)0) +#define NS_LOG_TEST(_module, _level) 0 +#define NS_LOG(_module, _level, _args) ((void)0) +#define NS_DEFINE_LOG(_log, _level) NS_OK +#define NS_LOG_FLUSH(_log) NS_OK +#define NS_LOG_WITH_INDENT(_log, _msg) ((void)0) +#define NS_LOG_BEGIN_INDENT(_log) ((void)0) +#define NS_LOG_END_INDENT(_log) ((void)0) +#define NS_LOG_WITH_TIMING(_log) ((void)0) +#define NS_LOG_BEGIN_TIMING(_log) ((void)0) +#define NS_LOG_END_TIMING(_log, _elapsed) (*(_elapsed) = 0) +#define NS_LOG_DESCRIBE_TIMING(_log,_msg) NS_OK +#define NS_LOG_RESET_TIMING(_log) NS_OK + +#endif // !NS_ENABLE_LOGGING + +// Redefine NSPR's logging system: +#undef PR_LOG_TEST +#define PR_LOG_TEST NS_LOG_TEST +#undef PR_LOG +#define PR_LOG NS_LOG +#define PRLogModuleInfo #error use_NS_DECL_LOG_instead +#define PR_NewLogModule #error use_NS_INIT_LOG_instead +#undef PR_ASSERT +#define PR_ASSERT(x) NS_ASSERTION(x, #x) +#define printf use_NS_DECL_LOG_instead +#define fprintf use_NS_DECL_LOG_instead + +%} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/mozilla/xpcom/base/nsLogging.cpp b/mozilla/xpcom/base/nsLogging.cpp new file mode 100644 index 00000000000..ad4f9980724 --- /dev/null +++ b/mozilla/xpcom/base/nsLogging.cpp @@ -0,0 +1,818 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + */ + +#include "nsLogging.h" + +#ifdef NS_ENABLE_LOGGING + +#include "nsCRT.h" +#include "prthread.h" +#include "nsAutoLock.h" +#include "nsISupportsArray.h" +#include "nsIServiceManager.h" +#include "nsSpecialSystemDirectory.h" +#include +#ifdef XP_PC +#include +#endif + +#undef printf +#undef fprintf + +static PRMonitor* gLogMonitor = nsnull; +static nsObjectHashtable* gSettings = nsnull; + +NS_DECL_LOG(LogInfo); + +NS_DEFINE_CID(kLoggingServiceCID, NS_LOGGINGSERVICE_CID); + +//////////////////////////////////////////////////////////////////////////////// +// nsLoggingService + +nsLoggingService::nsLoggingService() + : mDefaultControlFlags(nsILog::PRINT_THREAD_ID | + nsILog::PRINT_LOG_NAME | + nsILog::PRINT_LEVEL | + nsILog::TIMING_PER_THREAD), + mLogs(16) +{ + NS_INIT_REFCNT(); +} + +nsLoggingService::~nsLoggingService() +{ + NS_INIT_LOG(LogInfo); + DescribeLogs(LogInfo); + DescribeTimings(LogInfo); +} + +NS_IMPL_ISUPPORTS1(nsLoggingService, nsILoggingService) + +static void* +levelClone(nsHashKey *aKey, void *aData, void* closure) +{ + PRUint32 level = (PRUint32)aData; + return (void*)level; +} + +static PRBool +levelDestroy(nsHashKey *aKey, void *aData, void* closure) +{ + return PR_TRUE; +} + +static void +RecordSetting(const char* name, const char* value) +{ + PRUint32 level = nsILog::LEVEL_ERROR; + if (nsCRT::strcasecmp(value, "ERROR") == 0 || + nsCRT::strcasecmp(value, "2") == 0) { + level = nsILog::LEVEL_ERROR; + fprintf(stderr, "### NS_LOG: %s = ERROR\n", name); + } + else if (nsCRT::strcasecmp(value, "WARN") == 0 || + nsCRT::strcasecmp(value, "WARNING") == 0 || + nsCRT::strcasecmp(value, "3") == 0) { + level = nsILog::LEVEL_WARN; + fprintf(stderr, "### NS_LOG: %s = WARN\n", name); + } + else if (nsCRT::strcasecmp(value, "STDOUT") == 0 || + nsCRT::strcasecmp(value, "OUT") == 0 || + nsCRT::strcasecmp(value, "4") == 0) { + level = nsILog::LEVEL_STDOUT; + fprintf(stderr, "### NS_LOG: %s = STDOUT\n", name); + } + else if (nsCRT::strcasecmp(value, "DBG") == 0 || + nsCRT::strcasecmp(value, "DEBUG") == 0 || + nsCRT::strcasecmp(value, "5") == 0) { + level = nsILog::LEVEL_DBG; + fprintf(stderr, "### NS_LOG: %s = DBG\n", name); + } + else { + fprintf(stderr, "### NS_LOG error: %s = %s (bad level)\n", name, value); + } + + nsStringKey key(name); + gSettings->Put(&key, (void*)level); +} + +nsresult +nsLoggingService::Init() +{ + nsresult rv; + nsStandardLogEventSink* defaultSink = nsnull; + const char* outputPath = nsnull; + + if (gLogMonitor == nsnull) { + gLogMonitor = PR_NewMonitor(); + if (gLogMonitor == nsnull) + return NS_ERROR_OUT_OF_MEMORY; + } + + if (gSettings == nsnull) { + gSettings = new nsObjectHashtable(levelClone, nsnull, + levelDestroy, nsnull, + 16); + if (gSettings == nsnull) { + rv = NS_ERROR_OUT_OF_MEMORY; + goto error; + } + } + + // try the nspr log environment variables first: + { + const char* nspr_log_modules = getenv("NSPR_LOG_MODULES"); + if (nspr_log_modules) { + fprintf(stderr, "### NS_LOG: using NSPR_LOG_MODULES (instead of .nslog)\n"); + char* head = nsCRT::strdup(nspr_log_modules); + char* rest = nsCRT::strdup(nspr_log_modules); + while (1) { + char* name = nsCRT::strtok(rest, ":", &rest); + if (name == nsnull) break; + char* value = nsCRT::strtok(rest, ";", &rest); + if (value == nsnull) break; + RecordSetting(name, value); + } + nsCRT::free(head); + } + const char* nspr_log_file = getenv("NSPR_LOG_FILE"); + if (nspr_log_file) { + fprintf(stderr, "### NS_LOG: using NSPR_LOG_FILE (instead of .nslog) -- logging to %s\n", + nspr_log_file); + outputPath = nspr_log_file; + } + } + // then load up log description file: + { + nsSpecialSystemDirectory file(nsSpecialSystemDirectory::OS_CurrentProcessDirectory); + file += ".nslog"; + const char* path = file.GetNativePathCString(); + FILE* f = ::fopen(path, "r"); + if (f != nsnull) { + PRInt32 cnt; + while (PR_TRUE) { + char name[64]; + char value[64]; +// cnt = ::fscanf(f, "%64s=%64s\n", name, value); + cnt = ::fscanf(f, "%64s\n", name); + if (cnt <= 0) break; + cnt = ::fscanf(f, "%64s\n", value); + if (cnt <= 0) break; + RecordSetting(name, value); + } + ::fclose(f); + } + } + + defaultSink = new nsStandardLogEventSink(); + if (defaultSink == nsnull) { + rv = NS_ERROR_OUT_OF_MEMORY; + goto error; + } + + NS_ADDREF(defaultSink); + if (outputPath) + rv = defaultSink->Init(outputPath, nsILog::LEVEL_ERROR); + else + rv = defaultSink->InitFromFILE("stderr", stderr, nsILog::LEVEL_ERROR); + if (NS_FAILED(rv)) goto error; + + mDefaultSink = defaultSink; + NS_RELEASE(defaultSink); + return NS_OK; + + error: + NS_IF_RELEASE(defaultSink); + if (gLogMonitor) { + PR_DestroyMonitor(gLogMonitor); + gLogMonitor = nsnull; + } + return rv; +} + +NS_METHOD +nsLoggingService::Create(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr) +{ + nsresult rv; + if (outer) + return NS_ERROR_NO_AGGREGATION; + + nsLoggingService* it = new nsLoggingService(); + if (it == NULL) + return NS_ERROR_OUT_OF_MEMORY; + + rv = it->Init(); + if (NS_FAILED(rv)) { + delete it; + return rv; + } + + rv = it->QueryInterface(aIID, aInstancePtr); + if (NS_FAILED(rv)) { + delete it; + return rv; + } + return rv; +} + +NS_IMETHODIMP +nsLoggingService::GetLog(const char* name, nsILog* *result) +{ + nsAutoMonitor monitor(gLogMonitor); + + nsStringKey key(name); + nsILog* log = (nsILog*)mLogs.Get(&key); + if (log) { + *result = log; + return NS_OK; + } + + nsLog* newLog = new nsLog(); + if (newLog == nsnull) + return NS_ERROR_OUT_OF_MEMORY; + nsresult rv = newLog->Init(name, mDefaultControlFlags, mDefaultSink); + if (NS_FAILED(rv)) { + delete newLog; + return rv; + } + mLogs.Put(&key, newLog); + *result = newLog; + NS_ADDREF(newLog); + return NS_OK; +} + +static PRBool +DescribeLog(nsHashKey *aKey, void *aData, void* closure) +{ + nsILog* log = (nsILog*)aData; + nsILog* out = (nsILog*)closure; + (void)log->Describe(out); + return PR_TRUE; +} + +NS_IMETHODIMP +nsLoggingService::DescribeLogs(nsILog* out) +{ + NS_LOG(out, STDOUT, ("%-20.20s %-8.8s %s\n", "LOG NAME", "ENABLED", "DESTINATION")); + mLogs.Enumerate(DescribeLog, out); + return NS_OK; +} + +static PRBool +DescribeTiming(nsHashKey *aKey, void *aData, void* closure) +{ + nsILog* log = (nsILog*)aData; + nsILog* out = (nsILog*)closure; + nsresult rv; + PRUint32 sampleSize; + double meanTime; + double stdDevTime; + rv = log->GetTimingStats(&sampleSize, &meanTime, &stdDevTime); + if (NS_SUCCEEDED(rv) && sampleSize > 0) + (void)log->DescribeTiming(out, "TOTAL TIME"); + return PR_TRUE; +} + +NS_IMETHODIMP +nsLoggingService::DescribeTimings(nsILog* out) +{ + NS_LOG(out, STDOUT, (" %-20.20s %-8.8s %s\n", "LOG NAME", "ENABLED", "DESTINATION")); + mLogs.Enumerate(DescribeTiming, out); + return NS_OK; +} + +NS_IMETHODIMP +nsLoggingService::GetDefaultControlFlags(PRUint32 *controlFlags) +{ + *controlFlags = mDefaultControlFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsLoggingService::SetDefaultControlFlags(PRUint32 controlFlags) +{ + mDefaultControlFlags = controlFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsLoggingService::GetDefaultLogEventSink(nsILogEventSink* *sink) +{ + *sink = mDefaultSink; + NS_ADDREF(*sink); + return NS_OK; +} + +NS_IMETHODIMP +nsLoggingService::SetDefaultLogEventSink(nsILogEventSink* sink) +{ + mDefaultSink = sink; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsLog + +nsLog::nsLog() + : mName(nsnull), + mControlFlags(0), + mIndentLevel(0) +{ + mEnabledLevel = LEVEL_ERROR; + NS_INIT_REFCNT(); +} + +nsLog::~nsLog() +{ + if (mName) nsCRT::free(mName); +} + +NS_IMPL_ISUPPORTS1(nsLog, nsILog) + +static void PR_CALLBACK +DeleteTimingData(void *priv) +{ + nsTimingData* data = (nsTimingData*)priv; + delete data; +} + +nsresult +nsLog::Init(const char* name, PRUint32 controlFlags, nsILogEventSink* sink) +{ + mName = nsCRT::strdup(name); + if (mName == nsnull) + return NS_ERROR_OUT_OF_MEMORY; + mControlFlags = controlFlags; + mSink = sink; + + nsStringKey key(name); + PRUint32 level = (PRUint32)gSettings->Get(&key); + if (level != LEVEL_NEVER) { + mEnabledLevel = level; + } + PRStatus status = PR_NewThreadPrivateIndex(&mThreadTimingDataIndex, + DeleteTimingData); + if (status != PR_SUCCESS) + return NS_ERROR_FAILURE; + return NS_OK; +} + +NS_IMETHODIMP +nsLog::GetName(char* *aName) +{ + *aName = nsCRT::strdup(mName); + return *aName ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsLog::GetLevel(PRUint32 *aLevel) +{ + *aLevel = mEnabledLevel; + return NS_OK; +} + +NS_IMETHODIMP +nsLog::SetLevel(PRUint32 aLevel) +{ + mEnabledLevel = aLevel; + return NS_OK; +} + +NS_IMETHODIMP +nsLog::Enabled(PRUint32 level, PRBool *result) +{ + *result = Test(level); + return NS_OK; +} + +NS_IMETHODIMP +nsLog::Print(PRUint32 level, const PRUnichar *message) +{ + nsAutoMonitor monitor(gLogMonitor); + + nsCString str(message); + char* msg = str.ToNewCString(); + nsLogEvent event(this, level); + nsresult rv = event.Printf(msg); + nsCRT::free(msg); + return rv; +} + +NS_IMETHODIMP +nsLog::Flush(void) +{ + nsAutoMonitor monitor(gLogMonitor); + + return mSink->Flush(); +} + +NS_IMETHODIMP +nsLog::PrintEvent(nsLogEvent& event) +{ + nsAutoMonitor monitor(gLogMonitor); + + return mSink->PrintEvent(event); +} + +NS_IMETHODIMP +nsLog::IncreaseIndent() +{ + nsAutoMonitor monitor(gLogMonitor); + + mIndentLevel++; + return NS_OK; +} + +NS_IMETHODIMP +nsLog::DecreaseIndent() +{ + nsAutoMonitor monitor(gLogMonitor); + +// PR_ASSERT(mIndentLevel > 0); // XXX layout is having trouble + if (mIndentLevel == 0) + return NS_ERROR_FAILURE; + mIndentLevel--; + return NS_OK; +} + +NS_IMETHODIMP +nsLog::GetIndentLevel(PRUint32 *aIndentLevel) +{ + *aIndentLevel = mIndentLevel; + return NS_OK; +} + +NS_IMETHODIMP +nsLog::Describe(nsILog* out) +{ + nsresult rv; + const char* levelName; + switch (mEnabledLevel) { + case nsILog::LEVEL_NEVER: levelName = "NEVER"; break; + case nsILog::LEVEL_ERROR: levelName = "ERROR"; break; + case nsILog::LEVEL_WARN: levelName = "WARN"; break; + case nsILog::LEVEL_STDOUT: levelName = "STDOUT"; break; + case nsILog::LEVEL_DBG: levelName = "DBG"; break; + default: levelName = ""; break; + } + char* dest = nsnull; + rv = mSink->GetDestinationName(&dest); + if (NS_FAILED(rv)) { + dest = nsCRT::strdup(""); + } + NS_LOG(out, STDOUT, (" %-20.20s %-8.8s %s\n", mName, levelName, dest)); + if (dest) nsCRT::free(dest); + return NS_OK; +} + +NS_IMETHODIMP +nsLog::BeginTiming(void) +{ + nsTimingData* data; + if (mControlFlags & TIMING_PER_THREAD) { + data = (nsTimingData*)PR_GetThreadPrivate(mThreadTimingDataIndex); + if (data == nsnull) { + data = new nsTimingData; + if (data == nsnull) + return NS_ERROR_OUT_OF_MEMORY; + PRStatus status = PR_SetThreadPrivate(mThreadTimingDataIndex, data); + if (status != PR_SUCCESS) + return NS_ERROR_FAILURE; + } + } + else { + data = &mTimingData; + } + + PR_ASSERT(data->mStartTime == 0); + if (data->mStartTime != 0) + return NS_ERROR_FAILURE; + data->mStartTime = PR_IntervalNow(); + return NS_OK; +} + +NS_IMETHODIMP +nsLog::EndTiming(PRIntervalTime *elapsedTime) +{ + nsTimingData* data; + if (mControlFlags & TIMING_PER_THREAD) + data = (nsTimingData*)PR_GetThreadPrivate(mThreadTimingDataIndex); + else + data = &mTimingData; + + PR_ASSERT(data->mStartTime != 0); + if (data->mStartTime == 0) + return NS_ERROR_FAILURE; + PRIntervalTime elapsed = PR_IntervalNow(); + elapsed -= data->mStartTime; + data->mStartTime = 0; + data->mTimingSamples++; + data->mTotalTime += elapsed; + data->mTotalSquaredTime += elapsed * elapsed; + *elapsedTime = elapsed; + + if (mControlFlags & TIMING_PER_THREAD) { + // dump per-thread data into per-log data: + mTimingData.mTotalTime += data->mTotalTime; + mTimingData.mTotalSquaredTime += data->mTotalSquaredTime; + mTimingData.mTimingSamples += data->mTimingSamples; + + // destroy TLS: + PRStatus status = PR_SetThreadPrivate(mThreadTimingDataIndex, nsnull); + if (status != PR_SUCCESS) + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsLog::GetTimingStats(PRUint32 *sampleSize, + double *meanTime, + double *stdDevTime) +{ + *sampleSize = mTimingData.mTimingSamples; + double mean = mTimingData.mTotalTime / mTimingData.mTimingSamples; + *meanTime = (PRIntervalTime)mean; + double variance = fabs(mTimingData.mTotalSquaredTime / mTimingData.mTimingSamples - mean * mean); + *stdDevTime = sqrt(variance); + return NS_OK; +} + +NS_IMETHODIMP +nsLog::DescribeTiming(nsILog* out, const char* msg) +{ + nsresult rv; + + PR_ASSERT(mTimingData.mStartTime == 0); + if (mTimingData.mStartTime != 0) { + NS_LOG(this, STDOUT, ("%s: TIMING ERROR\n", msg)); + return NS_ERROR_FAILURE; + } + + PRUint32 realTimeSamples; + double realTimeMean, realTimeStdDev; + rv = GetTimingStats(&realTimeSamples, &realTimeMean, &realTimeStdDev); + if (NS_FAILED(rv)) return rv; + PRUint32 tps = PR_TicksPerSecond(); + if (realTimeSamples > 1) { + NS_LOG(out, STDOUT, ("%s: %.2f +/- %.2f ms (%d samples)\n", + msg, + realTimeMean * 1000 / tps, + realTimeStdDev * 1000 / tps, + realTimeSamples)); + } + else { + NS_LOG(out, STDOUT, ("%s: %.2f ms\n", + msg, realTimeMean * 1000 / tps)); + } + return NS_OK; +} + +NS_IMETHODIMP +nsLog::ResetTiming(void) +{ + PR_ASSERT(mTimingData.mStartTime == 0); + mTimingData.mTotalTime = 0; + mTimingData.mTotalSquaredTime = 0; + mTimingData.mTimingSamples = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsLog::GetControlFlags(PRUint32 *flags) +{ + nsAutoMonitor monitor(gLogMonitor); + + *flags = mControlFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsLog::SetControlFlags(PRUint32 flags) +{ + nsAutoMonitor monitor(gLogMonitor); + + mControlFlags = flags; + return NS_OK; +} + +NS_IMETHODIMP +nsLog::GetLogEventSink(nsILogEventSink* *sink) +{ + nsAutoMonitor monitor(gLogMonitor); + + *sink = mSink; + NS_ADDREF(*sink); + return NS_OK; +} + +NS_IMETHODIMP +nsLog::SetLogEventSink(nsILogEventSink* sink) +{ + nsAutoMonitor monitor(gLogMonitor); + + mSink = sink; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsLogEvent + +nsresult +nsLogEvent::Printf(const char* format, ...) +{ + if (mMessage == nsnull) { + va_list args; + va_start(args, format); + mMessage = PR_vsmprintf(format, args); + va_end(args); + } + return mLog->PrintEvent(*this); +} + +nsresult +nsLogEvent::Vprintf(const char* format, va_list args) +{ + if (mMessage == nsnull) { + mMessage = PR_vsmprintf(format, args); + } + return mLog->PrintEvent(*this); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsStandardLogEventSink + +nsStandardLogEventSink::nsStandardLogEventSink() + : mName(nsnull), + mOutput(nsnull), + mDebugLevel(nsILog::LEVEL_NEVER), + mBeginningOfLine(PR_TRUE), + mCloseFile(PR_FALSE) +{ + NS_INIT_REFCNT(); +} + +nsStandardLogEventSink::~nsStandardLogEventSink() +{ + if (mCloseFile) { + ::fclose(mOutput); + mOutput = nsnull; + } + if (mName) nsCRT::free(mName); +} + +NS_IMPL_ISUPPORTS2(nsStandardLogEventSink, + nsIStandardLogEventSink, + nsILogEventSink) + +NS_IMETHODIMP +nsStandardLogEventSink::GetDestinationName(char* *result) +{ + *result = nsCRT::strdup(mName); + return *result ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsStandardLogEventSink::Init(const char* filePath, PRUint32 levelForDebugOutput) +{ + FILE* filePtr; + if (nsCRT::strcmp(filePath, "1") == 0) { + filePtr = stdout; + } + else if (nsCRT::strcmp(filePath, "2") == 0) { + filePtr = stderr; + } + else { + filePtr = ::fopen(filePath, "W"); + if (filePtr == nsnull) + return NS_ERROR_FAILURE; + mCloseFile = PR_TRUE; + } + return InitFromFILE(filePath, filePtr, levelForDebugOutput); +} + +NS_IMETHODIMP +nsStandardLogEventSink::InitFromFILE(const char* name, FILE* filePtr, PRUint32 levelForDebugOutput) +{ + mOutput = filePtr; + mDebugLevel = levelForDebugOutput; + mName = nsCRT::strdup(name); + return mName ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsStandardLogEventSink::PrintEvent(nsLogEvent& event) +{ + nsresult rv; + nsILog* log = event.GetLog(); + PRUint32 level = event.GetLevel(); + const char* msg = event.GetMsg(); + + if (level == nsILog::LEVEL_NEVER) + return NS_OK; + + // do debug output first + if (level <= mDebugLevel) { +#ifdef XP_PC + OutputDebugString(msg); +#elif defined(XP_MAC) + { +# define BUF_SIZE 1024 + char buf[BUF_SIZE]; + PRUint32 len = + PL_snprintf(buf+1, BUF_SIZE-1, "ERROR: %s", msg); + buf[0] = (char) (len > 255 ? 255 : len); + DebugStr(StringPtr(buf)); + } +#endif + } + + if (!log->Test(level)) + return NS_OK; + + nsAutoMonitor monitor(gLogMonitor); + + if (!mBeginningOfLine) { + ::fputc('\n', mOutput); + } + + // print preamble + char levels[] = { 'X', 'E', 'W', ' ', 'D' }; + PRUint32 flags; + rv = log->GetControlFlags(&flags); + if (NS_FAILED(rv)) return rv; + if (flags & nsILog::PRINT_THREAD_ID) { + ::fprintf(mOutput, "%8x ", PR_CurrentThread()); + } + else { + ::fprintf(mOutput, "%8s ", ""); + } + + char* name; + rv = log->GetName(&name); + ::fprintf(mOutput, "%-8.8s %c ", + flags & nsILog::PRINT_LOG_NAME ? name : "", + flags & nsILog::PRINT_LEVEL ? levels[level] : ' '); + nsCRT::free(name); + mBeginningOfLine = PR_FALSE; + + PRUint32 indentLevel; + rv = log->GetIndentLevel(&indentLevel); + if (NS_FAILED(rv)) return rv; + do { + indent: + // do indentation + for (PRUint32 i = 0; i < indentLevel; i++) { + ::fputs("| ", mOutput); + } + + char c; + while ((c = *msg++)) { + switch (c) { + case '\n': + if (*msg == '\0') { + ::fputc('\n', mOutput); + mBeginningOfLine = PR_TRUE; + break; + } + else { + ::fputs("\n ", mOutput); + goto indent; + } + default: + ::fputc(c, mOutput); + } + } + } while (0); + return NS_OK; +} + +NS_IMETHODIMP +nsStandardLogEventSink::Flush() +{ + ::fflush(mOutput); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +#endif // NS_ENABLE_LOGGING diff --git a/mozilla/xpcom/base/nsLogging.h b/mozilla/xpcom/base/nsLogging.h new file mode 100644 index 00000000000..953ae7e046d --- /dev/null +++ b/mozilla/xpcom/base/nsLogging.h @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1998 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + */ + +#ifndef nsLogging_h__ +#define nsLogging_h__ + +#include "nsILoggingService.h" + +#ifdef NS_ENABLE_LOGGING + +#include "nsHashtable.h" +#include "nsCOMPtr.h" + +class nsLoggingService : public nsILoggingService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSILOGGINGSERVICE + + nsLoggingService(); + virtual ~nsLoggingService(); + + static NS_METHOD + Create(nsISupports* outer, const nsIID& aIID, void* *aInstancePtr); + + nsresult Init(); + +protected: + nsSupportsHashtable mLogs; + PRUint32 mDefaultControlFlags; + nsCOMPtr mDefaultSink; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsTimingData { +public: + nsTimingData() + : mStartTime(0), + mTotalTime(0), + mTotalSquaredTime(0), + mTimingSamples(0) { + } + PRIntervalTime mStartTime; + double mTotalTime; + double mTotalSquaredTime; + PRUint32 mTimingSamples; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsLog : public nsILog +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSILOG + + nsLog(); + virtual ~nsLog(); + + nsresult Init(const char* name, PRUint32 controlFlags, nsILogEventSink* sink); + +protected: + char* mName; + PRUint32 mControlFlags; + PRUint32 mIndentLevel; + PRUintn mThreadTimingDataIndex; + nsTimingData mTimingData; + nsCOMPtr mSink; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class nsStandardLogEventSink : public nsIStandardLogEventSink +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSILOGEVENTSINK + NS_DECL_NSISTANDARDLOGEVENTSINK + + nsStandardLogEventSink(); + virtual ~nsStandardLogEventSink(); + +protected: + char* mName; + FILE* mOutput; + PRUint32 mDebugLevel; + PRBool mBeginningOfLine; + PRBool mCloseFile; +}; + +#endif // NS_ENABLE_LOGGING + +#endif // nsLogging_h__ + + +