Section 12.2. Using Flash Remoting to Log Events

12.2. Using Flash Remoting to Log Events

The following example shows how to use Flash Remoting to create a FlashCom logging system in conjunction with ColdFusion. A logging system can be very useful with your FlashCom applications. The Admin Service application included with FlashCom uses a logging system. However, that logging system writes to a stream, so the log is accessible only to applications that read the stream's binary FLV format, most notably FlashCom. In the following example, the logging system uses Flash Remoting to write the log data to a database so that the data can later be read from a variety of clients . You can then use SQL and ColdFusion features such as charting to create various reports , for example.

The FlashCom logging application has six basic parts :

  • The database into which the log events are saved

  • The Server-Side ActionScript (SSAS) class that takes care of caching and then sending the events to the database via ColdFusion and Flash Remoting

  • The ColdFusion component that takes the event data sent from FlashCom and records it to the database

  • The FlashCom application that utilizes the aforementioned ActionScript class

  • The Flash client that connects to the FlashCom application

  • The ColdFusion page that allows a user to view and filter events

12.2.1. Setting Up the Database

Before writing any ColdFusion or ActionScript code, you need to determine what data should be logged and create the database tables to store that data. You can approach recording log events in various ways. For the purposes of this example application, we'll record the following information:


Event type

A FlashCom developer should be able to create arbitrary event types and record those in the database. For example, one application developer may want to differentiate between the application starting and stopping. Another developer may also want to log an event each time a user connects to the application.


Event description

Allowing for a description of the event means developers can record more detailed information about the event.


Event time

We record the time at which the event occurred.


Information object

The information object allows developers to store any additional information they want. For example, when a client connects to the application, a developer may want to record the client's IP address.


Application name

We record the name of the application.


Logger object name

An application might have more than one logger object. Therefore, users may later want to be able to filter events based on the logger that recorded the event data.

You can use any database management system you want in order to create the database tables. The scripts provided in this chapter have been tested with MySQL, but generally should work with other standard database management systems.

The database we'll use in the example has a single table with seven fields. Table 12-1 describes the database table.

Table 12-1. The fields in table fcs_event

Field

Datatype

event_id

varchar(50)

event_type

varchar(50)

event_description

text

event_timestamp

datetime

info_object

text

application_name

varchar(50)

logger_name

varchar(50)


