log.cpp revision 4c74ce9adde4dc1b18a34a3eddc709e6260f2bee
/* $Id$ */
/** @file
* Runtime VBox - Logger.
*/
/*
* Copyright (C) 2006-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE distribution, in which case the provisions of the
* CDDL are applicable instead of those of the GPL.
*
* You may elect to license modified versions of this file under the
* terms and conditions of either the GPL or the CDDL or both.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#ifndef IN_RC
# include <iprt/semaphore.h>
#endif
#ifdef IN_RING3
# include <iprt/lockvalidator.h>
#endif
#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
# include <iprt/asm-amd64-x86.h>
#endif
#ifdef IN_RING3
# include <stdio.h>
#endif
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* Arguments passed to the output function.
*/
typedef struct RTLOGOUTPUTPREFIXEDARGS
{
/** The logger instance. */
/** The flags. (used for prefixing.) */
unsigned fFlags;
/** The group. (used for prefixing.) */
unsigned iGroup;
/**
* Internal logger data.
*
* @remarks Don't make casual changes to this structure.
*/
typedef struct RTLOGGERINTERNAL
{
/** The structure revision (RTLOGGERINTERNAL_REV). */
/** The size of the internal logger structure. */
/** Spinning mutex semaphore. Can be NIL. */
/** Pointer to the flush function. */
/** Custom prefix callback. */
/** Prefix callback argument. */
void *pvPrefixUserArg;
/** This is set if a prefix is pending. */
bool fPendingPrefix;
/** Alignment padding. */
bool afPadding1[3];
/** The max number of groups that there is room for in afGroups and papszGroups.
* Used by RTLogCopyGroupAndFlags(). */
/** Pointer to the group name array.
* (The data is readonly and provided by the user.) */
const char * const *papszGroups;
/** The number of log entries per group. NULL if
* RTLOGFLAGS_RESTRICT_GROUPS is not specified. */
/** The max number of entries per group. */
/** Padding. */
#ifdef IN_RING3 /* Note! Must be at the end! */
/** @name File logging bits for the logger.
* @{ */
/** Pointer to the function called when starting logging, and when
* ending or starting a new log file as part of history rotation.
* This can be NULL. */
/** Handle to log file (if open). */
/** Log file history settings: maximum amount of data to put in a file. */
/** Log file history settings: current amount of data in a file. */
/** Log file history settings: maximum time to use a file (in seconds). */
/** Log file history settings: in what time slot was the file created. */
/** Log file history settings: number of older files to keep.
* 0 means no history. */
/** Pointer to filename. */
char szFilename[RTPATH_MAX];
/** @} */
#endif /* IN_RING3 */
/** The revision of the internal logger structure. */
#ifdef IN_RING3
/** The size of the RTLOGGERINTERNAL structure in ring-0. */
#endif
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
#ifndef IN_RC
static unsigned rtlogGroupFlags(const char *psz);
#endif
#ifdef IN_RING0
static void rtR0LogLoggerExFallback(uint32_t fDestFlags, uint32_t fFlags, const char *pszFormat, va_list va);
#endif
#ifdef IN_RING3
#endif
static void rtlogLoggerExVLocked(PRTLOGGER pLogger, unsigned fFlags, unsigned iGroup, const char *pszFormat, va_list args);
#ifndef IN_RC
static void rtlogLoggerExFLocked(PRTLOGGER pLogger, unsigned fFlags, unsigned iGroup, const char *pszFormat, ...);
#endif
/*******************************************************************************
* Global Variables *
*******************************************************************************/
#ifdef IN_RC
/** Default logger instance. Make it weak because our RC module loader does not
* necessarily resolve this symbol and the compiler _must_ check if this is
* the case or not. That doesn't work for Darwin (``incompatible feature used:
* .weak_reference (must specify "-dynamic" to be used'') */
# ifdef RT_OS_DARWIN
# else
# endif
#else /* !IN_RC */
/** Default logger instance. */
#endif /* !IN_RC */
#ifdef IN_RING3
/** The RTThreadGetWriteLockCount() change caused by the logger mutex semaphore. */
static uint32_t volatile g_cLoggerLockCount;
#endif
#ifdef IN_RING0
/** Number of per-thread loggers. */
static int32_t volatile g_cPerThreadLoggers;
/** Per-thread loggers.
* This is just a quick TLS hack suitable for debug logging only.
* If we run out of entries, just unload and reload the driver. */
static struct RTLOGGERPERTHREAD
{
/** The thread. */
RTNATIVETHREAD volatile NativeThread;
/** The (process / session) key. */
/** The logger instance.*/
} g_aPerThreadLoggers[8] =
{
{ NIL_RTNATIVETHREAD, 0, 0},
{ NIL_RTNATIVETHREAD, 0, 0},
{ NIL_RTNATIVETHREAD, 0, 0},
{ NIL_RTNATIVETHREAD, 0, 0},
{ NIL_RTNATIVETHREAD, 0, 0},
{ NIL_RTNATIVETHREAD, 0, 0},
{ NIL_RTNATIVETHREAD, 0, 0},
{ NIL_RTNATIVETHREAD, 0, 0}
};
#endif /* IN_RING0 */
/**
* Logger flags instructions.
*/
static struct
{
const char *pszInstr; /**< The name */
bool fInverted; /**< Inverse meaning? */
} const s_aLogFlags[] =
{
/* We intentionally omit RTLOGFLAGS_RESTRICT_GROUPS. */
};
/**
* Logger destination instructions.
*/
static struct
{
const char *pszInstr; /**< The name. */
} const s_aLogDst[] =
{
{ "history", sizeof("history" ) - 1, 0 }, /* Must be 3rd! */
{ "histsize", sizeof("histsize") - 1, 0 }, /* Must be 4th! */
{ "histtime", sizeof("histtime") - 1, 0 }, /* Must be 5th! */
};
/**
* Locks the logger instance.
*
* @returns See RTSemSpinMutexRequest().
* @param pLogger The logger instance.
*/
{
#ifndef IN_RC
AssertMsgReturn(pInt->uRevision == RTLOGGERINTERNAL_REV, ("%#x != %#x\n", pInt->uRevision, RTLOGGERINTERNAL_REV),
{
if (RT_FAILURE(rc))
return rc;
}
#else
#endif
return VINF_SUCCESS;
}
/**
* Unlocks the logger instance.
* @param pLogger The logger instance.
*/
{
#ifndef IN_RC
#else
#endif
return;
}
#ifndef IN_RC
# ifdef IN_RING3
# ifdef SOME_UNUSED_FUNCTION
/**
* Logging to file, output callback.
*
* @param pvArg User argument.
* @param pachChars Pointer to an array of utf-8 characters.
* @param cbChars Number of bytes in the character array pointed to by pachChars.
*/
{
return cbChars;
}
/**
* Callback to format VBox formatting extentions.
* See @ref pg_rt_str_format for a reference on the format types.
*
* @returns The number of bytes formatted.
* @param pvArg Formatter argument.
* @param pfnOutput Pointer to output function.
* @param pvArgOutput Argument for the output function.
* @param ppszFormat Pointer to the format string pointer. Advance this till the char
* after the format specifier.
* @param pArgs Pointer to the argument list. Use this to fetch the arguments.
* @param cchWidth Format Width. -1 if not specified.
* @param cchPrecision Format Precision. -1 if not specified.
* @param fFlags Flags (RTSTR_NTFS_*).
* @param chArgSize The argument size specifier, 'l' or 'L'.
*/
static DECLCALLBACK(size_t) rtlogPhaseFormatStr(void *pvArg, PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
{
char ch = *(*ppszFormat)++;
return 0;
}
# endif /* SOME_UNUSED_FUNCTION */
/**
* Log phase callback function, assumes the lock is already held
*
* @param pLogger The logger instance.
* @param pszFormat Format string.
* @param ... Optional arguments as specified in the format string.
*/
{
}
/**
* Log phase callback function, assumes the lock is not held.
*
* @param pLogger The logger instance.
* @param pszFormat Format string.
* @param ... Optional arguments as specified in the format string.
*/
{
}
# endif /* IN_RING3 */
{
int rc;
/*
* Validate input.
*/
if ( (cGroups && !papszGroups)
{
AssertMsgFailed(("Invalid parameters!\n"));
return VERR_INVALID_PARAMETER;
}
if (pszErrorMsg)
/*
* Allocate a logger instance.
*/
if (fFlags & RTLOGFLAGS_RESTRICT_GROUPS)
if (pLogger)
{
# endif
if (fFlags & RTLOGFLAGS_RESTRICT_GROUPS)
else
# ifdef IN_RING3
if (cbHistoryFileMax == 0)
else
if (cSecsHistoryTimeSlot == 0)
else
# endif /* IN_RING3 */
if (pszGroupSettings)
/*
* Emit wrapper code.
*/
if (pu8Code)
{
pu8Code += sizeof(void *);
*pu8Code++ = 0x64;
*pu8Code++ = 0x24;
*pu8Code++ = 0x04;
rc = VINF_SUCCESS;
}
else
{
# ifdef RT_OS_LINUX
if (pszErrorMsg) /* Most probably SELinux causing trouble since the larger RTMemAlloc succeeded. */
# endif
rc = VERR_NO_MEMORY;
}
if (RT_SUCCESS(rc))
# endif /* X86 wrapper code*/
{
# ifdef IN_RING3 /* files and env.vars. are only accessible when in R3 at the present time. */
/*
* Format the filename.
*/
if (pszFilenameFmt)
{
/** @todo validate the length, fail on overflow. */
}
/*
* Parse the environment variables.
*/
if (pszEnvVarBase)
{
/* make temp copy of environment variable base. */
/*
* Destination.
*/
if (pszValue)
/*
* The flags.
*/
if (pszValue)
/*
* The group settings.
*/
if (pszValue)
}
# endif /* IN_RING3 */
/*
* Open the destination(s).
*/
rc = VINF_SUCCESS;
# ifdef IN_RING3
{
{
/* Rotate in case of appending to a too big log file,
otherwise this simply doesn't do anything. */
}
else
{
/* Force rotation if it is configured. */
/* If the file is not open then rotation is not set up. */
{
}
}
}
# endif /* IN_RING3 */
/*
* Create mutex and check how much it counts when entering the lock
* so that we can report the values for RTLOGFLAGS_PREFIX_LOCK_COUNTS.
*/
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
# ifdef IN_RING3 /** @todo do counters in ring-0 too? */
if (Thread != NIL_RTTHREAD)
{
c = RTLockValidatorWriteLockGetCount(Thread) - c;
}
/* Use the callback to generate some initial log contents. */
# endif
return VINF_SUCCESS;
}
if (pszErrorMsg)
}
# ifdef IN_RING3
# endif
# if defined(LOG_USE_C99) && defined(RT_WITHOUT_EXEC_ALLOC)
# else
# endif
}
}
else
rc = VERR_NO_MEMORY;
return rc;
}
{
int rc;
return rc;
}
{
int rc;
return rc;
}
/**
* Destroys a logger instance.
*
* The instance is flushed and all output destinations closed (where applicable).
*
* @returns iprt status code.
* @param pLogger The logger instance which close destroyed. NULL is fine.
*/
{
int rc;
/*
* Validate input.
*/
if (!pLogger)
return VINF_SUCCESS;
/*
* Acquire logger instance sem and disable all logging. (paranoia)
*/
while (iGroup-- > 0)
/*
* Flush it.
*/
# ifdef IN_RING3
/*
* Add end of logging message.
*/
/*
* Close output stuffs.
*/
{
}
# endif
/*
* Free the mutex, the wrapper and the instance memory.
*/
if (hSpinMtx != NIL_RTSEMSPINMUTEX)
{
int rc2;
}
{
# if defined(LOG_USE_C99) && defined(RT_WITHOUT_EXEC_ALLOC)
# else
# endif
}
return rc;
}
/**
* Create a logger instance clone for RC usage.
*
* @returns iprt status code.
*
* @param pLogger The logger instance to be cloned.
* @param pLoggerRC Where to create the RC logger instance.
* @param cbLoggerRC Amount of memory allocated to for the RC logger
* instance clone.
* @param pfnLoggerRCPtr Pointer to logger wrapper function for this
* instance (RC Ptr).
* @param pfnFlushRCPtr Pointer to flush function (RC Ptr).
* @param fFlags Logger instance flags, a combination of the RTLOGFLAGS_* values.
*/
{
/*
* Validate input.
*/
if ( !pLoggerRC
|| !pfnFlushRCPtr
|| !pfnLoggerRCPtr)
{
AssertMsgFailed(("Invalid parameters!\n"));
return VERR_INVALID_PARAMETER;
}
if (cbLoggerRC < sizeof(*pLoggerRC))
{
return VERR_INVALID_PARAMETER;
}
/*
* Initialize GC instance.
*/
pLoggerRC->offScratch = 0;
pLoggerRC->fPendingPrefix = false;
/*
* Resolve defaults.
*/
if (!pLogger)
{
if (!pLogger)
return VINF_SUCCESS;
}
/*
* Check if there's enough space for the groups.
*/
{
AssertMsgFailed(("%d req=%d cGroups=%d\n", cbLoggerRC, RT_OFFSETOF(RTLOGGERRC, afGroups[pLogger->cGroups]), pLogger->cGroups));
return VERR_BUFFER_OVERFLOW;
}
memcpy(&pLoggerRC->afGroups[0], &pLogger->afGroups[0], pLogger->cGroups * sizeof(pLoggerRC->afGroups[0]));
/*
* Copy bits from the HC instance.
*/
/*
* Check if we can remove the disabled flag.
*/
if ( pLogger->fDestFlags
return VINF_SUCCESS;
}
/**
* Flushes a RC logger instance to a R3 logger.
*
*
* @returns iprt status code.
* @param pLogger The R3 logger instance to flush pLoggerRC to. If NULL
* the default logger is used.
* @param pLoggerRC The RC logger instance to flush.
*/
{
/*
* Resolve defaults.
*/
if (!pLogger)
{
if (!pLogger)
{
pLoggerRC->offScratch = 0;
return;
}
}
/*
* Any thing to flush?
*/
if ( pLogger->offScratch
|| pLoggerRC->offScratch)
{
/*
* Acquire logger instance sem.
*/
if (RT_FAILURE(rc))
return;
/*
* Write whatever the GC instance contains to the HC one, and then
* flush the HC instance.
*/
if (pLoggerRC->offScratch)
{
pLoggerRC->offScratch = 0;
}
/*
* Release the semaphore.
*/
}
}
# ifdef IN_RING3
{
/*
* Validate input.
*/
/*
* Initialize the ring-0 instance.
*/
pLogger->achScratch[0] = 0;
pLogger->offScratch = 0;
if (fFlags & RTLOGFLAGS_RESTRICT_GROUPS)
cMaxGroups /= 2;
for (;;)
{
break;
cMaxGroups--;
}
pInt->fPendingPrefix = false;
if (fFlags & RTLOGFLAGS_RESTRICT_GROUPS)
{
}
else
return VINF_SUCCESS;
}
{
cb += sizeof(RTLOGGERINTERNAL);
if (fFlags & RTLOGFLAGS_RESTRICT_GROUPS)
return cb;
}
{
/*
* Validate input.
*/
/*
* Resolve defaults.
*/
if (!pSrcLogger)
{
if (!pSrcLogger)
{
pDstLogger->afGroups[0] = 0;
return VINF_SUCCESS;
}
}
/*
* Copy flags and group settings.
*/
PRTLOGGERINTERNAL pDstInt = (PRTLOGGERINTERNAL)((uintptr_t)pDstLogger->pInt - pDstLoggerR0Ptr + (uintptr_t)pDstLogger);
int rc = VINF_SUCCESS;
{
pSrcLogger->cGroups, RT_OFFSETOF(RTLOGGER, afGroups[pSrcLogger->cGroups]) + RTLOGGERINTERNAL_R0_SIZE));
}
memcpy(&pDstLogger->afGroups[0], &pSrcLogger->afGroups[0], cGroups * sizeof(pDstLogger->afGroups[0]));
return rc;
}
{
/*
* Do the work.
*/
PRTLOGGERINTERNAL pInt = (PRTLOGGERINTERNAL)((uintptr_t)pLogger->pInt - pLoggerR0Ptr + (uintptr_t)pLogger);
return VINF_SUCCESS;
}
{
/*
* Resolve defaults.
*/
if (!pLogger)
{
if (!pLogger)
{
if (pLoggerR0->offScratch)
pLoggerR0->offScratch = 0;
return;
}
}
/*
* Any thing to flush?
*/
if ( pLoggerR0->offScratch
|| pLogger->offScratch)
{
/*
* Acquire logger semaphores.
*/
if (RT_FAILURE(rc))
return;
if (RT_SUCCESS(rc))
{
/*
* Write whatever the GC instance contains to the HC one, and then
* flush the HC instance.
*/
if (pLoggerR0->offScratch)
{
pLoggerR0->offScratch = 0;
}
}
}
}
# endif /* IN_RING3 */
/**
* Flushes the buffer in one logger instance onto another logger.
*
* @returns iprt status code.
*
* @param pSrcLogger The logger instance to flush.
* @param pDstLogger The logger instance to flush onto.
* If NULL the default logger will be used.
*/
{
/*
* Resolve defaults.
*/
if (!pDstLogger)
{
if (!pDstLogger)
{
if (pSrcLogger->offScratch)
{
if (RT_SUCCESS(rc))
{
pSrcLogger->offScratch = 0;
}
}
return;
}
}
/*
* Any thing to flush?
*/
if ( pSrcLogger->offScratch
|| pDstLogger->offScratch)
{
/*
* Acquire logger semaphores.
*/
if (RT_FAILURE(rc))
return;
if (RT_SUCCESS(rc))
{
/*
* Write whatever the GC instance contains to the HC one, and then
* flush the HC instance.
*/
if (pSrcLogger->offScratch)
{
pSrcLogger->offScratch = 0;
}
/*
* Release the semaphores.
*/
}
}
}
/**
* Sets the custom prefix callback.
*
* @returns IPRT status code.
* @param pLogger The logger instance.
* @param pfnCallback The callback.
* @param pvUser The user argument for the callback.
* */
RTDECL(int) RTLogSetCustomPrefixCallback(PRTLOGGER pLogger, PFNRTLOGPREFIX pfnCallback, void *pvUser)
{
/*
* Resolve defaults.
*/
if (!pLogger)
{
if (!pLogger)
return VINF_SUCCESS;
}
/*
* Do the work.
*/
return VINF_SUCCESS;
}
/**
* Matches a group name with a pattern mask in an case insensitive manner (ASCII).
*
* @returns true if matching and *ppachMask set to the end of the pattern.
* @returns false if no match.
* @param pszGrp The group name.
* @param ppachMask Pointer to the pointer to the mask. Only wildcard supported is '*'.
* @param cchMask The length of the mask, including modifiers. The modifiers is why
* we update *ppachMask on match.
*/
{
const char *pachMask;
return false;
for (;;)
{
{
const char *pszTmp;
/*
* Check for wildcard and do a minimal match if found.
*/
if (*pachMask != '*')
return false;
/* eat '*'s. */
do pachMask++;
/* is there more to match? */
if ( !cchMask
|| *pachMask == '.'
|| *pachMask == '=')
break; /* we're good */
/* do extremely minimal matching (fixme) */
if (!pszTmp)
if (!pszTmp)
return false;
continue;
}
/* done? */
if (!*++pszGrp)
{
/* trailing wildcard is ok. */
do
{
pachMask++;
cchMask--;
if ( !cchMask
|| *pachMask == '.'
|| *pachMask == '=')
break; /* we're good */
return false;
}
if (!--cchMask)
return false;
pachMask++;
}
/* match */
return true;
}
/**
* Updates the group settings for the logger instance using the specified
* specification string.
*
* @returns iprt status code.
* Failures can safely be ignored.
* @param pLogger Logger instance.
* @param pszValue Value to parse.
*/
{
/*
* Resolve defaults.
*/
if (!pLogger)
{
if (!pLogger)
return VINF_SUCCESS;
}
/*
* Iterate the string.
*/
while (*pszValue)
{
/*
* Skip prefixes (blanks, ;, + and -).
*/
bool fEnabled = true;
char ch;
const char *pszStart;
unsigned i;
{
pszValue++;
}
if (!*pszValue)
break;
/*
* Find end.
*/
pszValue++;
/*
* Find the group (ascii case insensitive search).
* Special group 'all'.
*/
if ( cch >= 3
{
/*
* All.
*/
{
if (fEnabled)
else
}
}
else
{
/*
* Specific group(s).
*/
{
{
if (fEnabled)
else
}
} /* for each group */
}
} /* parse specification */
return VINF_SUCCESS;
}
/**
* Interprets the group flags suffix.
*
* @returns Flags specified. (0 is possible!)
* @param psz Start of Suffix. (Either dot or equal sign.)
*/
static unsigned rtlogGroupFlags(const char *psz)
{
unsigned fFlags = 0;
/*
* Literal flags.
*/
while (*psz == '.')
{
static struct
{
const char *pszFlag; /* lowercase!! */
unsigned fFlag;
} aFlags[] =
{
{ "eo", RTLOGGRPFLAGS_ENABLED },
{ "enabledonly",RTLOGGRPFLAGS_ENABLED },
{ "l1", RTLOGGRPFLAGS_LEVEL_1 },
{ "level1", RTLOGGRPFLAGS_LEVEL_1 },
{ "l", RTLOGGRPFLAGS_LEVEL_2 },
{ "l2", RTLOGGRPFLAGS_LEVEL_2 },
{ "level2", RTLOGGRPFLAGS_LEVEL_2 },
{ "l3", RTLOGGRPFLAGS_LEVEL_3 },
{ "level3", RTLOGGRPFLAGS_LEVEL_3 },
{ "l4", RTLOGGRPFLAGS_LEVEL_4 },
{ "level4", RTLOGGRPFLAGS_LEVEL_4 },
{ "l5", RTLOGGRPFLAGS_LEVEL_5 },
{ "level5", RTLOGGRPFLAGS_LEVEL_5 },
{ "l6", RTLOGGRPFLAGS_LEVEL_6 },
{ "level6", RTLOGGRPFLAGS_LEVEL_6 },
{ "f", RTLOGGRPFLAGS_FLOW },
{ "flow", RTLOGGRPFLAGS_FLOW },
{ "restrict", RTLOGGRPFLAGS_RESTRICT },
{ "lelik", RTLOGGRPFLAGS_LELIK },
{ "michael", RTLOGGRPFLAGS_MICHAEL },
{ "sunlover", RTLOGGRPFLAGS_SUNLOVER },
{ "achim", RTLOGGRPFLAGS_ACHIM },
{ "achimha", RTLOGGRPFLAGS_ACHIM },
{ "s", RTLOGGRPFLAGS_SANDER },
{ "sander", RTLOGGRPFLAGS_SANDER },
{ "sandervl", RTLOGGRPFLAGS_SANDER },
{ "klaus", RTLOGGRPFLAGS_KLAUS },
{ "frank", RTLOGGRPFLAGS_FRANK },
{ "b", RTLOGGRPFLAGS_BIRD },
{ "bird", RTLOGGRPFLAGS_BIRD },
{ "aleksey", RTLOGGRPFLAGS_ALEKSEY },
{ "dj", RTLOGGRPFLAGS_DJ },
{ "n", RTLOGGRPFLAGS_NONAME },
{ "noname", RTLOGGRPFLAGS_NONAME }
};
unsigned i;
bool fFound = false;
psz++;
{
{
psz1++;
psz2++;
if (!*psz1)
{
break;
fFound = true;
break;
}
} /* strincmp */
} /* for each flags */
}
/*
* Flag value.
*/
if (*psz == '=')
{
psz++;
if (*psz == '~')
else
}
return fFlags;
}
/**
* Helper for RTLogGetGroupSettings.
*/
static int rtLogGetGroupSettingsAddOne(const char *pszName, uint32_t fGroup, char **ppszBuf, size_t *pcchBuf, bool *pfNotFirst)
{
# define APPEND_PSZ(psz,cch) do { memcpy(*ppszBuf, (psz), (cch)); *ppszBuf += (cch); *pcchBuf -= (cch); } while (0)
/*
* Add the name.
*/
return VERR_BUFFER_OVERFLOW;
if (*pfNotFirst)
APPEND_CH(' ');
else
*pfNotFirst = true;
/*
* Only generate mnemonics for the simple+common bits.
*/
/* nothing */;
else if ( fGroup == (RTLOGGRPFLAGS_ENABLED | RTLOGGRPFLAGS_LEVEL_1 | RTLOGGRPFLAGS_LEVEL_2 | RTLOGGRPFLAGS_FLOW)
&& *pcchBuf >= sizeof(".e.l.f"))
APPEND_SZ(".e.l.f");
&& *pcchBuf >= sizeof(".e.f"))
APPEND_SZ(".e.f");
{
APPEND_CH('=');
}
else
return VERR_BUFFER_OVERFLOW;
return VINF_SUCCESS;
}
/**
* Get the current log group settings as a string.
*
* @returns VINF_SUCCESS or VERR_BUFFER_OVERFLOW.
* @param pLogger Logger instance (NULL for default logger).
* @param pszBuf The output buffer.
* @param cchBuf The size of the output buffer. Must be greater
* than zero.
*/
{
bool fNotFirst = false;
int rc = VINF_SUCCESS;
uint32_t i;
/*
* Resolve defaults.
*/
if (!pLogger)
{
if (!pLogger)
{
*pszBuf = '\0';
return VINF_SUCCESS;
}
}
/*
* Check if all are the same.
*/
for (i = 1; i < cGroups; i++)
break;
if (i >= cGroups)
else
{
/*
* Iterate all the groups and print all that are enabled.
*/
for (i = 0; i < cGroups; i++)
{
if (fGroup)
{
if (pszName)
{
if (rc)
break;
}
}
}
}
*pszBuf = '\0';
return rc;
}
#endif /* !IN_RC */
/**
* Updates the flags for the logger instance using the specified
* specification string.
*
* @returns iprt status code.
* Failures can safely be ignored.
* @param pLogger Logger instance (NULL for default logger).
* @param pszValue Value to parse.
*/
{
int rc = VINF_SUCCESS;
/*
* Resolve defaults.
*/
if (!pLogger)
{
if (!pLogger)
return VINF_SUCCESS;
}
/*
* Iterate the string.
*/
while (*pszValue)
{
/* check no prefix. */
bool fNo = false;
char ch;
unsigned i;
/* skip blanks. */
while (RT_C_IS_SPACE(*pszValue))
pszValue++;
if (!*pszValue)
return rc;
{
{
pszValue += 2;
}
else if (ch == '+')
{
pszValue++;
fNo = true;
}
{
pszValue++;
}
else
break;
}
/* instruction. */
for (i = 0; i < RT_ELEMENTS(s_aLogFlags); i++)
{
{
else
break;
}
}
/* unknown instruction? */
if (i >= RT_ELEMENTS(s_aLogFlags))
{
pszValue++;
}
/* skip blanks and delimiters. */
pszValue++;
} /* while more environment variable value left */
return rc;
}
/**
* Changes the buffering setting of the specified logger.
*
* This can be used for optimizing longish logging sequences.
*
* @returns The old state.
* @param pLogger The logger instance (NULL is an alias for the
* default logger).
* @param fBuffered The new state.
*/
{
bool fOld;
/*
* Resolve the logger instance.
*/
if (!pLogger)
{
if (!pLogger)
return false;
}
if (fBuffered)
else
return fOld;
}
#ifdef IN_RING3
{
/*
* Resolve the logger instance.
*/
if (!pLogger)
{
if (!pLogger)
return UINT32_MAX;
}
return cOld;
}
#endif
#ifndef IN_RC
/**
* Get the current log flags as a string.
*
* @returns VINF_SUCCESS or VERR_BUFFER_OVERFLOW.
* @param pLogger Logger instance (NULL for default logger).
* @param pszBuf The output buffer.
* @param cchBuf The size of the output buffer. Must be greater
* than zero.
*/
{
bool fNotFirst = false;
int rc = VINF_SUCCESS;
unsigned i;
/*
* Resolve defaults.
*/
if (!pLogger)
{
if (!pLogger)
{
*pszBuf = '\0';
return VINF_SUCCESS;
}
}
/*
* Add the flags in the list.
*/
for (i = 0; i < RT_ELEMENTS(s_aLogFlags); i++)
if ( !s_aLogFlags[i].fInverted
{
{
break;
}
if (fNotFirst)
{
*pszBuf++ = ' ';
cchBuf--;
}
fNotFirst = true;
}
*pszBuf = '\0';
return rc;
}
/**
* Updates the logger destination using the specified string.
*
* @returns VINF_SUCCESS or VERR_BUFFER_OVERFLOW.
* @param pLogger Logger instance (NULL for default logger).
* @param pszValue The value to parse.
*/
{
/*
* Resolve defaults.
*/
if (!pLogger)
{
if (!pLogger)
return VINF_SUCCESS;
}
/*
* Do the parsing.
*/
while (*pszValue)
{
bool fNo;
unsigned i;
/* skip blanks. */
while (RT_C_IS_SPACE(*pszValue))
pszValue++;
if (!*pszValue)
break;
/* check no prefix. */
fNo = false;
{
fNo = true;
pszValue += 2;
}
/* instruction. */
for (i = 0; i < RT_ELEMENTS(s_aLogDst); i++)
{
{
if (!fNo)
else
/* check for value. */
while (RT_C_IS_SPACE(*pszValue))
pszValue++;
{
const char *pszEnd;
pszValue++;
if (!pszEnd)
# ifdef IN_RING3
/* log file name */
if (i == 0 /* file */ && !fNo)
{
}
/* log directory */
{
}
else if (i == 2 /* history */)
{
if (!fNo)
{
char szTmp[32];
if (RT_SUCCESS(rc))
AssertMsgReturn(RT_SUCCESS(rc) && cHistory < _1M, ("Invalid history value %s (%Rrc)!\n", szTmp, rc), rc);
}
else
}
else if (i == 3 /* histsize */)
{
if (!fNo)
{
char szTmp[32];
if (RT_SUCCESS(rc))
}
else
}
else if (i == 4 /* histtime */)
{
if (!fNo)
{
char szTmp[32];
if (RT_SUCCESS(rc))
}
else
}
else
AssertMsgFailedReturn(("Invalid destination value! %s%s doesn't take a value!\n",
# endif /* IN_RING3 */
}
break;
}
}
/* assert known instruction */
("Invalid destination value! unknown instruction %.20s\n", pszValue),
/* skip blanks and delimiters. */
pszValue++;
} /* while more environment variable value left */
return VINF_SUCCESS;
}
/**
* Get the current log destinations as a string.
*
* @returns VINF_SUCCESS or VERR_BUFFER_OVERFLOW.
* @param pLogger Logger instance (NULL for default logger).
* @param pszBuf The output buffer.
* @param cchBuf The size of the output buffer. Must be greater
* than 0.
*/
{
bool fNotFirst = false;
int rc = VINF_SUCCESS;
unsigned i;
*pszBuf = '\0';
/*
* Resolve defaults.
*/
if (!pLogger)
{
if (!pLogger)
return VINF_SUCCESS;
}
/*
* Add the flags in the list.
*/
{
if (fNotFirst)
{
if (RT_FAILURE(rc))
return rc;
}
if (RT_FAILURE(rc))
return rc;
fNotFirst = true;
}
# ifdef IN_RING3
/*
* Add the filename.
*/
if (fDestFlags & RTLOGDEST_FILE)
{
if (RT_FAILURE(rc))
return rc;
if (RT_FAILURE(rc))
return rc;
fNotFirst = true;
}
if (fDestFlags & RTLOGDEST_FILE)
{
char szNum[32];
{
RTStrPrintf(szNum, sizeof(szNum), fNotFirst ? "history=%u" : " history=%u", pLogger->pInt->cHistory);
if (RT_FAILURE(rc))
return rc;
}
{
RTStrPrintf(szNum, sizeof(szNum), fNotFirst ? "histsize=%llu" : " histsize=%llu", pLogger->pInt->cbHistoryFileMax);
if (RT_FAILURE(rc))
return rc;
}
{
RTStrPrintf(szNum, sizeof(szNum), fNotFirst ? "histtime=%llu" : " histtime=%llu", pLogger->pInt->cSecsHistoryTimeSlot);
if (RT_FAILURE(rc))
return rc;
}
}
# endif /* IN_RING3 */
return VINF_SUCCESS;
}
#endif /* !IN_RC */
/**
* Flushes the specified logger.
*
* @param pLogger The logger instance to flush.
* If NULL the default instance is used. The default instance
* will not be initialized by this call.
*/
{
/*
* Resolve defaults.
*/
if (!pLogger)
{
#ifdef IN_RC
#else
#endif
if (!pLogger)
return;
}
/*
* Any thing to flush?
*/
if (pLogger->offScratch)
{
#ifndef IN_RC
/*
* Acquire logger instance sem.
*/
if (RT_FAILURE(rc))
return;
#endif
/*
* Call worker.
*/
#ifndef IN_RC
/*
* Release the semaphore.
*/
#endif
}
}
/**
* Gets the default logger instance, creating it if necessary.
*
* @returns Pointer to default logger instance.
* @returns NULL if no default logger instance available.
*/
{
#ifdef IN_RC
return &g_Logger;
#else /* !IN_RC */
# ifdef IN_RING0
/*
* Check per thread loggers first.
*/
if (g_cPerThreadLoggers)
{
while (i-- > 0)
return g_aPerThreadLoggers[i].pLogger;
}
# endif /* IN_RING0 */
/*
* If no per thread logger, use the default one.
*/
if (!g_pLogger)
return g_pLogger;
#endif /* !IN_RC */
}
/**
* Gets the default logger instance.
*
* @returns Pointer to default logger instance.
* @returns NULL if no default logger instance available.
*/
{
#ifdef IN_RC
return &g_Logger;
#else
# ifdef IN_RING0
/*
* Check per thread loggers first.
*/
if (g_cPerThreadLoggers)
{
while (i-- > 0)
return g_aPerThreadLoggers[i].pLogger;
}
# endif /* IN_RING0 */
return g_pLogger;
#endif
}
#ifndef IN_RC
/**
* Sets the default logger instance.
*
* @returns iprt status code.
* @param pLogger The new default logger instance.
*/
{
}
#endif /* !IN_RC */
#ifdef IN_RING0
/**
* Changes the default logger instance for the current thread.
*
* @returns IPRT status code.
* @param pLogger The logger instance. Pass NULL for deregistration.
* @param uKey Associated key for cleanup purposes. If pLogger is NULL,
* all instances with this key will be deregistered. So in
* order to only deregister the instance associated with the
* current thread use 0.
*/
{
int rc;
if (pLogger)
{
int32_t i;
unsigned j;
/*
* Iterate the table to see if there is already an entry for this thread.
*/
while (i-- > 0)
{
return VINF_SUCCESS;
}
/*
* Allocate a new table entry.
*/
{
return VERR_BUFFER_OVERFLOW; /* horrible error code! */
}
for (j = 0; j < 10; j++)
{
while (i-- > 0)
{
AssertCompile(sizeof(RTNATIVETHREAD) == sizeof(void*));
&& ASMAtomicCmpXchgPtr((void * volatile *)&g_aPerThreadLoggers[i].NativeThread, (void *)Self, (void *)NIL_RTNATIVETHREAD))
{
return VINF_SUCCESS;
}
}
}
}
else
{
/*
* Search the array for the current thread.
*/
while (i-- > 0)
{
}
rc = VINF_SUCCESS;
}
return rc;
}
#endif /* IN_RING0 */
/**
* Write to a logger instance.
*
* @param pLogger Pointer to logger instance.
* @param pszFormat Format string.
* @param args Format arguments.
*/
{
}
/**
* Write to a logger instance.
*
* This function will check whether the instance, group and flags makes up a
* logging kind which is currently enabled before writing anything to the log.
*
* @param pLogger Pointer to logger instance. If NULL the default logger instance will be attempted.
* @param fFlags The logging flags.
* @param iGroup The group.
* The value ~0U is reserved for compatibility with RTLogLogger[V] and is
* only for internal usage!
* @param pszFormat Format string.
* @param args Format arguments.
*/
RTDECL(void) RTLogLoggerExV(PRTLOGGER pLogger, unsigned fFlags, unsigned iGroup, const char *pszFormat, va_list args)
{
int rc;
/*
* A NULL logger means default instance.
*/
if (!pLogger)
{
if (!pLogger)
return;
}
/*
* Validate and correct iGroup.
*/
iGroup = 0;
/*
* If no output, then just skip it.
*/
#ifndef IN_RC
|| !pLogger->fDestFlags
#endif
return;
if ( iGroup != ~0U
&& (pLogger->afGroups[iGroup] & (fFlags | RTLOGGRPFLAGS_ENABLED)) != (fFlags | RTLOGGRPFLAGS_ENABLED))
return;
/*
* Acquire logger instance sem.
*/
if (RT_FAILURE(rc))
{
#ifdef IN_RING0
#endif
return;
}
/*
* Check restrictions and call worker.
*/
#ifndef IN_RC
{
else
{
else
}
}
else
#endif
/*
* Release the semaphore.
*/
}
#ifdef IN_RING0
/**
* For rtR0LogLoggerExFallbackOutput and rtR0LogLoggerExFallbackFlush.
*/
typedef struct RTR0LOGLOGGERFALLBACK
{
/** The current scratch buffer offset. */
/** The destination flags. */
/** The scratch buffer. */
char achScratch[80];
/** Pointer to RTR0LOGLOGGERFALLBACK which is used by
* rtR0LogLoggerExFallbackOutput. */
typedef RTR0LOGLOGGERFALLBACK *PRTR0LOGLOGGERFALLBACK;
/**
* Flushes the fallback buffer.
*
* @param pThis The scratch buffer.
*/
{
if (!pThis->offScratch)
return;
# ifndef LOG_NO_COM
# endif
/* empty the buffer. */
pThis->offScratch = 0;
}
/**
* Callback for RTLogFormatV used by rtR0LogLoggerExFallback.
* See PFNLOGOUTPUT() for details.
*/
static DECLCALLBACK(size_t) rtR0LogLoggerExFallbackOutput(void *pv, const char *pachChars, size_t cbChars)
{
if (cbChars)
{
for (;;)
{
/* how much */
uint32_t cb = sizeof(pThis->achScratch) - pThis->offScratch - 1; /* minus 1 - for the string terminator. */
/* copy */
/* advance */
/* done? */
if (cbChars <= 0)
return cbRet;
/* flush */
}
/* won't ever get here! */
}
else
{
/*
* Termination call, flush the log.
*/
return 0;
}
}
/**
* Ring-0 fallback for cases where we're unable to grab the lock.
*
* This will happen when we're at a too high IRQL on Windows for instance and
* needs to be dealt with or we'll drop a lot of log output. This fallback will
* only output to some of the log destinations as a few of them may be doing
* dangerous things. We won't be doing any prefixing here either, at least not
* for the present, because it's too much hassle.
*
* @param fDestFlags The destination flags.
* @param fFlags The logger flags.
* @param pszFormat The format string.
* @param va The format arguments.
*/
static void rtR0LogLoggerExFallback(uint32_t fDestFlags, uint32_t fFlags, const char *pszFormat, va_list va)
{
/* fallback indicator. */
/* selected prefixes */
if (fFlags & RTLOGFLAGS_PREFIX_PID)
{
This.offScratch += RTStrFormatNumber(&This.achScratch[This.offScratch], Process, 16, sizeof(RTPROCESS) * 2, 0, RTSTR_F_ZEROPAD);
}
if (fFlags & RTLOGFLAGS_PREFIX_TID)
{
This.offScratch += RTStrFormatNumber(&This.achScratch[This.offScratch], Thread, 16, sizeof(RTNATIVETHREAD) * 2, 0, RTSTR_F_ZEROPAD);
}
}
#endif /* IN_RING0 */
/**
* vprintf like function for writing to the default log.
*
* @param pszFormat Printf like format string.
* @param args Optional arguments as specified in pszFormat.
*
* @remark The API doesn't support formatting of floating point numbers at the moment.
*/
{
}
#ifdef IN_RING3
/**
*
* @param pLogger The logger instance to update. NULL is not allowed!
* @param pszErrorMsg A buffer which is filled with an error message if
* something fails. May be NULL.
* @param cchErrorMsg The size of the error message buffer.
*/
{
else
if (RT_FAILURE(rc))
{
if (pszErrorMsg)
RTStrPrintf(pszErrorMsg, cchErrorMsg, N_("could not open file '%s' (fOpen=%#x)"), pLogger->pInt->szFilename, fOpen);
}
else
{
if (RT_FAILURE(rc))
{
/* Don't complain if this fails, assume the file is empty. */
rc = VINF_SUCCESS;
}
}
return rc;
}
/**
* Closes, rotates and opens the log files if necessary.
*
* Used by the rtlogFlush() function as well as RTLogCreateExV.
*
* @param pLogger The logger instance to update. NULL is not allowed!
* @param uTimeSlit Current time slot (for tikme based rotation).
* @param fFirst Flag whether this is the beginning of logging, i.e.
* called from RTLogCreateExV. Prevents pfnPhase from
* being called.
*/
{
/* Suppress rotating empty log files simply because the time elapsed. */
/* Check rotation condition: file still small enough and not too old? */
return;
/*
* Save "disabled" log flag and make sure logging is disabled.
* The logging in the functions called during log file history
* rotation would cause severe trouble otherwise.
*/
/*
* Disable log rotation temporarily, otherwise with extreme settings and
* chatty phase logging we could run into endless rotation.
*/
/*
* Close the old log file.
*/
{
/* Use the callback to generate some final log contents, but only if
* this is a rotation with a fully set up logger. Leave the other case
* to the RTLogCreateExV function. */
{
}
}
if (cSavedHistory)
{
/*
* Rotate the log files.
*/
{
if (i > 0)
else
}
/*
* Delete excess log files.
*/
{
if (RT_FAILURE(rc))
break;
}
}
/*
* Update logger state and create new log file.
*/
/*
* Use the callback to generate some initial log contents, but only if this
* is a rotation with a fully set up logger. Leave the other case to the
* RTLogCreateExV function.
*/
{
}
/* Restore saved values. */
}
#endif /* IN_RING3 */
/**
* Writes the buffer to the given log device without checking for buffered
* data or anything.
* Used by the RTLogFlush() function.
*
* @param pLogger The logger instance to write to. NULL is not allowed!
*/
{
if (cchScratch == 0)
return; /* nothing to flush. */
/* Make sure the string is terminated. On Windows, RTLogWriteDebugger
will get upset if it isn't. */
else
AssertFailed();
#ifndef IN_RC
# ifdef IN_RING3
{
{
}
}
# endif
# endif
#endif /* !IN_RC */
#ifdef IN_RC
#else
#endif
/* empty the buffer. */
pLogger->offScratch = 0;
#ifdef IN_RING3
/*
* Rotate the log file if configured. Must be done after everything is
* and footer messages.
*/
rtlogRotate(pLogger, RTTimeProgramSecTS() / pLogger->pInt->cSecsHistoryTimeSlot, false /* fFirst */);
#endif
}
/**
* Callback for RTLogFormatV which writes to the com port.
* See PFNLOGOUTPUT() for details.
*/
{
if (cbChars)
{
for (;;)
{
/* sanity */
{
}
#endif
/* how much */
/* copy */
/* advance */
/* done? */
if (cbChars <= 0)
return cbRet;
/* flush */
}
/* won't ever get here! */
}
else
{
/*
* Termination call.
* There's always space for a terminator, and it's not counted.
*/
return 0;
}
}
/**
* stpncpy implementation for use in rtLogOutputPrefixed w/ padding.
*
* @returns Pointer to the destination buffer byte following the copied string.
* @param pszDst The destination buffer.
* @param pszSrc The source string.
* @param cchSrcMax The maximum number of characters to copy from
* the string.
* @param cchMinWidth The minimum field with, padd with spaces to
* reach this.
*/
DECLINLINE(char *) rtLogStPNCpyPad(char *pszDst, const char *pszSrc, size_t cchSrcMax, size_t cchMinWidth)
{
if (pszSrc)
{
}
do
*pszDst++ = ' ';
while (cchSrc++ < cchMinWidth);
return pszDst;
}
/**
* Callback for RTLogFormatV which writes to the logger instance.
* This version supports prefixes.
*
* See PFNLOGOUTPUT() for details.
*/
{
if (cbChars)
{
for (;;)
{
const char *pszNewLine;
char *psz;
#ifdef IN_RC
#else
#endif
/*
* Pending prefix?
*/
if (*pfPendingPrefix)
{
*pfPendingPrefix = false;
/* sanity */
{
}
#endif
/*
* Flush the buffer if there isn't enough room for the maximum prefix config.
* Max is 256, add a couple of extra bytes. See CCH_PREFIX check way below.
*/
{
}
/*
* Write the prefixes.
* psz is pointing to the current position.
*/
{
int iBase = 16;
unsigned int fFlags = RTSTR_F_ZEROPAD;
{
iBase = 10;
fFlags = 0;
}
{
static volatile uint64_t s_u64LastTs;
s_u64LastTs = u64;
/* We could have been preempted just before reading of s_u64LastTs by
* another thread which wrote s_u64LastTs. In that case the difference
* is negative which we simply ignore. */
}
/* 1E15 nanoseconds = 11 days */
*psz++ = ' ';
}
#define CCH_PREFIX_01 0 + 17
{
#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
#else
#endif
int iBase = 16;
unsigned int fFlags = RTSTR_F_ZEROPAD;
{
iBase = 10;
fFlags = 0;
}
{
static volatile uint64_t s_u64LastTsc;
s_u64LastTsc = u64;
/* We could have been preempted just before reading of s_u64LastTsc by
* another thread which wrote s_u64LastTsc. In that case the difference
* is negative which we simply ignore. */
}
/* 1E15 ticks at 4GHz = 69 hours */
*psz++ = ' ';
}
{
#else
#endif
/* 1E8 milliseconds = 27 hours */
*psz++ = ' ';
}
{
*psz++ = ':';
*psz++ = ':';
*psz++ = '.';
*psz++ = ' ';
#else
psz += 16;
#endif
}
{
*psz++ = ':';
*psz++ = ':';
u32 %= RT_US_1MIN;
*psz++ = '.';
*psz++ = ' ';
#else
psz += 16;
#endif
}
# if 0
{
char szDate[32];
*psz++ = ' ';
}
# else
# define CCH_PREFIX_06 CCH_PREFIX_05 + 0
# endif
{
#ifndef IN_RC
#else
#endif
*psz++ = ' ';
}
{
#ifndef IN_RC
#else
#endif
*psz++ = ' ';
}
{
#ifdef IN_RING3
const char *pszName = RTThreadSelfName();
const char *pszName = "EMT-RC";
#else
const char *pszName = "R0";
#endif
}
{
#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
#else
#endif
*psz++ = ' ';
}
#ifndef IN_RC
{
}
#endif
{
#ifdef IN_RING3 /** @todo implement these counters in ring-0 too? */
if (Thread != NIL_RTTHREAD)
{
*psz++ = '/';
}
else
#endif
{
*psz++ = '?';
*psz++ = '/';
*psz++ = '?';
}
*psz++ = ' ';
}
{
*psz++ = ' ';
}
{
#ifdef IN_RING3
#else
#endif
}
{
{
*psz++ = ' ';
}
else
{
} /* +9 */
}
{
const char *pszGroup;
{
/* personal groups */
}
}
#define CCH_PREFIX ( CCH_PREFIX_16 )
/*
* Done, figure what we've used and advance the buffer and free size.
*/
}
else if (cb <= 0)
{
}
/* sanity */
{
}
#endif
/* how much */
/* have newline? */
if (pszNewLine)
{
else
{
*pfPendingPrefix = true;
}
}
/* copy */
/* advance */
if ( pszNewLine
{
cbRet++;
cbChars--;
cb++;
*pfPendingPrefix = true;
}
/* done? */
if (cbChars <= 0)
return cbRet;
}
/* won't ever get here! */
}
else
{
/*
* Termination call.
* There's always space for a terminator, and it's not counted.
*/
return 0;
}
}
/**
* Write to a logger instance (worker function).
*
* This function will check whether the instance, group and flags makes up a
* logging kind which is currently enabled before writing anything to the log.
*
* @param pLogger Pointer to logger instance. Must be non-NULL.
* @param fFlags The logging flags.
* @param iGroup The group.
* The value ~0U is reserved for compatibility with RTLogLogger[V] and is
* only for internal usage!
* @param pszFormat Format string.
* @param args Format arguments.
*/
static void rtlogLoggerExVLocked(PRTLOGGER pLogger, unsigned fFlags, unsigned iGroup, const char *pszFormat, va_list args)
{
/*
* Format the message and perhaps flush it.
*/
{
}
else
&& pLogger->offScratch)
}
#ifndef IN_RC
/**
* For calling rtlogLoggerExVLocked.
*
* @param pLogger The logger.
* @param fFlags The logging flags.
* @param iGroup The group.
* The value ~0U is reserved for compatibility with RTLogLogger[V] and is
* only for internal usage!
* @param pszFormat Format string.
* @param ... Format arguments.
*/
static void rtlogLoggerExFLocked(PRTLOGGER pLogger, unsigned fFlags, unsigned iGroup, const char *pszFormat, ...)
{
}
#endif /* !IN_RC */