/*
* Copyright 1995, 1996 Perforce Software. All rights reserved.
*/
/*
* ErrorLog.cc - report layered errors
*/
# define NEED_SYSLOG
# define NEED_FLOCK
# define NEED_DBGBREAK
# define NEED_CRITSEC
# include <stdhdrs.h>
# include <strbuf.h>
# include <error.h>
# include <errorlog.h>
# include <fdutil.h>
# include <filesys.h>
/*
* ErrorLog::errorLog - file where errors are written, type punned
* ErrorLog::errorTag - string attached to reported errors
*/
Error AssertError;
ErrorLog AssertLog;
ErrorLog::ErrorLog( ErrorLog *from )
: hook(NULL), context(NULL)
{
errorFsys = 0;
errorTag = from->errorTag;
logType = from->logType;
// clone errorFsys
if( from->errorFsys && from->logType == type_none )
{
errorFsys = FileSys::Create( FST_ATEXT );
errorFsys->Set( from->errorFsys->Name() );
errorFsys->Perms( FPM_RW );
}
// We will not inherit the critical section. We also do not create
// a new one, as only AssertLog will be using a critical section.
vp_critsec = 0;
}
ErrorLog::~ErrorLog()
{
delete errorFsys;
errorFsys = 0;
# ifdef HAVE_CRITSEC
if( vp_critsec )
{
CRITICAL_SECTION *critsec;
critsec = (CRITICAL_SECTION *)vp_critsec;
DeleteCriticalSection( critsec );
delete critsec;
vp_critsec = 0;
}
# endif
}
/*
* Error::SysLog() - OS style System Logger entry point.
*/
void
ErrorLog::SysLog( const Error *e, int tagged, const char *et, const char *buf )
{
const char *errTag = errorTag;
if( !errorTag )
init();
if( et )
errTag = et;
# ifdef HAVE_SYSLOG
// Default to LOG_DEBUG to maintain behavior from LogWrite.
int level = LOG_DEBUG;
// If severity defined, determine level accordingly.
if( e )
{
if( e->GetSeverity() != E_FATAL )
level = LOG_WARNING;
else
level = LOG_ERR;
}
/* char * cast for Lynx 4.0 */
openlog( (char *)errTag, LOG_PID, LOG_DAEMON );
if( tagged )
syslog( level, "%s: %s", e->FmtSeverity(), buf );
else
syslog( LOG_WARNING, "%s", buf );
closelog();
# endif
# ifdef HAVE_EVENT_LOG
// Log a warning unless exiting, then log an error.
// (can exit on E_FAILED, through ::Abort)
WORD level = EVENTLOG_INFORMATION_TYPE;
if( e )
{
if( e->GetSeverity() != E_FATAL )
level = EVENTLOG_WARNING_TYPE;
else
level = EVENTLOG_ERROR_TYPE;
}
// Log the event to the Windows Application Event log.
// We don't configure our event IDs in the registry or have
// an event message resource. Windows will preface our entry
// with a silly warning message.
HANDLE hEventSource =
RegisterEventSource( NULL, errTag );
LPTSTR lpszStrings[1] = { (LPTSTR)buf };
if( hEventSource != NULL )
{
ReportEvent(
hEventSource, // handle of event source
level, // event type
0, // event category, (doesn't matter)
0, // event ID
NULL, // current user's SID, (let it default)
1, // strings in lpszStrings
0, // no bytes of raw data
(LPCTSTR *)lpszStrings, // array of strings
NULL ); // no raw data
DeregisterEventSource(hEventSource);
}
# endif // HAVE_EVENT_LOG
return;
}
void
ErrorLog::init()
{
logType = type_stderr;
errorTag = "Error";
errorFsys = 0;
vp_critsec = 0;
}
void
ErrorLog::EnableCritSec()
{
# ifdef HAVE_CRITSEC
if( vp_critsec == NULL )
{
CRITICAL_SECTION *critsec;
critsec = new CRITICAL_SECTION;
InitializeCriticalSection( critsec );
vp_critsec = (void *)critsec;
}
# endif
}
void
ErrorLog::Report( const Error *e, int reportFlags )
{
if( e->GetSeverity() == E_EMPTY )
return;
int tagged = reportFlags & REPORT_TAGGED;
int hooked = reportFlags & REPORT_HOOKED;
if( !errorTag )
init();
StrBuf buf;
e->Fmt( buf, tagged ? EF_INDENT | EF_NEWLINE : EF_NEWLINE );
# if defined( HAVE_SYSLOG ) || defined( HAVE_EVENT_LOG )
if ( logType == type_syslog )
{
SysLog( e, tagged, NULL, buf.Text() );
return;
}
# endif
if( tagged )
{
StrBuf out;
out.Set( errorTag );
out.Extend( ' ' );
out.Append( e->FmtSeverity() );
out.Extend( ':' );
out.Extend( '\n' );
out.Append( &buf );
LogWrite( out );
}
else
LogWrite( buf );
if( hook && hooked )
(*hook)( context, e );
}
void
ErrorLog::LogWrite( const StrPtr &s )
{
# if defined( HAVE_SYSLOG ) || defined( HAVE_EVENT_LOG )
if ( logType == type_syslog )
{
// Pass e=NULL and tagged=0 to maintain previous bahavior,
// see SysLog for details.
SysLog( NULL, 0, NULL, s.Text() );
return;
}
# endif
if( errorFsys )
{
Error tmpe;
# ifdef HAVE_CRITSEC
if( vp_critsec )
{
CRITICAL_SECTION *critsec;
critsec = (CRITICAL_SECTION *)vp_critsec;
EnterCriticalSection( critsec );
}
# endif
errorFsys->Open( FOM_WRITE, &tmpe );
if( !tmpe.Test() )
{
errorFsys->Write( &s, &tmpe );
errorFsys->Close( &tmpe );
}
if( tmpe.Test() )
{
// An error was encountered when
// attempting to write to the log.
# if defined( HAVE_SYSLOG ) || defined( HAVE_EVENT_LOG )
// Write to syslog or the event log the original
// message that was to be written to the log.
SysLog( NULL, 0, NULL, s.Text() );
// Write to syslog or the event log the error that was
// encountered when attempting to write to the log.
StrBuf buf;
tmpe.Fmt( &buf );
SysLog( &tmpe, 1, NULL, buf.Text() );
# endif
# ifndef OS_NT
// Write to stderr the error that was encountered when
// attempting to write to the log. stderr should be
// well-defined on platforms other than Windows.
ErrorLog tmpeel;
tmpeel.SetTag( errorTag );
tmpeel.Report( &tmpe );
# endif
}
# ifdef HAVE_CRITSEC
if( vp_critsec )
{
CRITICAL_SECTION *critsec;
critsec = (CRITICAL_SECTION *)vp_critsec;
LeaveCriticalSection( critsec );
}
# endif
return;
}
// Under a Windows Service, stderr is not valid. Observation
// has shown that fileno(stderr) returns -2. This is not detected
// as an invalid file descriptor by the Posix checks. I suppose
// MS did this intentionally, just not sure why.
//
// Unix logType defaults to type_stderr.
// Windows Services must call UnsetLogType().
//
if ( logType == type_stdout || logType == type_stderr )
{
FILE *flog=stderr;
if( logType == type_stdout )
flog = stdout;
// lock the file exclusive for this append, some platforms
// don't do append correctly (you know who you are!)
int fd = fileno( flog );
lockFile( fd, LOCKF_EX );
fputs( s.Text(), flog );
/* Flush even if stderr, for NT's buffered stderr. */
fflush( flog );
lockFile( fd, LOCKF_UN );
}
}
/*
* Error::Abort() - blurt out an error and exit
*/
void
ErrorLog::Abort( const Error *e )
{
if( !e->Test() )
return;
Report( e );
# ifdef HAVE_DBGBREAK
if( IsDebuggerPresent() )
{
char msg[128];
// Notice p4diag this is a Process abort event.
sprintf (msg, "event: Process Abort");
OutputDebugString(msg);
// Under p4diag, create a strack trace and mini dump.
DebugBreak();
}
# endif // HAVE_DBGBREAK
exit(-1);
}
/*
* ErrorLog::SetLog() - redirect Abort() and Report() to named file
*/
void
ErrorLog::SetLog( const char *file )
{
// Redirect to syslog, stdout or stderr?
if( !strcmp( file, "syslog" ) )
{
logType = type_syslog;
return;
}
else if( !strcmp( file, "stdout" ) )
{
logType = type_stdout;
return;
}
else if( !strcmp( file, "stderr" ) )
{
logType = type_stderr;
return;
}
FileSys *fs = FileSys::Create( FST_ATEXT );
Error e;
fs->Set( file );
fs->Perms( FPM_RW );
fs->MkDir( &e );
if( !e.Test() )
fs->Open( FOM_WRITE, &e );
if( e.Test() )
AssertLog.Report( &e );
else
UnsetLogType();
fs->Close( &e );
if( errorFsys )
delete errorFsys;
errorFsys = fs;
}
void
ErrorLog::Rename( const char *file, Error *e )
{
FileSys *fs = FileSys::Create( FST_ATEXT );
fs->Set( file );
errorFsys->Rename( fs, e );
delete fs;
}
offL_t
ErrorLog::Size()
{
if( !errorFsys )
return 0;
Error e;
errorFsys->Open( FOM_READ, &e );
if( e.Test() )
return 0;
offL_t size = errorFsys->GetSize();
errorFsys->Close( &e );
return size;
}
const char *
ErrorLog::Name()
{
if( !errorFsys )
return 0;
return errorFsys->Name();
}