The following SQL script creates the database and table if run from many database management systems:

 CREATE DATABASE 'fcs_log'; USE 'fcs_log'; CREATE TABLE 'fcs_event' (   'event_id' varchar(50) NOT NULL default '',   'event_type` varchar(50) default NULL,   'event_description' text,   'event_timestamp' datetime default '0000-00-00 00:00:00',   'info_object' text,   'application_name' varchar(50) default NULL,   'logger_name' varchar(50) default NULL,   PRIMARY KEY ('event_id') ) TYPE=MyISAM; 

No indexes are created for this table, but depending on how the table will be used, indexes could be created for a number of the attributes.

Once you've created the database and table, you should make sure to register the database as a datasource within the ColdFusion Administrator.

You can register a datasource in ColdFusion by selecting the Data Sources link from the navigation menu on the left of the screen. Once at the next screen, enter fcs_log as the datasource name, select MySQL as the datasource, and click the Next button. From there, you'll be prompted to specify the server information so that ColdFusion can connect to the database.

In the example code, we'll use the datasource name of fcs_log .

12.2.2. Creating the Logger Class

Logging is managed on the server side by a Server-Side ActionScript class that we'll call PFCSLogger . The PFCSLogger class has the following specifications:

  • Each instance should have a name it can use to distinguish itself from other instances.

  • Instances of the class should write events to a stream and store the events until a maximum number of events is cached. Once the cache maximum is reached, it should send the event data to the CFC in a single batch. That way, fewer Flash Remoting calls are made.

  • The class should have an API that allows the developer to specify the event type, description, and info object when calling the log( ) method. The event timestamp, application name, and logger name should be inserted automatically by the PFCSLogger class.

In order to define the PFCSLogger class, create a new SSAS file named PFCSLogger.asc and place it in a directory named fcs_log under the FlashCom applications directory.

Add the following code to PFCSLogger.asc :

 // Load the NetServices classes. load("NetServices.asc"); // Define the constructor. A single parameter specifies a unique instance name. function PFCSLogger (name) {   // If the   name   parameter is undefined, use a default value and issue a warning.   if (name == undefined) {     this.name = "defaultLogger";     trace("PFCSLogger WARNING: Default logger name used. " +           "Pass a parameter to the constructor to use a different logger name.");   }   this.name = name;   // Create a new stream within a directory with the same name as the logger.   this.cache = Stream.get(this.name + "/log_cache");   // Start appending data to the stream.   this.cache.record("append");   // Create a new stream for playing back the data recorded in the cache stream.   this.cache_playback = Stream.get("log_cache_playback");   // Add a reference to the stream within which the data is recorded.   this.cache_playback.log_cache = this.cache;   // Specify a default maximum number of cached events.   this.cache_playback.max_cached = 10;   // Every event is recorded using   send( )   , specifying   log( )   as the method passed to   // the   send( )   call. Therefore, each event that is read from the stream calls the   //   log( )   method. So define   log( )   such that it appends events to an array.   this.cache_playback.log = function (info) {     this.events.push(info);   };   // Use   onStatus( )   to determine when the stream has stopped playing, so you'll   // know when the events have been read from the stream.   this.cache_playback.onStatus = function (info) {     if (info.code == "NetStream.Play.Stop") {       // Check whether the number of events in the stream exceeds the maximum       // number of cached events. Also, if   flush   is true, call the service method       // to record the events to the database. Call the stream's   record( )   method       // with parameter "record" to overwrite previous events that had been cached.       if (this.events.length >= this.max_cached  this.flush) {         this.fr_service[this.method](this.events);         this.log_cache.record("record");         this.flush = false;       }         }   };   // Set an interval to check the cached events (default interval is 10 seconds).   this.interval = setInterval(this, "checkCache", 10000); } //   log( )   adds an event to the stream using   send( )   . The method has three parameters: // the event type, the event description, and an optional info object. PFCSLogger.prototype.log = function (event_type, description, info_object) {   // Specify the   log( )   method when calling   send( )   , so   log( )   is called   // when the events are read from the stream. Pass to the stream an object    // containing the event type, event description, and info object, plus the    // application name, the event timestamp, and the logger name.   //   this.cache.send("log", {event_timestamp: (new Date( )), event_type: event_type,                   description: description, info_object: info_object,                    application_name: application.name, logger_name: this.name}); }; // Define a method so that the default maximum cache value can be overwritten. PFCSLogger.prototype.setMaxCached = function (max_cached) {   this.cache_playback.max_cached = max_cached; }; // Define a method so that the default poll interval can be overwritten. PFCSLogger.prototype.setPollInterval = function (poll_interval) {   clearInterval(this.interval);   this.interval = setInterval(this, "checkCache", poll_interval); }; // Define a method that allows the Flash Remoting information to be set. PFCSLogger.prototype.setFlashRemoting = function (url, service_name, method) {   NetServices.setDefaultGatewayUrl(url);   var nc = NetServices.createGatewayConnection( );   this.cache_playback.fr_service = nc.getService(service_name, this);   this.cache_playback.method = method; }; //   flush( )   allows the data to get sent immediately to the CFC regardless of // whether the maximum number of cached events has been stored. PFCSLogger.prototype.flush = function ( ) (   this.cache_playback.flush = true;   this.checkCache( ); }; //   checkCache( )   gets called at an interval to read the event data from the stream. PFCSLogger.prototype.checkCache = function ( ) (   this.cache_playback.events = new Array( );   this.cache_playback.play(this.name + "/log_cache", -2, -1, 3); }; 

Much of the preceding code is fairly self-explanatory. However, some portions warrant a more detailed explanation.

Each instance should have a name property that is set in the constructor. The name property allows the logger instance to create a stream that is unique to it. As you can see in the code, the stream that the logger creates is named log_cache , and it is placed within a directory with the same name as the name property:

 function PFCSLogger(name) {   if (name == undefined) {     this.name = "defaultLogger";     trace("PFCSLogger WARNING: Default logger name used. " +           "Pass a parameter to the constructor to use a different logger name.");   }   this.name = name;   this.cache = Stream.get(this.name + "/log_cache"); 

The stream should initially be set to record in append mode because the application could have restarted while previous events were still in cache. Using the append mode makes sure those previous events are not overwritten:

 this.cache.record("append"); 

In order to play back the events recorded in the stream, you'll need a second stream. The cache_playback property creates a second stream in order to handle that task:

 this.cache_playback = Stream.get("log_cache_playback"); 

Since streams are recorded in real time, you cannot read the events in the way you might read the contents of a shared object or an array. Instead, you need to play the stream, then use an onStatus( ) event handler to determine when the end of the stream has been reached. When the end of the stream has been reached, the method will get called with an information object whose code property is "NetStream.Play.Stop". At that point, you can check how many events are recorded in the stream. If the maximum number of cache events has been reached, send the events to ColdFusion via Flash Remoting. Then, call the record( ) method in record mode to overwrite the previously cached events:

 this.cache_playback.onStatus = function (info) {     if (info.code == "NetStream.Play.Stop") {       if (this.events.length >= this.max_cached) {         this.fr_service[this.method](this.events);         this.log_cache.record("record");       }     }   }; } 

The logger calls the checkCache( ) method at a polling interval to read the contents of the stream. Each time the method is called, it should create a new, empty array into which to store the events and call the play( ) method to play back the stream. The empty array is used to count the number of events. Each time the log( ) method of the playback stream is called, you can add the event to the array. The play( ) method plays back the stream to which the events are getting recorded. Notice that it uses a value of 3 for the fourth parameter passed to play( ) ; this tells FlashCom to return the data in the stream immediately rather than in real time:

 PFCSLogger.prototype.checkCache = function (  ) (   this.cache_playback.events = new Array(  );   this.cache_playback.play(this.name + "/log_cache", -2, -1, 3); }; 

12.2.3. Creating the CFC

The next step is to create the CFC that records the data in the database. The CFC has one method: record( ) . The method accepts a single parameter, which should be an array of events. Each event is an associative array (in ColdFusion, they are called structures or structs) with the following properties: event_type , description , event_timestamp , logger_name , and application_name . It may also have an info_object property. The CFC method is fairly simple; it loops through each of the elements of the array and inserts the event data as database records. For the purposes of this example, create a subdirectory named fcs_log in the web root, and create a new file named Logger.cfc with the following CFC code in the fcs_log folder:

 <cfcomponent>   <!---   # Define the   record(  )   method, and allow remote access from Flash Remoting.   # Define one parameter named   events   , which should be an array.   --->   <cffunction name="record" access="remote">     <cfargument name="events" type="array" />     <!--- Loop through each of the elements in the events array. --->     <cfloop from="1" to="#ArrayLen(events)#" index="i">       <!--- Use   CreateUUID( )   to create a primary key. --->       <cfset event_id = CreateUUID( ) />       <cfset event = events[i] />       <!--- If   info_object   is defined, use WDDX to serialize the data into             a string that can get inserted into the database.              Otherwise, use an empty string.        --->       <cfif IsDefined("event.info_object")>         <cfwddx action="cfml2wddx" input="#event.info_object#"                        output="info_object_wddx" />       <cfelse>         <cfset info_object_wddx = "" />       </cfif>       <!--- Insert the data into the database. --->       <cfquery datasource="fcs_log">       INSERT INTO fcs_event(event_id, event_type, event_description,                        event_timestamp, info_object, application_name, logger_name)       VALUES('#event_id#', '#event.event_type#', '#event.description#',              #CreateODBCDateTime(event.event_timestamp)#, '#info_object_wddx#',              '#event.application_name#', '#event.logger_name#')       </cfquery>     </cfloop>   </cffunction> </cfcomponent> 

The CFC method is very basicit takes the array of events and inserts the data into the database. A few details, however, could use some additional explanation.

Auto-incrementing integer fields can be used as primary keys in most database management systems. However, most database systems have a different way of defining auto-incrementing fields or sequences. While often less efficient than product-specific options such as Oracle Sequences, you can use a varchar field as the primary key and use a universally unique identifier value. ColdFusion has a function called CreateUUID( ) that generates such a value. So the following code simply creates the ID and assigns it to a variable:

 <cfset event_id = CreateUUID(  ) /> 

Besides working with any database, CreateUUID( ) has other advantages. For example, if you want to insert a new record and then retrieve the primary key, you can use CreateUUID( ) to create the primary key in the code before the INSERT statement. That provides the key without having to do an INSERT and then a SELECT, as you would need to do if relying on the database system to generate a unique ID.

The event.info_object property is a ColdFusion structure. That means it is a complex datatype, and it would be rather difficult to parse the data, determine the datatypes of each of the values in the structure, and insert those values into the database in an efficient manner. Instead, it is much more convenient to serialize the structure as a WDDX packet. WDDX is an XML-based language specifically designed to represent complex data (arrays, associative arrays, recordsets, etc.) as strings so the data can be sent via protocols such as HTTP or, in this case, inserted into a text field in a database. ColdFusion has built-in WDDX support using the <cfwddx> tag, allowing you to quickly serialize (and deserialize ) data. The following code converts the event.info_object structure to a WDDX packet and stores it in a variable named info_object_wddx :

 <cfwddx action="cfml2wddx" input="#event.info_object#" output="info_object_wddx" /> 

12.2.4. Building the FlashCom Application

The next step is to create the FlashCom application, which simply creates instances of the PFCSLogger class and writes log events. Place the following code in your application's main.asc file. Assuming you named the FlashCom application's directory fcs_log , you can alternatively name the main file fcs_log.asc :

 // Load the file that defines the PFCSLogger class. load("PFCSLogger.asc"); application.onAppStart = function ( ) (   // When the application starts, create a new logger instance named "application."   this.logger = new PFCSLogger("application");   // Set the Flash Remoting information.   this.logger.setFlashRemoting("http://localhost/flashservices/gateway",                                "fcs_log.Logger", "record");   // Write an event that indicates the application started.   this.logger.log("app_start", "The application started");   // Set an interval that calls   log( )   every minute, just to generate test data.   setInterval(this.logger, "log", 60000, "poll", "A poll interval ellapsed"); }; application.onConnect = function (newClient, username) {   this.acceptConnection(newClient);   newClient.username = username;   // Create a new logger instance as a property of the client.   newClient.logger = new PFCSLogger("client_" + username);   newClient.logger.setFlashRemoting("http://localhost/flashservices/gateway",                                     "fcs_log.Logger", "record");   // Write a log event that says the user has connected.   newClient.logger.log("client_connect", "A client connected to the application",                         {username: username, ip: newClient.ip}); }; application.onDisconnect = function (disconnectingClient) {   // Write a log event that indicates the user disconnected. Then call   flush( )   to   // send the data to the CFC.   disconnectingClient.logger.log("client_disconnect", "A client disconnected",              {username: disconnectingClient.username, ip: disconnectingClient.ip});   disconnectingClient.logger.flush( ); }; // When the application stops, send a log event and flush the cache. application.onAppStop = function ( ) (   this.logger.log("app_stop", "The application stopped");   this.logger.flush( ); }; // Define a client method that simply returns an array of connected users. // Write a log event each time the method is called. Client.prototype.getUserList = function ( ) (   this.logger.log("get_user_list", "Client requested user list.",                    {client: this.username, ip: this.ip});   return application.clients; }; 

The application code is fairly straightforward and doesn't require much additional explanation. It creates PFCSLogger instancesone as a property of the application object and one as a property of each connected client. The application logger logs events when the application starts and stops. Additionally, and simply for testing purposes, the application uses setInterval( ) to log poll events once per minute. The client logger logs events when clients connect, disconnect, or call the getUserList( ) method.

12.2.5. Creating the Flash Client

In order to test the application and the loggers, you next need to create a Flash movie to serve as the client. The client is very simple. It should contain two component instancesa Button component named cbt and a List component named user_list . Create a file named loggerClient.fla , add the two components using the Components panel, and give them instance names using the Properties panel. Then, rename the first layer to Actions and add the following ActionScript code to the first frame of the layer on the main timeline:

 // Define a response object to handle responses from FlashCom client method calls. var response = new Object(  ); response.onResult = function (data) {   // When an array of users is returned, assign it to the data provider of the list.   user_list.dataProvider = data; }; // Define a listener object to handle click events dispatched by the button. var listener = new Object( ); // When a user clicks the button, call FlashCom application's   getUserList( )   method. listener.click = function ( ) (   nc.call("getUserList", response); }; // Register the listener object with the button. cbt.addEventListener("click", listener); // Create the NetConnection. var nc = new NetConnection( ); // Connect to the application with a username of "some_user". nc.connect("rtmp:/fcs_log", "some_user"); 

Once you've created and saved the Flash client movie, run it a few times. Running the client starts the FlashCom application and logs the corresponding events. Click the button to retrieve the user list, which also logs an event.

Now that we have some events, we need a way to view the event log.

12.2.6. Viewing the Events

The final step in the logger example is to create a simple ColdFusion page to view the events. You can define a ColdFusion page within the fcs_log directory in the web root. Name the page log_view.cfm , and add the following code to it:

 <!--- Add three queries to retrieve the distinct event types, application names,        and logger names. ---> <cfquery datasource="fcs_log" name="qEventTypes"> SELECT DISTINCT event_type FROM fcs_event </cfquery> <cfquery datasource="fcs_log" name="qApplications"> SELECT DISTINCT application_name FROM fcs_event </cfquery> <cfquery datasource="fcs_log" name="qLoggers"> SELECT DISTINCT logger_name FROM fcs_event </cfquery> <html>   <head>     <title>FCS Log</title>   </head>   <body>     <!--- Define a form that posts back to itself. --->     <form action="<cfoutput>#CGI.SCRIPT_NAME#</cfoutput>" method="post">       <table>         <!--- Define three select controls that allow the user to select event               types, application names, and logger names to filter the records.         --->         <tr>           <td>Event Type:</td>           <td>             <select name="event_type">               <option value="*">any event type</option>               <cfoutput query="qEventTypes">                 <option                  value="#qEventTypes.event_type#"> #qEventTypes.event_type#</option>               </cfoutput>             </select>           </td>         </tr>         <tr>           <td>Application:</td>           <td>             <select name="application_name">               <option value="*">any application</option>               <cfoutput query="qApplications">                 <option value="#qApplications.application_name#">                                #qApplications.application_name#                 </option>               </cfoutput>             </select>           </td>         </tr>         <tr>           <td>Logger:</td>           <td>             <select name="logger_name">               <option value="*">any logger</option>               <cfoutput query="qLoggers">                 <option                      value="#qLoggers.logger_name#">#qLoggers.logger_name#</option>               </cfoutput>             </select>           </td>         </tr>         <tr>           <td colspan="2">             <input type="submit" name="submit_button" value="View Events" />           </td>         </tr>       </table>     </form>     <!--- If the form has been submitted, display the records. --->     <cfif IsDefined("FORM.submit_button")>       <!--- Add a query that retrieves the matching event records. --->       <cfquery datasource="fcs_log" name="qEvents">         SELECT *         FROM fcs_event         WHERE 1 = 1         <cfif FORM.event_type NEQ "*">           AND event_type = '#FORM.event_type#'         </cfif>         <cfif FORM.application_name NEQ "*">           AND application_name = '#FORM.application_name#'         </cfif>         <cfif FORM.logger_name NEQ "*">           AND logger_name = '#FORM.logger_name#'         </cfif>       </cfquery>       <table cellpadding="5">         <tr>           <td></td>           <td>event type</td>           <td>description</td>           <td>timestamp</td>           <td>application</td>           <td>logger</td>           <td>info object</td>         </tr>         <cfoutput query="qEvents">           <tr bgcolor="##FFF5DF">             <td>#qEvents.CurrentRow#</td>             <td>#qEvents.event_type#</td>             <td>#qEvents.event_description#</td>             <td>#DateFormat(qEvents.event_timestamp, "medium")#                  #TimeFormat(qEvents.event_timestamp, "medium")#</td>             <td>#qEvents.application_name#</td>             <td>#qEvents.logger_name#</td>             <td>                <cfif qEvents.info_object NEQ "">                 <cfwddx action="wddx2cfml" input="#qEvents.info_object#"                          output="info_object_deserialized" />                 <cfloop collection="#info_object_deserialized#" item="item">                   #item#: #info_object_deserialized[item]#<br />                 </cfloop>               </cfif>             </td>           </tr>         </cfoutput>       </table>     </cfif>   </body> </html> 

Once you've created the ColdFusion page or copied the version from the book's web site to your server, you can browse to it and view the events that have been logged.

This event-logging application is but one example of the kinds of applications you can build using Flash Remoting to communicate between FlashCom and ColdFusion.



Programming Flash Communication Server
Programming Flash Communication Server
ISBN: 0596005040
EAN: 2147483647
Year: 2003
Pages: 203

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net