Binding logging code to application code lets you track your application without changing its code



Similar documents
Advisor Answers. Using textbox for address. November, 2005 VFP 9/8

Simple Document Management Using VFP, Part 1 Russell Campbell russcampbell@interthink.com

Smooks Dev Tools Reference Guide. Version: GA

Google Resume Search Engine Optimization (SEO) and. LinkedIn Profile Search Engine Optimization (SEO) Jonathan Duarte

Documentum Developer Program

Software Documentation Guidelines

Windows XP Backup Made Easy

FTP Client Engine Library for Visual dbase. Programmer's Manual

EasyPush Push Notifications Extension for ios

Configure ActiveSync with a single Exchange server (Exchange sync for an iphone)

Working with forms in PHP

The Power Loader GUI

Creating XML Report Web Services

About ZPanel. About the framework. The purpose of this guide. Page 1. Author: Bobby Allen Version: 1.1

My Secure Backup: How to reduce your backup size

Lotus Domino Backup Strategy

Patterns in. Lecture 2 GoF Design Patterns Creational. Sharif University of Technology. Department of Computer Engineering

YubiKey with Password Safe

1. To start Installation: To install the reporting tool, copy the entire contents of the zip file to a directory of your choice. Run the exe.

Visual FoxPro Accessing MySQL. presented to The Atlanta FoxPro Users Group June 19 th, 2007

6. Always pay something: Even if you can't pay all of your bills in full, always make the minimum payment by the due date.

Guidelines for successful Website Design:

Crowdsourced Code Review in Programming Classes

v1.1.0 SimpleSQL SQLite manager for Unity3D echo17.com

Java (12 Weeks) Introduction to Java Programming Language

Unix Sampler. PEOPLE whoami id who

TECHNOLOGY Computer Programming II Grade: 9-12 Standard 2: Technology and Society Interaction

Character Bodypart Tutorial

Copyright 2014 Jaspersoft Corporation. All rights reserved. Printed in the U.S.A. Jaspersoft, the Jaspersoft

AN INTRODUCTION TO MACRO VARIABLES AND MACRO PROGRAMS Mike S. Zdeb, New York State Department of Health

Android Programming Family Fun Day using AppInventor

Draft report of the Survey of Participants in Stage 3 of the Workflow Pilot Non MEs users

Tutorial: Building a Web Application with Struts

Installation & User Guide

The presentation explains how to create and access the web services using the user interface. WebServices.ppt. Page 1 of 14

Getting Started with STATISTICA Enterprise Programming

Why you shouldn't use set (and what you should use instead) Matt Austern

Text of Templates

Continuous Integration Part 2

Detecting rogue systems

Managed File Transfer with Universal File Mover

Arithmetic Coding: Introduction

Date. Hello, Good Afternoon and Welcome to Pegasus Racing! I cordially invite you to join me...

Using Form Scripts in WEBPLUS

Java Interview Questions and Answers

The single biggest mistake many people make when starting a business is they'll create a product...

A Simple Shopping Cart using CGI

Tracking Referrals. Creating a List of Referral Sources. Using the Sales Rep List

Sound System Buying Guide

Lab 2 : Basic File Server. Introduction

PHP Language Binding Guide For The Connection Cloud Web Services

Introduction to Open Atrium s workflow

1.2 Using the GPG Gen key Command

Look for the 'BAERCOM Instruction Manual' link in Blue very near the bottom.

DBCX 2: The Quickening Doug Hennig

Guidelines for Schools

Dell World Software User Forum 2013

Google Lead Generation For Attorneys - Leverage The Power Of Adwords To Grow Your Law Business FAST. The Foundation of Google AdWords

HP Service Virtualization

Configuration Manager

Logging in Java Applications

Visual Basic. murach's TRAINING & REFERENCE

Installation & User Guide

Notepad++ The COMPSCI 101 Text Editor for Windows. What is a text editor? Install Python 3

When a web site has been optimized, it has been set up so that search engines will find the site and rank it well in their organic listings 1.

Demonstrating a DATA Step with and without a RETAIN Statement

Qualtrics Question API

EVALUATION. WA1844 WebSphere Process Server 7.0 Programming Using WebSphere Integration COPY. Developer

H I V. and Insurance YOUR LEGAL RIGHTS HIV AND INSURANCE 1

How to Write a Marketing Plan: Identifying Your Market

Doctor's appointment. Summary. Problem description. Main actor (s) Activity scenario

SQL Server Database Coding Standards and Guidelines

SyncTool for InterSystems Caché and Ensemble.

Google Lead Generation for Attorneys

