FileLogging.c

/*
    File:       FileLogging.c
 
    Contains:   File logging engine for OTStreamLogViewer.
 
    Written by: Quinn "The Eskimo!"
 
    Copyright:  © 1998 by Apple Computer, Inc., all rights reserved.
 
    Change History (most recent first):
 
    You may incorporate this sample code into your applications without
    restriction, though the sample code has been provided "AS IS" and the
    responsibility for its operation is 100% yours.  However, what you are
    not permitted to do is to redistribute the source as "DSC Sample Code"
    after having made changes. If you're going to re-distribute the source,
    we require that you make it clear in the source that the code was
    descended from Apple Sample Code, but that you've made changes.
*/
 
#define qDebug 1
 
/////////////////////////////////////////////////////////////////////
// Standard C stuff.
 
#include <stdio.h>
 
// Pick up toolbox stuff.
 
#include <OpenTransport.h>
#include <Files.h>
#include <TextUtils.h>
#include <MacErrors.h>
#include <Devices.h>
 
// Pick up our resource definitions.
 
#include "StreamLogResources.h"
 
// Pick up our own prototypes.
 
#include "FileLogging.h"
 
/////////////////////////////////////////////////////////////////////
 
// OTDebugStr is defined in a wacky OT library.  Let's just use 
// lowercase debugstr instead.
 
#define OTDebugStr debugstr
 
/////////////////////////////////////////////////////////////////////
 
static OSStatus FSWriteQAtLEOF(SInt16 fileRefNum, SInt32 count, void *buffer)
    // Writes the count bytes at buffer to the end of fileRefNum.
{
    ParamBlockRec pb;
 
    pb.ioParam.ioRefNum = fileRefNum;
    pb.ioParam.ioBuffer = buffer;
    pb.ioParam.ioReqCount = count;
    pb.ioParam.ioPosMode = fsFromLEOF;
    pb.ioParam.ioPosOffset = 0;
    
    return PBWriteSync(&pb);
}
 
/////////////////////////////////////////////////////////////////////
// File Logging Stuff
 
static SInt16 gLogFileRefNum = 0;
    // The file refnum of the log file, or 0 if logging
    // is not active.
 
static char gLogAssemblyBuffer[10240];
    // A temporary buffer used to assemble log text 
    // we're about to write to the file.
 
static UInt32 gTicksOfLastFileWrite;
    // The TickCount time at which we last wrote to the file.
    // If TickCount is more than this value plus
    // kTicksPerFlush, we need to flush the file now.
 
enum {
    kTicksPerFlush = 6,
        // Number of ticks from when we write to a file to when
        // we perform a FlushFile to make sure it gets to the disk.
 
    kLogFileCleanTicks = (UInt32) -(kTicksPerFlush+1)
        // When the file is flushed we set gTicksOfLastFileWrite
        // to this value to prevent further writes from happening.
        // This corresponds to "far in the future". 
};
 
static char kStartFileLogString[] = "Sequence\tFlags\tFlags (as number)\tTime (ms)\tReal Time\tModule ID\tStream ID\tMessage\r";
    // When we start logging, we write this line to the file
    // to give the user some idea of the what each column means.
    
extern OSStatus StartFileLogging(void)
    // See comment in interface part.
{
    OSStatus err;
    Str255 logFileName;
    FSSpec logFile;
    
    OTAssert("StartFileLogging: We're already logging!", gLogFileRefNum == 0);
 
    gTicksOfLastFileWrite = kLogFileCleanTicks;
 
    // Open up the log file, creating it if necessary.
        
    GetIndString(logFileName, rMiscStrings, strLogFileName);
    err = FSMakeFSSpec(-1, fsRtDirID, logFileName, &logFile);
    if (err == fnfErr) {
        err = noErr;
    }
    if (err == noErr) {
        (void) FSpCreate(&logFile, 'R*ch', 'TEXT', 0);
        err = FSpOpenDF(&logFile, fsWrPerm, &gLogFileRefNum);
        if (err != noErr) {
            gLogFileRefNum = 0;
        }
    }
    
    // Write the header to the file.
    
    if (err == noErr) {
        err = FSWriteQAtLEOF(gLogFileRefNum, OTStrLength(kStartFileLogString), kStartFileLogString);
    }   
    
    // Clean up.
    
    if (err != noErr) {
        if ( gLogFileRefNum != 0 ) {
            StopFileLogging();
        }
    }
    
    return err;
}
 
