2025-04-27 07:49:33 -04:00

734 lines
24 KiB
C

/*++
EZLOG.H
Logging abstraction API rebuilt from the ground up. This should be
considered a replacement for NTLOG.
Copyright (C) 1997 Microsoft Corporation, all rights reserved
Created, 11/08/1997 by DavidCHR
--*/
#ifndef __INC_EZLOG_H__
#define __INC_EZLOG_H__ 1
#ifdef __cplusplus
#define CPPONLY(x) x
#define EZ_EXTERN_C extern "C"
#else
#define CPPONLY(x)
#define EZ_EXTERN_C
#endif
/* All ezlog APIs are extern "C" and __cdecl. */
#define EZLOGAPI EZ_EXTERN_C BOOL __cdecl
/* LOGGING LEVELS (feel free to define your own-- see ezOpenLog):
These we keep manually in sync with NTLOG.H, in case someone decides
to use this system as a drop-in replacement for NTLOG.
They are listed in order of precedence-- if a test logs a block,
it takes precedence over a PASS or a SEV[x] */
// NTLOG-eq. prec. flags
#define EZLOG_ABORT 0x00000001L // TLS_ABORT 500 TE_ABORT
#define EZLOG_BLOCK 0x00000400L // TLS_BLOCK 400 Attempted
#define EZLOG_SEV1 0x00000002L // TLS_SEV1 300 Attempted
#define EZLOG_SEV2 0x00000004L // TLS_SEV2 200 Attempted
#define EZLOG_SEV3 0x00000008L // TLS_SEV3 100 Attempted
#define EZLOG_WARN 0x00000010L // TLS_WARN 50 successful, TE_WARN
#define EZLOG_PASS 0x00000020L // TLS_PASS 10 successful, DEFAULT
#define EZLOG_SKIPPED 0x00004000L // 1 SKIPPED
#define EZLOG_INFO 0x00002000L // TLS_INFO -- nonresultant
#define EZLOG_DEBUG 0x00001000L // TLS_TESTDEBUG -- nonresultant
// ezOpenLog flags:
#define EZLOG_OPT_APPENDLOG 0x001 /* Append to an existing log--
(create if it doesn't exist) */
#define EZLOG_OPT_KEEP_TRACK 0x002 /* keep track of block names and
results. If you don't specify this,
the logging data will just be sent
to the log file.
It should be noted that this will
cause memory to be allocated for each
block for which we are keeping track,
so if you have thousands and thousands
of blocks with really really long names,
use of this flag should be discouraged.
*/
#define EZLOG_OUTPUT_STDOUT 0x004 // send output additionally to stdout
#define EZLOG_OUTPUT_STDERR 0x008 // as above, but to stderr (mixable)
#define EZLOG_USE_MY_LEVELS 0x010 /* use specified levels in whatever
order: [levelnumber] [levelname]
[precedence] [flags] --
terminate with zero levelnumber. */
// the logging level flags are (so far):
#define EZLOG_LFLAG_DEBUGBREAK 0x001 // DebugBreak() on invocation
#define EZLOG_LFLAG_RED 0x002 // print logging messages in this color
#define EZLOG_LFLAG_YELLOW 0x004 // print logging messages in this color
#define EZLOG_LFLAG_BLUE 0x008 // print logging messages in this color
#define EZLOG_LFLAG_GREEN 0x010 // print logging messages in this color
/* these loglevel flags deal with the way managers want regressions returned:
if the logging levels passed to ezOpenLog include at least one invocation
of these, ezCloseLog will construct a table of %passed/%attempted
variations. This table is built according to a variation's output
logging level-- a failure would have EZLOG_LFLAG_ATTEMPTED only,
a pass would have ATTEMPTED | SUCCESSFUL, and a BLOCK wouldn't have
either one. */
#define EZLOG_LFLAG_ATTEMPTED 0x020
#define EZLOG_LFLAG_SUCCESSFUL 0x040
#define EZLOG_LFLAG_SKIPPED 0x080 /* variations logged with this logging
level are ignored for the purposes
of computing percentages for other
logging levels (e.g.: */
/* for a table with three passes, one failure and two "skipped" blocks,
we'd see:
Pass: 3/4 (75%)
Failure: 1/4 (25%)
Skipped: 2/6 (33%) NOTE: these were not considered in other results.
If there were multiple logging level types with "skipped" flags, they'd
look something like this (although I doubt anyone will need this):
Pass: 3/4 (75%)
Failure: 1/4 (25%)
OneSkip: 2/8 (25%) NOTE: not considered part of the regression
TwoSkip: 2/8 (25%) NOTE: not considered part of the regression
*/
#define EZLOG_LFLAG_NONRESULTANT 0x100 /* Use this to indicate that receipt
of a log event at this level does
not affect the outcome of a test
block. the NTLOG "SYSTEM", "INFO",
and "DEBUG" levels are examples
of this.
note that precedence is ignored
for nonresultant levels.
If you're wondering what the point
of this flag is, then don't worry
about it ;-) */
/* the NOT_CONSOLE / NOT_LOGFILE flags are useful for logging events that
are VERY prolific (and not useful in case a test fails, for instance,
so you wouldn't want them on the console), or events that give the test
status (so you wouldn't need them in the log file). */
#define EZLOG_LFLAG_NOT_CONSOLE 0x200 /* do not send this logging event to
the console. */
#define EZLOG_LFLAG_NOT_LOGFILE 0x400 /* do not send it to the logfile */
#define EZLOG_LFLAG_REPORT_INVOCATIONS 0x800 /* if this flag is specified,
invocations of this logging
level will be reported on
even if no variation actually
has it as a result
(normally, only variation
resultant logging levels
are printed)
This makes the most sense
when combined with LFLAG_
NONRESULTANT. Otherwise,
the output may be confusing. */
#define EZLOG_LFLAG_TE_WARN 0x1000 /* For Test-Enterprise. This specifies
that the given logging level should
be treated as a warning for TE
purposes.
If this level is combined with
LFLAG_ATTEMPTED or LFLAG_SUCCESSFUL,
TE_WARN takes precedence, since TE
doesn't recognize successful warnings.*/
#define EZLOG_LFLAG_TE_ABORT 0x2000 /* Also for TE. This specifies that
the given logging level should be
treated as an ABORT for TE purposes.
See the TE documentation for what
this means.
If this level is combined with
LFLAG_ATTEMPTED or LFLAG_SUCCESSFUL,
TE_ABORT takes precedence, since TE
doesn't recognize ABORT in conjunction
with anything else. */
#define EZLOG_EXIT_ON_FAIL 0x020 /* exit() on failure of any base logging
function (except ezCloseLog if you
specify EZCLOSELOG_FAIL_IF... flags).
Since most of the functions can only
fail when there isn't enough memory or
similar critical error, most tests
will probably want this.
EXCEPTION: don't do this for stress
programs, for obvious reasons. */
#define EZLOG_NO_DEFAULT_LEVELS 0x040 /* ignore default levels--
this is useless by itself */
#define EZLOG_USE_ONLY_MY_LEVELS 0x050 /* ignore the default logging level
definitions and use only the ones
specified on the argument list. */
#define EZLOG_GIVE_ME_A_HANDLE 0x080 /* return a handle to the log, rather
than setting the default log--
most clients will want this */
#define EZLOG_OPENCLOSE_FILES 0x100 /* leave file descriptors closed
when they're not in use. Open
them for the duration of the
logging attempt, then close them
afterwards.
This is very slow, so don't use
it unless you're worried that
your tests can crash the system */
#define EZLOG_INCREMENT_FILENAME 0x200 /* if the filename exists, increment
it-- if the filename is "ezlog.log"
then the log will try:
ezlog.log.1
ezlog.log.2
...
ezlog.log.99999...
until it gets a unique filename or
gets an error other than that the
file already exists. */
#define EZLOG_USE_INDENTATION 0x400 /* causes messages in blocks
to be indented one space from
the block start/end:
block 1
block 1.1
messages for 1.1
block 1.1.1
messages for 1.1.1
end 1.1.1
block 1.1.2
end 1.1.2
end 1.1
end 1
*/
#define EZLOG_REPORT_BLOCKCLOSE 0x800 /* stylistic determination--
causes the ENDS of blocks to
be logged as well as the beginning.
e.g:
Starting MyBlockName
MyBlockName completed
*/
#define EZLOG_DISTINGUISH_ENDPOINTS 0x1000 /* prints newlines or dashes
(or something similar, but
probably newlines) before
and after block-opens
(and block-closes if
EZLOG_REPORT_BLOCKCLOSE
is specified).
This keeps people like
RuiM from having to manually
insert them. */
#define EZLOG_LEVELS_ARE_MASKABLE 0x2000 /* if this is set, then
the logging api assume
that each bit in a logging
mask is unique-- this
is the default unless you
use EZLOG_USE_MY_LEVELS.
unique/discrete masks
are all nonrepeated
single-bit values.
This isn't yet supported.
See the example below */
#define EZLOG_LEVELS_ARE_DISCRETE_MASKS EZLOG_LEVELS_ARE_MASKABLE
/* "Discrete" has different meanings
to different people.
So use EZLOG_LEVELS_ARE_MASKABLE
instead. */
#if 0 // EXAMPLE:
/* These levels are nondiscrete because MY_FAIL has more than one bit
in its mask (0x1 and 0x2). It also conflicts with both MY_PASS and
MY_WARN. */
#define MY_PASS 1 // 0001
#define MY_WARN 2 // 0010
#define MY_FAIL 3 // 0011
/* These levels ARE discrete masks, because no mask contains bits from
any other. */
#define MY_PASS 0x1 // 0001 <-- only one bit is set
#define MY_WARN 0x2 // 0010 <-- only one bit is set
#define MY_FAIL 0x4 // 0100 <-- only one bit is set
#endif
#define EZLOG_FORMAT_BUG_NUMBERS 0x4000 /* adds a string to format the
given bug number as an additional
line. This should be a printf
format string-- the bug number
is a ULONG, so %ld will work
to replace it. The default is
just "%ld". For convenience,
we insert the following constant
for NT bugs: */
/* We define the following well-known bug strings that can change
over time-- the idea is that rather than having to recompile your
code, all you have to do is relink (or grab a new DLL) when the
URL changes. */
#define EZLOG_WELL_KNOWN_NTBUG_STRING 0x1
#define NTBUG_FMT_STRINGA ((LPSTR) EZLOG_WELL_KNOWN_NTBUG_STRING )
#define NTBUG_FMT_STRINGW ((LPWSTR) EZLOG_WELL_KNOWN_NTBUG_STRING )
#define EZLOG_LOG_EVERYTHING 0x8000 /* by default, unknown logging levels
(other than zero) are ignored.
If you specify this flag, ALL levels
will be logged, although unknown
levels will not be saved as block
results. */
#define EZLOG_OUTPUT_RESULTS 0x10000 /* causes the current block result
to be printed with the logging
output:
without:
compat.c 4 passing-message
compat.c 22 failing-message
compat.c 29 another failing-message
with either this:
compat.c 4 PASS -> PASS passing-message
compat.c 22 PASS -> SEV2 failing-message
compat.c 29 SEV2 -> SEV2 another failing-message
or this: (I haven't decided which yet)
compat.c 4 PASS passing-message
compat.c 22 SEV2 failing-message
compat.c 29 SEV2 another failing-message
This is on by default when EZLOG_USE_MY_LEVELS is NOT specified and when
using the NTLOG compatibility APIs. The reasoning behind this is that
the default logging levels and the NTLOG logging levels are of a fairly
tame length, and they're justified based on the maximum string-length.
e.g. if you add a logging level named "REALLYLONG", the
above messages would look like this:
compat.c 4 PASS -> PASS passing-message
compat.c 22 PASS -> SEV2 failing-message
compat.c 29 SEV2 -> SEV2 another failing-message
... because REALLYLONG is so much longer than PASS and SEV2.
*/
#define EZLOG_NO_FILE_OUTPUT 0x20000 /* Do not send ANY output to the
logfile. This is the equivalent
of specifying LFLAG_NOT_LOGFILE
to every logging level--
this is useful for test apps that
have a large base of logging apis
that need to be modified for, say,
stress operation. */
#define EZLOG_NO_CONSOLE_OUTPUT 0x40000 /* just like NO_FILE_OUTPUT, but
for the console. This is like
specifying LFLAG_NOT_CONSOLE
to every logging level. */
#define EZLOG_BLOCKNAME_IN_MSGS 0x80000 /* include the current blockname
in any logging messages.
This is useful for tests that
perform multiple variations
simultaneously, to distinguish
messages intended for one
variation from messages intended
for another variation.
This flag is useless without
EZLOG_KEEP_TRACK. */
#define EZLOG_DONT_TRUNCATE_FILENAMES 0x100000 /* WITHOUT this switch,
ezlog will collapse
pathnames to filenames:
foo\bar\baz.c --> baz.c
WITH this switch,
ezlog will print
foo\bar\baz.c.
This is only desireable if
you have lots and lots
of identical filenames. */
#define EZLOG_ADD_THREAD_ID 0x00200000 /* Add the current threadId to the
beginning of each logging message */
#define EZLOG_ADD_TIMESTAMP 0x00400000 /* add the current time to the
beginning of each logging message*/
#define EZLOG_NO_IMPLIED_NEWLINES 0x00800000 /* allows the caller to control
newlines in functions. This
is very simplistic.
If this flag is on, then we
don't add newlines to every
logmsg. Furthermore, if the
format string does not end
explicitly with a newline,
then we do not decorate further
logging messages (assuming
they don't pass file and line)
until a newline-terminated
message is received.
If this flag is off, then
all messages are decorated
and automatically newline-
terminated if they aren't
already. */
#define EZLOG_COUNT_ALL_RESULTANT_INVOCATIONS 0x01000000
/* This is for users who want to use the invocation
counting system, but don't want to modify the default
levels. In effect, it adds LFLAG_REPORT_INVOCATIONS to
every RESULTANT loglevel. */
#define EZLOG_TRACK_MESSAGE_RESULTS 0x02000000 /* If this flag is specified,
ezlog will save the result-
setting message for all
blocks. This has no
effect for nonresultant
invocations. */
#define EZLOG_CONSOLE_IS_RAW 0x04000000 /* If this flag is specified,
ezlog will only send the
data that the caller passed
in to the console (only
useful if OUTPUT_STDOUT or
OUTPUT_STDERR is supplied).
All other information that
ezlog adds (timestamp, thread
id, loglevel, file/line) will
only be sent to the logfile*/
/*------------------------------------------------------------
EZLOG_FAILURE_CALLBACK_ARG/FUNCTION:
This function can be passed in with an EZOPENLOG_DATA version 3
or higher. It lets applications do things if ezlog encounters
something it can't handle, like an inability to open the logfile,
memory allocation failure, etc.
This is an extension of the old EZLOG_EXIT_ON_FAIL (above), because
it gives applications the ability to terminate on their own, after
possibly cleaning up any runtime cruft. */
#ifndef EZLOG_FAILURE_CALLBACK_ARG_VERSION
#define EZLOG_FAILURE_CALLBACK_ARG_VERSION 1
#endif
#ifndef __INC_EZMSG_H__
typedef ULONG EZLOG_MSGID, *PEZLOG_MSGID;
#endif
typedef struct {
IN ULONG Version; /* ezLog will set this to whatever
version is current. Fields will
only be ADDED to the structure, so
if the version is in the future,
ignore the extraneous fields. */
IN EZLOG_MSGID MessageId; /* What problem caused us to call this
function. See ezmsg.h for a list. */
} EZLOG_FAILURE_CALLBACK_ARGS, *PEZLOG_FAILURE_CALLBACK_ARGS;
typedef EZLOG_FAILURE_CALLBACK_FUNCTION( IN PEZLOG_FAILURE_CALLBACK_ARGS );
typedef EZLOG_FAILURE_CALLBACK_FUNCTION *PEZLOG_FAILURE_CALLBACK_FUNCTION;
/* Declare the function data requirements. This is necessary because
resdll.h and ezdef.h have circular dependencies. */
struct __ezreport_function_data; // forward. fleshed out in resdll.h below
typedef VOID (__ezlog_report_function)( IN struct __ezreport_function_data *);
typedef __ezlog_report_function EZLOG_REPORT_FUNCTION, *PEZLOG_REPORT_FUNCTION;
/* ------------------------------------------------------------ */
/* Here, we doubly-include ezdef.h, which includes everything that
is unicode/ansi sensitive. We control the means by which
the ansi and unicode data are selected by defining
LPEZSTR and EZU( x ). If you are unfamiliar with how
the C Preprocessor works, just translate LPEZSTR to LPTSTR
and assume that EZU( foo ) == foo, and you'll be okay.
This keeps me from having to doubly-define the whole mess. */
#define LPEZSTR LPSTR
#define EZU( x ) x ## A
#include ".\ezdef.h"
#undef LPEZSTR
#undef EZU
#define LPEZSTR LPWSTR
#define EZU( x ) x ## W
#include ".\ezdef.h"
#undef LPEZSTR
#undef EZU
#include ".\resdll.h"
EZLOGAPI
ezRecordBug( IN HANDLE hLog,
IN ULONG ulBugIndex );
// ezCloseLog flags:
#if 0 // not actually supported.
#define EZCLOSELOG_FAIL_IF_RESULT_EXISTS 0x001 /* return FALSE if anything in
the next mask was returned.
(the next argument should
then be a mask comprised of
EZLOG_BLOCK | ... type
flags, OR user-defined ones.
*/
#define EZCLOSELOG_PASS_IF_RESULT_EXISTS 0x002 /* opposite of 0x001. return
FALSE if any value NOT
matching the next argument
was ever logged. */
#endif
#define EZCLOSELOG_CLOSE_HANDLE 0x008 /* pass in a handle to the
log to close-- otherwise,
use the default log */
#define EZCLOSELOG_FULL_FILE_REPORT 0x010 /* This overrides the OpenLog
cReportBlockThresh, if
defined, making ezlog
dump the full version
of the block tree regardless
of how long or wide it is.
The idea behind this is to
still produce summary output
in the file, even though it
may not be useful at the
console. */
#define EZCLOSELOG_CALLER_IS_THREADSAFE 0x020 /* the caller can guarantee
that no other operations
are pending on this log
handle, which makes it
safe to delete some runtime
data.
If this is not specified,
we will leave around some
data, specifically a
critical section to ensure
that although further
logging calls won't WORK,
they also won't AV. */
#define EZCLOSELOG_WARN_OF_UNCLOSED_BLOCKS 0x040 /* If this flag is specified,
ezCloseLog will warn the
caller with obnoxious
messages if it encounters
blocks that are unclosed.
Another way of putting it
is that by using this flag,
you're sure you closed all
the block handles, so if
ezLog detects that you
didn't, it'll warn you. */
EZLOGAPI
ezCloseLog( IN OPTIONAL ULONG flags CPPONLY( = 0 ),
/* Result flag */
/* handle */
... );
typedef enum {
ezLevelRes_InvocationCount = 0, // returns a ULONG
ezLevelRes_ResultCount,
ezLevelRes_UNDEFINED // must be last!
} ezLevelResults;
EZLOGAPI
ezGetLevelData( IN HANDLE hLog,
IN ULONG LevelId,
IN ezLevelResults infoType,
IN OUT size_t *psize,
OUT PVOID pvData ); // should be ULONG size.
typedef enum { // this is the information available on a block.
ezBlockRes_Outcome = 0, /* returns a ULONG indicating the current
outcome of the block. */
ezBlockRes_NameA, // LPSTR
ezBlockRes_NameW, // LPWSTR -- not supported under Win95.
ezBlockRes_ParentBlock, // hBlock
ezBlockRes_nChildBlocks, // ULONG -- count of the below blocks
ezBlockRes_ChildBlocks, // vector of hBlocks
ezBlockRes_ParentLog, /* hLog -- useful when using a single
logging structure (e.g. NULL) */
// version 2 adds the following:
ezBlockRes_CurrentBlock, /* useful when using null hBlocks and hLogs.
returns an hBlock. */
#define ezBlockRes_VERSION 2 // update if you add more.
ezBlockRes_UNDEFINED // counter-- MUST BE LAST
} ezBlockResults;
EZLOGAPI
ezGetBlockData( IN HANDLE hBlock, // pvBlockId
IN ezBlockResults infoType,
IN OUT size_t *pStructureSize,
OUT PVOID pvStructureData );
/* Notes on ezParseBlockTree:
if the passed-in function returns FALSE, ezParseBlockTree stops
and returns FALSE immediately. It returns TRUE only on successful
parsing of ALL nodes.
Note that the algorithm for hitting all the nodes may change, so
the subject function must NOT make any assumptions about how and
when it will be called-- only that it will be called on a valid node.
Note that if hStartingBlock is the hLog, the entire tree will be parsed.
*/
typedef BOOL (ezBlockParserFunction)( IN HANDLE, // hBlock
IN PVOID /* pvUserData */ );
typedef ezBlockParserFunction *pezBlockParserFunction;
EZLOGAPI
ezParseBlockTree( IN HANDLE hStartingBlock,
IN pezBlockParserFunction pfParser,
IN PVOID pvUserData );
// Flags to ezStartBlock (see ezdef.h)
#define EZBLOCK_TRACK_SUBBLOCKS 0x001
#define EZBLOCK_OUTCOME_INDEPENDENT 0x002 /* outcome of this block is
independent of the sub-block's
outcome */
EZLOGAPI
ezFinishBlock( IN OPTIONAL HANDLE hBlock );
#define EZ_DEF NULL, TEXT(__FILE__), __LINE__ /* default pvBlockId, file,
and line number for
ezLogMsg */
// backwards compatibility:
#define EZ_DEFAULT NULL, __FILE__, __LINE__ /* default pvBlockId, file,
and line number for ezLogMsg */
#ifdef UNICODE
#define ezLogMsg ezLogMsgW
#define vezLogMsg vezLogMsgW
#define ezOpenLog ezOpenLogW
#define ezStartBlock ezStartBlockW
#define NTBUG_FMT_STRING NTBUG_FMT_STRINGW
#define ezOpenLogEx ezOpenLogExW
#define ezBlockRes_Name ezBlockRes_NameW
typedef EZLOG_OPENLOG_DATAW EZLOG_OPENLOG_DATA, *PEZLOG_OPENLOG_DATA;
typedef EZLOG_LEVEL_INIT_DATAW EZLOG_LEVEL_INIT_DATA, *PEZLOG_LEVEL_INIT_DATA;
#else
#define vezLogMsg vezLogMsgA
#define ezLogMsg ezLogMsgA
#define ezOpenLog ezOpenLogA
#define ezStartBlock ezStartBlockA
#define NTBUG_FMT_STRING NTBUG_FMT_STRINGA
#define ezOpenLogEx ezOpenLogExA
#define ezBlockRes_Name ezBlockRes_NameA
typedef EZLOG_OPENLOG_DATAA EZLOG_OPENLOG_DATA, *PEZLOG_OPENLOG_DATA;
typedef EZLOG_LEVEL_INIT_DATAA EZLOG_LEVEL_INIT_DATA, *PEZLOG_LEVEL_INIT_DATA;
#endif
#undef CPPONLY
#endif // multiple-include protection