Time Clock Import Setup & Use

The Settings tab: Check Uncheck Uncheck

How to Get High Precision on a Tablet with a Drawing-Like App. Craig A. Will. May, 2014

How To Design Your Code In Php (Php)

Anaplan makes workforce planning strategic at a fast-growing cloud computing company

Introduction to Python

System Preferences is chock full of controls that let you tweak your system to how you see fit.

Microsoft Windows PowerShell v2 For Administrators

Computing Concepts with Java Essentials

Collecting your College deposit? Keep Friday 26 June free. LEAVERS 2015 SUMMER Collecting Exam Results:

EVALUATION ONLY. WA2088 WebSphere Application Server 8.5 Administration on Windows. Student Labs. Web Age Solutions Inc.

Introduction. Here's What You Need To Know In Under 2 Minutes

SCF/FEF Evaluation of Nagios and Zabbix Monitoring Systems. Ed Simmonds and Jason Harrington 7/20/2009

Wave Analytics Data Integration

Connect to an Oracle Database from within Visual Basic 6 (Part 1)

Facebook Twitter YouTube Google Plus Website

How On-Premise ERP Deployment Compares to a Cloud (SaaS) Deployment of Microsoft Dynamics NAV

Firewall Builder Architecture Overview

IRA 423/08. Designing the SRT control software: Notes to the UML schemes. Andrea Orlati 1 Simona Righini 2

Version Uncontrolled! : How to Manage Your Version Control

Transcription:

July, 2005 Use BINDEVENT() for invisible logging Binding logging code to application code lets you track your application without changing its code by Tamar E. Granor, Technical Editor When I first learned about the inclusion of BINDEVENT() in VFP 8, I had a hard time figuring out why I would use it. Why not just call the code I want when I want it to run? It took a while for the light to go on, but finally a couple of useful cases came to mind. The first situation where BINDEVENT() is handy is when you can't change the source code. If you don't have the source or it comes from a third-party, binding may be the only way to hook your own code in. This realization led me to the second case, times when you may have access to the source, but adding the necessary calls would be onerous or would make your code unwieldy. In particular, I think this applies to progress reporting and logging application activity. Both showing progress and logging are removed from the main purpose of your code and what you want to do is likely to change over time, especially for logging. I've now used BINDEVENT() successfully for both tracking progress and logging application activity. In the case of logging, I've both tacked it on as a debugging measure and built it in from the start. In this article, I'll show you how to easily set up logging code without changing the actual application code. A Logger base class The whole thing starts with a class capable of creating a log. Since you might want to do different things with the log (create a text file, send it to the Debug Output window, create a web page, etc.), the base class is abstract and quite simple: DEFINE CLASS Logger AS Custom PROCEDURE LogIt(cString as Character) * Abstract here ENDDEFINE