enum {
    kFlagsToStringBufferSize = 50
        // flagStr must point to a buffer of at least 50 characters.
};
 
static void FlagsToString(char flags, char *flagStr)
    // Create a verbose string that represents the various
    // flag bits in flags.
{
    flagStr[0] = 0;
 
    if ((flags & SL_TRACE) != 0) {
        OTStrCat(flagStr, "Trace ");
    }
    if ((flags & SL_ERROR) != 0) {
        OTStrCat(flagStr, "Error ");
    }
    if ((flags & SL_CONSOLE) != 0) {
        OTStrCat(flagStr, "Console ");
    }
    
    if ((flags & SL_FATAL) != 0) {
        OTStrCat(flagStr, "Fatal ");
    }
    if ((flags & SL_NOTIFY) != 0) {
        OTStrCat(flagStr, "Notify ");
    }
    if ((flags & SL_WARN) != 0) {
        OTStrCat(flagStr, "Warning ");
    }
    if ((flags & SL_NOTE) != 0) {
        OTStrCat(flagStr, "Notice ");
    }
    
    // Remove the trailing space.
    
    if ( flagStr[0] != 0 ) {
        flagStr[ OTStrLength(flagStr) - 1 ] = 0;
    }
}
 
extern char *LogEntryToCString(LogEntryPtr thisEntry)
    // See comment in interface part.
{
    Str255 dateStr;
    Str255 timeStr;
    char   flagStr[kFlagsToStringBufferSize];
 
    OTAssert("LogEntryToCString: paramErr", thisEntry != nil);
    
    FlagsToString(thisEntry->fLogHeader.flags, flagStr);
 
    // ltime is generated by calling OTGetTimeStamp and converting
    // the result to milliseconds using OTTimeStampInMilliseconds.
    
    // ttime is the result of the low-memory global Time, which
    // is the same as the result of GetDateTime.
 
    DateString(thisEntry->fLogHeader.ttime, shortDate, dateStr, nil);
    TimeString(thisEntry->fLogHeader.ttime, true, timeStr, nil);
 
    sprintf(gLogAssemblyBuffer, "%ld\t%s\t0x%02x\t0x%08x\t%#s %#s\t%d\t%d\t%s\r",
            thisEntry->fLogHeader.seq_no,
            flagStr,
            thisEntry->fLogHeader.flags,
            thisEntry->fLogHeader.ltime,
            dateStr,
            timeStr,
            thisEntry->fLogHeader.mid,
            thisEntry->fLogHeader.sid,
            (char *) thisEntry + sizeof(LogEntry)
        );
    return gLogAssemblyBuffer;
}
 
extern void RecordLogEntryToFile(LogEntryPtr thisEntry)
    // See comment in interface part.
{
    OSStatus junk;
    
    if ( gLogFileRefNum != 0 ) {
    
        (void) LogEntryToCString(thisEntry);
        
        junk = FSWriteQAtLEOF(gLogFileRefNum, OTStrLength(gLogAssemblyBuffer), gLogAssemblyBuffer);
        OTAssert("RecordLogEntryToFile: Error writing log entry", junk == noErr);
        
        gTicksOfLastFileWrite = TickCount();
    }
}
 
extern void FileLoggingIdle(void)
    // See comment in interface part.
{
    OSStatus junk;
    ParamBlockRec pb;
 
    if ( gLogFileRefNum != 0 ) {
        if ( TickCount() > gTicksOfLastFileWrite + kTicksPerFlush ) {
            pb.ioParam.ioRefNum = gLogFileRefNum;
            junk = PBFlushFileSync(&pb);
            OTAssert("DoIdle: FlushFile returned an error", junk == noErr);
            gTicksOfLastFileWrite = kLogFileCleanTicks;
        }
    }
}
 
extern Boolean FileLoggingActive(void)
    // See comment in interface part.
{
    return gLogFileRefNum != 0;
}
 
extern void StopFileLogging(void)
    // See comment in interface part.
{
    OSStatus junk;
    
    OTAssert("StopFileLogging: We're not logging!", gLogFileRefNum != 0);
    
    junk = FSClose(gLogFileRefNum);
    OTAssert("StopFileLogging: Could not close log file", junk == noErr);
    gLogFileRefNum = 0;
}