Plateforme Level Extreme
Abonnement
Profil corporatif
Produits & Services
Support
Légal
English
Articles
Recherche: 

A Basic Audit-Trail
Hilmar Zonneveld, May 1, 2003
An audit-trail is a record of who did what changes, and when. In Visual FoxPro, this can easily be accomplished through triggers. I hinted at the possibility of doing an audit-trail, in my article on triggers - now, as a reaction to questions in the Universal Thread, I want to present a sample...
An audit-trail is a record of who did what changes, and when. In Visual FoxPro, this can easily be accomplished through triggers.

I hinted at the possibility of doing an audit-trail, in my article on triggers - now, as a reaction to questions in the Universal Thread, I want to present a sample solution.

What should be saved

First, consider what you want to save when a user changes a record. You will probably want to include most of the following options:

  • The affected table.
  • The name of the field that was changed.
  • The old value.
  • The new value.
  • When it was changed.
  • Who did the change (the username).
  • You may want to save additional information, for instance, whether the change was done interactively or through a batch process.

Where to save it

You could use a text-file, but you will probably want to use a table (DBF), since this will make it easier to do systematic searches.

How to accomplish it

First of all, you can intercept any change saved, with a trigger. If you are not familiar with triggers, I suggest you read my introductory article on triggers, Universal Thread Magazine, June 2002.

The trigger function receives a parameter to indicate whether the change done was an Insert, an Update, or a Delete. (Of course, you can also create three separate trigger functions for the three cases.) I believe that the trigger function is best saved in the database stored procedures.

Now, for each field in the table, save the old and new values to the audit-table. Old values (i.e., before the change was made) are obtained with an expression like OldVal("MyField").

Requirements

In order to use a trigger function, each table which you want to audit has to be part of a database.

While I strongly recommend to use buffering most of the time, this is not a requirement for triggers to work. There is frequent confusion on this topic, due to a somewhat confusing help topic for the function OldVal(). I tested trigger functions for an audit-trail (and other trigger functions, where changes in one table update another table), and it works quite well without buffering.

Some of the functions I used in this article require VFP 6 or later, but it is quite possible to adapt the audit-trail function to versions 3 or 5, as well.

Sample table structure

Create a table with these commands:

set blocksize to 0 && Optional; reduces memo size

create table MyAudit free;
  (TableName C(20), UserName C(20), TrigType C(1),;
    WhenChang T, Changes M)

Note: I avoided using field names of more than 10 characters, since I decided to keep the audit table free.

The fields used are:

  • TableName: The name of the table where the change occurred.
  • UserName: The user who did the change (in our sample, requires a global variable to be declared - otherwise, "???" will be substituted).
  • TrigType: "U" if called from an update trigger, "I" for an insert trigger, and "D" for a delete trigger.
  • WhenChang: The date and time when the change was done.
  • Changes: A log of the actual changes done. Includes field name, old and new values.

The sample audit-trail function

* Basic audit-trail function
* (Sample, for the article)
FUNCTION MyAudit(tcTriggerType)
local lnField, lcFieldName, lcTable, lcUserName, lcChanges
if type("gcUserName") = "C" && if public variable for the user has been declared
  lcUserName = gcUserName
else
  lcUserName = "???"
endif
lcTable = juststem(dbf())
lcChanges = ""
do case
case tcTriggerType = "D"
  * Nothing is required here, unless you want to
  * save OldVal(...) for each field
  * (in case the user made changes, and then deleted the record)
case tcTriggerType = "I"
  for lnField = 1 to fcount()
    lcFieldName = field(lnField)
    lcChanges = lcChanges + lcFieldName + ": ";
      + transform(eval(lcFieldName));
      + chr(13) + chr(10)
  next
case tcTriggerType = "U"
  for lnField = 1 to fcount()
    lcFieldName = field(lnField)
    lcChanges = lcChanges + lcFieldName + ": ";
      + transform(oldval(lcFieldName)) + " -> ";
      + transform(eval(lcFieldName));
      + chr(13) + chr(10)
  next
endcase
insert into MyAudit;
    (TableName, UserName, TrigType, WhenChang, Changes);
  values;
    (lcTable, lcUserName, tcTriggerType, datetime(), lcChanges)

How to use the sample function

Open any table for which you want to save audit-trail information, with MODIFY STRUCTURE. Go to the "Table" tab.