I've created two subclasses of the Logger class: LogToDebugOut sends log information to the Debug Output window, while LogToFile sends it to a text file. Here's the code for LogToFile; all three classes are included on this month's Professional Resource CD (PRD) in Logger.PRG. DEFINE CLASS LogToFile AS Logger cfile = "" lstartover =.T. PROCEDURE Init(cFile as Character) IF NOT EMPTY(cFile) This.cFile = m.cfile PROCEDURE LogIt(cString as Character) #DEFINE CRLF CHR(13) + CHR(10) STRTOFILE(cString + CRLF, This.cFile, ; NOT This.lStartOver) This.lStartOver =.F. While you could simply instantiate one of the Logger subclasses and call the LogIt method directly in your application, BINDEVENT() means you don't have to. Instead, you use another class that instantiates the appropriate Logger subclass and has one or more methods you can bind to the application methods. This class instantiates one of the Logger subclasses and uses it for logging. Logging the Toolbox To demonstrate what you can do with logging, I'll use the Toolbox as an example. Introduced in VFP 8, the Toolbox is a very cool example of what you can do with VFP. You'll find the code for it in Tools\XSource\VFPSource\Toolbox, once you unzip the file Tools\XSource\XSource.ZIP. (To learn how to use the Toolbox, see Cindy Winegarden's articles in the January, April and June 2003 issues of FoxPro Advisor.) The code for the Toolbox is complex and while trying to understand what's going on, you aren't likely to want to modify any of it. Logging

its events and methods provides the opportunity to see how your actions are processed. The Toolbox has two class hierarchies, one for the user interface and one for the underlying engine. Each has methods you might want to log. When you run the toolbox, the main user interface class is instantiated and a reference stored in the variable _otoolbox. That object instantiates the engine object and stores a reference to it in its otoolboxengine property. We'll look at two different approaches to logging the Toolbox's events. Generic logging The first way to log an object's events is to have a generic method that uses AEVENTS() to find out why it was called and logs a fairly generic message based on the results. In this case, the class you need (LogGeneric.PRG on the PRD) looks like this: DEFINE CLASS LogApplication AS Custom ologger =.null. PROCEDURE Init(cLogClass as Character, ; clogclasslib as Character, ; clogparams as Character) IF EMPTY(cLogParams) * No parameters to pass to Logger class This.oLogger = NEWOBJECT(cLogClass, clogclasslib) ELSE This.oLogger = NEWOBJECT(cLogClass, clogclasslib, ; "", clogparams) PROCEDURE LogAny(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) * Log any method or event #DEFINE CRLF CHR(13) + CHR(10) LOCAL acallingevent[1], clogstring, nparmcount nparmcount = PCOUNT() * Find out who called AEVENTS(aCallingEvent, 0) clogstring = acallingevent[1].name + "." + ; acallingevent[2] clogstring = clogstring + " was called with "

clogstring = clogstring + TRANSFORM(nParmCount) + ; " parameters:" + CRLF FOR nparm = 1 TO nparmcount clogstring = clogstring + " Parameter " + ; TRANSFORM(nParm) + ":" cparm = "p" + TRANSFORM(nParm) clogstring = clogstring + ; TRANSFORM(EVALUATE(cParm)) + CRLF ENDFOR This.oLogger.LogIt(cLogString) PROCEDURE BindIt(oObject as Object) * Bind methods of logged object to LogAny method * of this class LOCAL nobjcount, nmember, nmatch LOCAL aobjmembers[1] nobjcount = AMEMBERS(aObjMembers, oobject, 1) FOR nmember = 1 TO nobjcount IF INLIST(aObjMembers[nMember, 2], "Event", "Method") BINDEVENT(oObject, aobjmembers[nmember, 1], ; This, "LogAny") ENDFOR ENDDEFINE The Init method receives the name and class library for the Logger subclass to use, along with any parameters for that subclass. It then instantiates the Logger subclass, saving the reference to a custom property, ologger. The LogAny method does the actual logging. It uses AEVENTS() to figure out why it was called and then builds a string to send to the log. LogAny can be bound to any method that accepts up to 10 parameters. If you have code with more than that, add more parameters to LogAny. (Actually, if you have methods with more than 10 parameters, consider redesigning them to use fewer parameters and to take more advantage of properties.)

The BindIt method accepts an object as a parameter and binds every method or event of that object to the LogAny method, so every time a method or event of that object fires, the action is logged. To demonstrate the technique in use, here's a short program (BindToolboxGeneric.PRG on the PRD) that runs the Toolbox, instantiates the LogApplication class and then binds the events and methods from the toolbox's two key objects to the LogAny method: PUBLIC olog DO (_toolbox) olog = NEWOBJECT("LogApplication","LogGeneric.PRG", "", ; "LogToFile","Logger.PRG", "Toolbox.log") olog.bindit(_otoolbox.otoolboxengine) olog.bindit(_otoolbox) The olog variable here is public because this program has no wait state; a local or private variable would go out of scope at program end, and logging would be turned off. If you're binding to objects in a normal application, you just need to make sure that your logging object gets created with appropriate scope. (You might choose to create a logging object as part of your application object, so that it's always available. In that case, to use it, you only need the calls to the BindIt method.) After running the program, take some actions with the Toolbox. When you're done, if you examine the file Toolbox.log, you'll see entries like this: ToolboxEngine.GETRECORD was called with 1 parameters: Parameter 1:microsoft.ffc ToolboxEngine.RUNADDINS was called with 2 parameters: Parameter 1:microsoft.ffc Parameter 2:ONLOAD TOOLBOX.PAINT was called with 0 parameters: TOOLBOX.RESIZEALL was called with 0 parameters: TOOLBOX.RENDERCATEGORY was called with 1 parameters: Parameter 1:(Object) Of course, the exact contents of the log file depend on what actions you take in the Toolbox.

