JWColby
jwcolby at colbyconsulting.com
Sat Jan 6 11:16:32 CST 2007
I thought you guys might appreciate seeing how I do this. I need to log things to text files all of the time for exporting data out to clients. I am writing processes (classes) where I export data to fixed width files. The export process is table driven and allows taking a specification from the client that says what fields are expected, how long each field is, how they are padded (left / right), with what characters (space, zeros etc), date formats and so forth. That export process (class system) performs an export using a query that I build to get the data ready for export. The end result of that class system is a sequence of text strings that are the export data. Now a common task is that these export systems need to write the data out to disk. Another common task is that the export process needs to log any errors that it encounters. Given that this is a repetitive task I decided to create a class that my export process classes could instantiate and then use. It uses the Windows File System Object, so if you work under a notwork Nazi who has locked down your systems, you can pretty much stop reading now, or... you might want to replicate this using the methods for file manipulations built in to Access. I like the FSO and use it where possible. This log class is designed to be instantiated, then left open to write (or read) to a text file. In the Init() I pass in the pieces of the file name, the path, the file name, the extension. I like to date / time stamp my files in the file name so that I can write the same type of file out to a common directory and be able to see right in the file name the date and time that the file was written. So I also allow passing in a date format string and a time format string. If you are going to only do this once a week and don't care about the time, then only pass in the date format. If you are going to do this several times in a day, then also pass in a time format. The init function builds up a file spec which is the fully pathed location of the file. ATM it does not attempt to create the directory path to get there, although eventually it will do that, so that if the path does not exist it will attempt to build the path (directory structure). The log class has methods for recomputing the file spec, for example if the time has changed. There are a couple of simple methods for getting a read or write text stream. There are also properties for reading and writing the file spec pieces individually if desired. And finally there are two properties to allow getting a pointer to the file system object, and the text stream object. Using the pointer to the text stream object, you can then directly write to the log file. Since the class holds the text stream open until the class unloads or you intentionally close it, you can load this class and just start writing to the text file. Close when you are done. This class is an example of wrapping another object in order to extend that object's functionality with your own. As you know VBA does not directly allow inheritance, however by using wrapper classes like this you can crudely emulate inheritance. I add functionality to the text stream object here. To use the class you need to dim and instantiate the log class: dim clsMyLog as clsLogFile set clsMyLog = new clsLogFile clsMyLog.Init(mclsLog.Init "C:\Dev\DISNEW\", "Test", "LOG", "YYYYMMDD", "HHMMSS") Now you are ready to write to the file. Until such time as you unload the class instance you can write to the text stream. clsMyLog.pTS.WriteLine "test" clsMyLog gets a pointer to the log file class, pTS gets a pointer to the open text stream, .Writeline calls the method of the textstream, and "test" is written to the file. The file name will look something like Test-20070106-105937.LOG and will be located in the path C:\Dev\DISNEW\. Throw this class out in your library, expose it, and use it as needed. BTW, for those of you who follow the concept of a framework, I have a framework object which is itself a class. I have now built a new method of the framework object that can initialize these log files and hold them in a collection. Given that my apps all use the framework, syntax becomes: cfw.log("MyLogName").Init("C:\Dev\DISNEW\", "Test", "LOG", "YYYYMMDD", "HHMMSS") cfw.log("MyLogName").Log "Write some test text" cfw.log("MyLogName").Term 'closes the log file and unloads it from the framework collection Notice that this syntax looks strikingly similar to the syntax you would use to work with any other object such as the database object, the form object etc. My framework is just an object, with methods and properties. The first line causes the framework (cfw) to create an instance of the log class, initialize it with the filespec information, and store a pointer to the log class instance in a collection for future use. The second line uses a previously initialized log class instance to write text. The last line unloads the class from memory and the collection of log files available for use. Doing things "the framework way" allows me to create a ton of log files as required. For example: ' 'From inside of the ULLICO export process class I call the framework and set up two log files. ' cfw.log("ULLICOExport-Error").Init(C:\Dev\DISNEW\Export\ULLICO\", "ULLICO-Error", "ERR", "YYYYMMDD", "HHMMSS") cfw.log("ULLICOExport-Data").Init(C:\Dev\DISNEW\Export\ULLICO\", "ULLICO-Data", "DAT", "YYYYMMDD", "HHMMSS") ' 'From inside of the HV Export class I call the framework and set up two log classes. ' cfw.log("HVExport-Error").Init(C:\Dev\DISNEW\Export\HV\", "HV-Error", "ERR", "YYYYMMDD", "HHMMSS") cfw.log("HVExport-Data").Init(C:\Dev\DISNEW\Export\HV\", "HV-Data", "DAT", "YYYYMMDD", "HHMMSS") Now I have two different processes (a ULLICO export process and a HV export process), each using two log files. The Error log file is used to log any processing errors that the export process runs into. The Data log file is the actual data file that is being assembled by the export process. BTW, It took me almost as long to write this email as it did to write the log class and hook it into my framework. Classes are simple to write and use once you understand them. A framework magnifies the effectiveness of service classes such as this log class enormously, making them dead simple to set up, use and tear down. That's how I do it. The following is the log file class. It is a work in progress, but any additions you need, you can add. Option Compare Database Option Explicit '. '.========================================================================= '.Copyright : cColby Consulting 2000. All rights reserved. '.E-mail : jcolby at ColbyConsulting.com '.========================================================================= ' DO NOT DELETE THE COMMENTS ABOVE. All other comments in this module ' may be deleted from production code, but lines above must remain. '-------------------------------------------------------------------------- '.Written By : John W. Colby '.Date Created : 05/29/2002 '.Rev. History : '.Comments : '.------------------------------------------------------------------------- '. ' ADDITIONAL NOTES: ' ' BEHAVIORS: ' '*+ Class constant declaration Private Const DebugPrint As Boolean = False Private Const mcstrModuleName As String = "clsLogFile" '*- Class constant declaration '*+ Class variables declarations 'THE STRING INSTANCE NAME IS BUILT UP FROM THE MODULE NAME AND 'A RANDOM INT Private mname As String '*- Class variables declarations '*+ custom constants declaration '*- custom constants declaration '*+ custom variables declarations Private mFSO As Scripting.FileSystemObject Private mTS As Scripting.TextStream Private mstrFileName As String Private mstrFileExt As String Private mstrFilePath As String Private mstrFileSpec As String Private mstrDteFmt As String Private mstrDte As String Private mstrTimeFmt As String Private mstrTime As String '*- custom variables declarations '*+ Private Init/Terminate interface Private Sub Class_Initialize() Randomize mname = strModuleName & ":" & Random(999999, 0) 'Make a random name for ourself Set mFSO = New Scripting.FileSystemObject End Sub Private Sub Class_Terminate() On Error Resume Next Term Set mobjChildren = Nothing Set mobjParent = Nothing Set mFSO = Nothing mTS.Close Set mTS = Nothing End Sub '*- Private Init/Terminate interface '*+ Public Init/Terminate interface Public Function Init(ByRef robjParent As Object, _ strFilePath As String, strFileName As String, strFileExt As String, _ Optional strDteFmt As String = "", Optional strTimeFmt As String = "") As Boolean On Error GoTo Err_Init mstrFilePath = strFilePath mstrFileName = strFileName mstrFileExt = strFileExt mstrDteFmt = strDteFmt mstrTimeFmt = strTimeFmt ' 'Now that we have stored the file spec pieces, create a file spec mFmtFileSpec Exit_Init: Exit Function Err_Init: MsgBox err.Description, , "Error in Function clsLWS.Init" Resume Exit_Init Resume 0 '.FOR TROUBLESHOOTING End Function 'CLEAN UP ALL OF THE CLASS POINTERS Public Sub Term() On Error Resume Next End Sub '*- Public Init/Terminate interface 'get the name of this class / module Property Get strModuleName() As String strModuleName = mcstrModuleName End Property 'get the pointer to this object's instance name Public Property Get name() As String name = mname End Property '*+ Parent/Child links interface Property Get pFileName() As String pFileName = mstrFileName End Property Property Let pFileName(strFileName As String) mstrFileName = strFileName End Property Property Get pFileExt() As String pFileExt = mstrFileExt End Property Property Let pFileExt(strFileExt As String) mstrFileExt = strFileExt End Property Property Get pFilePath() As String pFilePath = mstrFilePath End Property Property Let pFilePath(strFilePath As String) mstrFilePath = strFilePath End Property Property Get pFileSpec() As String pFileSpec = mstrFileSpec End Property Property Get pFSO() As Scripting.FileSystemObject Set pFSO = mFSO End Property Property Get pTS() As Scripting.TextStream Set pTS = mTS End Property '*- Parent/Child links interface '*+ Withevents interface '*- Withevents interface '*+ Private class functions '*- Private class functions '*+ Public class functions ' 'This function creates the time string portion of the file spec at the instant this function is called ' Private Function mFmtTime() As String On Error GoTo Err_mFmtTime If Len(mstrTimeFmt) Then mstrTime = Format(Time(), mstrTimeFmt) End If mFmtTime = mstrTime Exit_mFmtTime: Exit Function Err_mFmtTime: MsgBox err.Description, , "Error in Function clsLogFile.mFmtTime" Resume Exit_mFmtTime Resume 0 '.FOR TROUBLESHOOTING End Function ' 'This function creates the date string portion of the file spec at the instant this function is called ' Private Function mFmtDte() As String On Error GoTo Err_mFmtDte If Len(mstrDteFmt) Then mstrDte = Format(date, mstrDteFmt) End If mFmtDte = mstrDte Exit_mFmtDte: Exit Function Err_mFmtDte: MsgBox err.Description, , "Error in Function clsLogFile.mFmtDte" Resume Exit_mFmtDte Resume 0 '.FOR TROUBLESHOOTING End Function ' 'This function creates the file spec string ' Function mFmtFileSpec() As String mstrFileSpec = mstrFilePath & mstrFileName If Len(mstrDteFmt) Then mstrFileSpec = mstrFileSpec & "-" & mFmtDte End If If Len(mstrTimeFmt) Then mstrFileSpec = mstrFileSpec & "-" & mFmtTime End If mstrFileSpec = mstrFileSpec & "." & mstrFileExt End Function Public Function mFSGetWrite() Set mTS = mFSO.CreateTextFile(mstrFileSpec) End Function Public Function mFSGetRead() Set mTS = mFSO.OpenTextFile(mstrFileSpec) End Function Public Function mFSClose() mTS.Close End Function '*- Public class functions John W. Colby Colby Consulting www.ColbyConsulting.com