Insert code for the three triggers. For the insert trigger, type MyAudit("I"). For the update trigger, type MyAudit("U"). For the delete trigger, type MyAudit("D").

If any of the triggers already has an expression, don't overwrite it. (The expressions may have been inserted by the Referential Integrity Builder, or by a programmer.) Instead, add the functions to the end of the expression, combined with AND.

For instance, if the expression for the insert trigger is __ri_insert_mytable(), change it to __ri_insert_mytable() and MyAudit("I").

Normally, it is convenient to put the audit function at the end, so that no audit information is saved if other triggers fail (return .F.).

You have to repeat this for every table you want to keep track of.

Possible Enhancements

One possible enhancement is to change to a format that makes it easier to be read automatically by a program. This may be important if you want to use the audit-trail not only for documentation purposes, or to fix responsibilities, but you also want to be able to have rollback and roll-forward capabilities (that is, undo changes, based on the audit-trail and new data; or recover data after the last backup, also from the audit-trail).

Another thing you may want to change is to use a more sophisticated type conversion, instead of the standard transform() function.

Finally, note that I didn't make every effort to make the function as perfect as possible, nor did I test it under a variety of circumstances. The intention was simply to have a working sample, to introduce you to audit-trail programming.

Additional considerations

For a multi-user system, it is important to keep the time on the different users' machines more or less synchronized. I did this by checking the time on the server on startup of the main program, and exiting (with an error message) if there was a discrepancy of more than 10 minutes. One way to check the time on the server is to create a file on the server, and check its date-time. The file will have the date-time of the server. (I only tested this on a Windows NT 4 file server.)

Another thing to consider is that the audit-trail table can grow rather quickly. You may want to save the older parts of the audit-trail to a history file, to reduce the size of the backup (in case you include the audit-trail in the backup), or in case it approaches the 2 GB limit.

Also note that saving data to the audit-trail can make every change to your data slower. This should be no problem when the user does changes interactively, but when a procedure or trigger updates hundreds or thousands of records automatically, things can get noticeably slower. You may therefore want to disable auditing under certain circumstances.

Conclusion

As you have seen, it is relatively easy to add an audit-trail to your tables.

The sample audit-trail in this article is a demo only; a proof of concept. The idea was to give you a hint of what can be done. You may want to use the ideas in this article as a starting-point, to create an audit-trail for your specific needs.

Source Code