Application-specific logging The second approach to logging is to create a separate logging class for each application and log only the events and methods you're interested in. An application-specific logging class needs a method for each method you want to track; name each method "Log" plus the name of the method to be logged. We again start with an abstract class. It's similar to the LogApplication class above, but it doesn't include the LogAny method. In addition, the BindIt method here does a little more work. As in the first version, you pass it an object reference; in this version, it finds all methods of the object you passed for which there's a corresponding Log method in the logging object and binds them. Both this top-level class and the toolbox-specific class are included in LogToolbox.PRG on the PRD: DEFINE CLASS LogApplication AS Custom ologger =.null. PROCEDURE Init(cLogClass as Character, ; clogclasslib as Character, ; clogparams as Character) IF EMPTY(cLogParams) * No parameters to pass to Logger class This.oLogger = NEWOBJECT(cLogClass, clogclasslib) ELSE This.oLogger = NEWOBJECT(cLogClass, clogclasslib, ; "", clogparams) PROCEDURE BindIt(oObject as Object) * Bind methods of logged object to methods of this class LOCAL nobjcount, nlogcount, nmember, nmatch LOCAL aobjmembers[1], alogmembers[1] nobjcount = AMEMBERS(aObjMembers, oobject, 1) nlogcount = AMEMBERS(aLogMembers, This, 1) FOR nmember = 1 TO nobjcount IF INLIST(aObjMembers[nMember, 2], "Event", "Method") * Does the log have a corresponding method? * Case-insensitive search, EXACT ON, return row nmatch = ASCAN(aLogMembers, "Log" + ; aobjmembers[nmember, 1], ; -1, -1, 1, 15) IF nmatch <> 0

BINDEVENT(oObject, aobjmembers[nmember, 1], ; This, alogmembers[ nmatch, 1]) ENDFOR ENDDEFINE The logging code for each method to be logged is quite simple. The method you create must receive the same parameters as the method it's logging. Other than that, there's just one line in the method a call to the LogIt method of the ologger object. (Of course, you can use additional code to build the string to send to the log.) For example, here's the Log method for the ToggleAlwaysOnTop method of the _toolboxform class: PROCEDURE LogToggleAlwaysOnTop(lAlwaysOnTop) This.oLogger.LogIt("Toggling always on top setting: " ; + TRANSFORM(m.lAlwaysOnTop)) This approach gives you more control over the log content. In the generic version, the message is the same for each logged method. With a specific logging class, you can customize the message based on the method called. Having separate Log methods also lets you control which methods are logged. With the generic Log approach, you can end up with extremely large logs. With this approach, only the methods and events for which you create a corresponding Log method are logged. The code to use the specific log class (BindToolbox.PRG on the PRD) is essentially the same as the code to use the generic log. You just need to specify the appropriate class. Like the earlier example, this code runs the toolbox, creates the logger and binds the same two toolbox objects. However, in this case, only methods for which there's a corresponding Log method are bound: PUBLIC olog DO (_toolbox) olog = NEWOBJECT("LogToolbox","LogToolbox.PRG", "", ; "LogToFile","Logger.PRG", "Toolbox.log") olog.bindit(_otoolbox.otoolboxengine)

olog.bindit(_otoolbox) How the binding works The BindIt method is the key to both versions. In each case, AMEMBERS() gets a list of PEMs for the object whose events and methods are to be bound. In the application-specific version, a second call to AMEMBERS() provides a list of methods of the logging object. The code loops through the members of the object being bound. If the member is an event or method, the code acts. For the generic version, it simply binds that event or method to the LogAny method of the logging object, with this line: BINDEVENT(oObject, aobjmembers[nmember, 1], ; This, "LogAny") The application-specific code checks whether the logging object has a method with the same name as the logged object's event or method prefixed with "Log". If so, the event or method to be logged is bound to the corresponding Log method. This code does the search and the binding: nmatch = ASCAN(aLogMembers, "Log" + ; aobjmembers[nmember, 1], -1, -1, 1, 15) IF nmatch <> 0 BINDEVENT(oObject, aobjmembers[nmember, 1], ; This, alogmembers[ nmatch, 1]) The best of both worlds You don't really have to choose either the generic or the applicationspecific approach; you can combine the two. One possibility is to check for a specific Log method corresponding to an object's event or method. If one exists, bind to it. If not, bind to a generic log method. A second possibility is to stick with the generic approach, but limit it to logging only events and methods of interest. In this approach, you need to specify which items to log using, for example, an array or an XML string. The BindIt method would check each event and method of the logged object to see whether it should be logged. You can even combine these two options to provide specific logging for some methods, generic logging for some others, and no logging at all for a third group.

Another way you could extend this code is to stick with the generic LogAny, but use a case statement to let you build more specific strings to send to the log in some cases. The best way to do this is to add a call in LogAny to another method, abstract in the LogApplication class, and then subclass as needed. Put it to work While setting up a two-level logging system like this takes a little effort, the payoff is that you can turn logging on and off without touching your application's code. That makes it much easier to track down problems, whether they occur while you're debugging or after an application is deployed.