Hilmar Zonneveld, Independent Consultant
Hilmar Zonneveld works in programming since 1986, using dBASE, FoxPro and Visual FoxPro. He is available as an independent consultant. He currently works as a programmer at Bata Shoe Organization; also as an instructor at Cisco Networking Academy. You can contact him through the Universal Thread, or, via e-mail, at hilmarz@yahoo.com. Personal Web page (mainly in Spanish): www.geocities.com/hilmarz.
More articles from this author
Hilmar Zonneveld, December 6, 2001
(The latest update contains minor edits only.) Five easy and fun ways to get yourself into trouble with inheritance. A frequent source of problems in OOP is called "breaking inheritance". This document briefly describes what inheritance is, how it applies to properties and methods, and how it ...
Hilmar Zonneveld, July 1, 2002
Introduction Buffering is a feature in Visual FoxPro that allows us to give the user "undo" and "save" capabilities. In the old FoxPro 2.x days, programmers either didn't provide this capability, or edited memory variables, and copied information between these variables and the table fiel...
Hilmar Zonneveld, October 6, 2005
Due to a recent Windows security fix, users can no longer access a CHM file on a server. The table of contents appears, but the individual pages are replaced by error messages. Access to CHM files in specific folders can be explicitly allowed through special registry settings.
Hilmar Zonneveld, July 20, 2001
(The last update contains minor edits only.) The idea is to have several controls on a form controlled with an array. Thus, you can quickly go through all the controls on the form, managing the array. The sample code included will help you get started quickly. You can easily adapt it to manage...
Hilmar Zonneveld, September 1, 2002
With Automation, you can control all aspects of Excel, Word, or other programs that provide this feature, from Visual FoxPro. In this article, I will concentrate on Excel. Its purpose is to provide a starting point, especially for people new to automation. Introduction With automation, you bas...
Hilmar Zonneveld, March 1, 2003
Introduction One common task in programming is to keep track of what problems are pending. For this purpose, I use a "hierarchical to-do list": a list of items, each of which can have sub-items. All you need is Microsoft Word. Alternatives are available as freeware or shareware, but in t...
Hilmar Zonneveld, October 7, 2005
This is a step-by-step tutorial to show inheritance, specifically in Visual FoxPro forms, as a guidance for people who are not familiar with inheritance in general, or who don’t know how to implement it in Visual FoxPro. The basic idea of inheritance is that all your forms, or several of your for...
Hilmar Zonneveld, May 30, 2004
The code shows how to quickly obtain the greatest common factor, and the least common multiple. Both functions are used when manipulating fractions, among others. Several methods are possible; the method usually taught in school involves prime numbers, but this code will execute much faster (and it ...
Hilmar Zonneveld, November 1, 2006
A standard requirement in a production system, or in systems for cost calculation, is to add up all the raw materials for a number of finished articles, to get the total cost, or simply to purchase the materials. In this article, Hilmar outlines how to do this with multiple levels of intermediate ar...
Hilmar Zonneveld, August 1, 2002
Overview The purpose of this article is to give an overview of normalization. Basically, normalization refers to having an efficient table structure. I will not discuss the famous "first to fifth normal forms" - if you want that information, enough texts exist about it in other places (search sit...
Hilmar Zonneveld, November 8, 2001
The following function will open any document, with its default association (the same application that will be called when you double-click on the file, in Windows Explorer). Use it to open a text-file, a Word or Excel document, an image, etc., with an external application.
Hilmar Zonneveld, May 1, 2002
Introduction This document explains the meaning of primary key, foreign key and candidate index in Visual FoxPro. A discussion of natural and surrogate keys (keys visible, or not visible, to the end-user) is included, including the advantages of each approach, as well as different methods for o...
Hilmar Zonneveld, January 1, 2003
Continuing my series of introductory articles, this article presents an introduction of a simple yet powerful programming concept: recursion. Introduction "To understand recursion, you must first understand recursion." "To make yogurt, you need milk and yogurt." If you are not accustomed...
Hilmar Zonneveld, December 1, 2002
Introduction This article presents an introduction to coding shortcuts in Visual FoxPro - when to use them, and when not to. Notes on coding in general This article is about coding shortcuts; however, I should first emphasize that making the code as small as possible is usually not the number...
Hilmar Zonneveld, July 20, 2001
Rushmore Optimization can help make queries much faster. However, "Full Rushmore Optimization" is not always a desirable goal. "Partial Optimization" is sometimes much faster. It is often believed that to speed things up, you need to have as many indices as possible. This article explains that so...
Hilmar Zonneveld, June 7, 2002
If you need to check elapsed time with seconds() or a datetime value, this function allows you to display the elapsed time in a human-readable format, that is, hours:minutes:seconds, instead of the total number of seconds. Just pass a number of seconds as a parameter.
Hilmar Zonneveld, April 1, 2002
SQL is a standard language used to manipulate databases. Several of the SQL commands are integrated into the Visual FoxPro language. Select This is a very flexible command, used to select data from a table, or from several tables. This command has options to get totals from several record...
Hilmar Zonneveld, August 1, 2003
In this article, I will show several ways to manipulate text-files. Knowledge of these methods is often important to import and export specific formats. Some of the techniques can also be used to work with files of any content; however, this article will concentrate on text-files. When ...
Hilmar Zonneveld, June 1, 2002
The purpose of this article is to show how to use some aspects provided by the Visual FoxPro database engine, to control our data. Indices Perhaps most readers already know indices; anyway, I find it convenient to include a brief summary of the topic, since this is a requisite to understan...
Hilmar Zonneveld, November 1, 2002
A help file can be used either for interactive help, or as an online manual. In this article, I will give an overview over creating help files in the new help format (CHM), for Visual FoxPro 6 and later. This article is introductory and assumes no prior knowledge of the Help Compiler, or of HTML cod...
Hilmar Zonneveld, February 1, 2003
Introduction Any real-world application will sooner or later misbehave. It is important to be able to find those problems. Visual FoxPro's built-in debugger can help a lot to find out why your program doesn't work as you thought it would. Most of the material in this article applies to Visual...
Hilmar Zonneveld, May 1, 2006
This article is an introduction to VisioModeler. This is a free CASE tool, that can help you design your database, in the process sharing the information with the client in a visual, easy-to-understand, format.