Capturing User Session Information

The data that ODBC logging collects is obviously very useful for measuring the number of hits our site gets, and the volume of data we send out over our 'Net connection. However, it doesn’t tell us much about the user that requested the page, other than their IP address (and this could be just the IP address of their company's proxy server, or an IP address generated dynamically by their ISP).

Of course, we'd also like to collect more meaningful information. Perhaps the user's browser type, which country they're in, and how long they spend on our site. To do this, we can use ASP sessions. We met sessions back in Chapter 5, and—although there are some problems with information loss under some circumstances—they provide a real opportunity to collect the information we want.

Basically, a session is started when the user first enters a site, i.e. when they request any ASP page from anywhere on the site for the first time (loading a non-ASP page doesn’t start a session). This page doesn’t have to be the Home page, and doesn’t have to be in the 'root' folder of the site. It only has to be in the same folder as the global.asa file, or in a subfolder below this folder. By default, a session ends twenty minutes after the last request from that user for a page within these folders.

Setting Up User Session Logging

When we are using ODBC logging, IIS doesn't provide any built-in features for automatically capturing information about user sessions and the more interesting HTTP header variables. Instead, we can take advantage of the events that ASP exposes when a session starts and ends. In fact, we're also going to record information about when the application itself starts and ends. This is less useful, but will indicate periods when we've had no visitors. The application starts just before the first visitor session starts, and ends after the last remaining visitor session ends.

Our Custom Sessions Table

The following screenshot shows the table that we'll use to hold our sessions log data. A script to create it is included with the samples available for this book on our Web site at http://webdev.wrox.co.uk/books/1797/. The date and time will be set automatically by SQL Server when a new record is inserted, because the default value of the EventDateTime field is the built-in function GETDATE():

Collecting the Session and Application 'Start' Information

So the first step is to react to the application starting, as this event will always occur first. Then we can react to the start of each individual user session. The following code is the first part of the global.asa file that lives in the root folder of our entire site:

  <SCRIPT LANGUAGE=VBSCRIPT RUNAT=SERVER>

Sub Application_onStart()
   On Error Resume Next
   strSQL = "INSERT INTO Sessions (EventType) VALUES ('Application Start')"
   WriteSessionData(strSQL)
End Sub

Sub Session_onStart()
   On Error Resume Next
   strSQL = "INSERT INTO Sessions " _
          & "(EventType,URL,Referer,RemoteHost,UserAgent,UALanguage,UserID,HostIP)" _
          & " VALUES ('New Session', '" _
          & Request.ServerVariables("URL") & "', '" _
          & Request.ServerVariables("HTTP_REFERER") & "', '" _
          & Request.ServerVariables("REMOTE_HOST") & "', '" _
          & Request.ServerVariables("HTTP_USER_AGENT") & "', '" _
          & Request.ServerVariables("HTTP_ACCEPT_LANGUAGE") & "', " _
          & CLng(Session.SessionID) & ", '" _
          & Request.ServerVariables("LOCAL_ADDR") & "')"
   WriteSessionData(strSQL)
End Sub
...

Each time the whole application starts, we're going to store a simple value in our sessions log. Because we're using a database (SQL Server), we're creating SQL INSERT statements in the code here. The table we're going to write to has a field named EventDateTime, which is of field type DATETIME and default value GETDATE(). We only need to create values for the rest of the record's fields, and SQL Server will insert the current date and time automatically. The actual creation of the entry in the database is done by a separate subroutine that we'll look at in a while.

Similarly, each time a user session starts we add a record to our sessions log. This time, however, we collect a range of useful values from the Request.ServerVariables collection, including the URL of the page they request, the page that contained the link that sent them to our site (if they found us that way), and details of their browser (including the languages it will accept). We also store the ASP session identifier—it might be useful—and (so that we know which of our sites they hit if we have several sites feeding a single log file) we also record the host site's IP address.

If we were logging to a text file instead, we would need to add code to record the current date and time (remember, SQL Server sets the current date and time through a default value for the field), and change the formatting of the string to create a comma-delimited file rather than an SQL statement:

  Sub Session_onStart()
   On Error Resume Next
   strEntry = FormatDataTime(Now()) & ", "
            & Request.ServerVariables("URL") & ", " _
            & Request.ServerVariables("HTTP_REFERER") & ", " _
            & Request.ServerVariables("REMOTE_HOST") & ", " _
            & Request.ServerVariables("HTTP_USER_AGENT") & ", " _
            & Request.ServerVariables("HTTP_ACCEPT_LANGUAGE") & ", " _
            & CStr(Session.SessionID) & ", " _
            & Request.ServerVariables("LOCAL_ADDR") & ")"
   WriteSessionString(strEntry)  'a custom routine to write the string to a file
End Sub

The string is then appended to a disk file with a routine that uses the VBScript FileSystemObject. We'll show you how shortly.

Collecting the 'End' Information

When either the entire application or a single session ends we write another entry to the log file to indicate this. At present we have no plans to use this information, but you never know—this might change in the future. It seems sensible to collect it anyway, while we are setting the process up. All we'll do is record the event type and the time and date. Again, because the table has a field that defaults to GETDATE(), we only need to create the record, SQL Server will insert the current date and time:

  ...
Sub Application_onEnd()
   On Error Resume Next
   strSQL = "INSERT INTO Sessions (EventType) VALUES ('Application End')"
   WriteSessionData(strSQL)
End Sub

Sub Session_onEnd()
   On Error Resume Next
   strSQL = "INSERT INTO Sessions (EventType, UserID) " _
       & "VALUES ('Session End', " & CLng(Session.SessionID) & ")"
   WriteSessionData(strSQL)
End Sub
...

Writing the Log File Entry

To put the data into the session log in SQL Server, we use ADO. This is quite efficient, because we should be able to use the same pool of connections as the Web server does to log file accesses. On top of that, because we only record application and session start and end events, we are writing far fewer entries than the Web server does.

For example, on entering the Wrox Web-Developer Home page, the Web server has to write around 25 to 30 entries into the IISLog table (depending on the browser type—remember some browsers will download and cache extra images, while others don’t support frames so will request less files). For the same event, our ASP code in global.asa will only have to write one entry to the Sessions table.

If the user's browser doesn’t support cookies, or has them disabled, a session is not started. In this case, no entry is written. In this case, no entry is written.

Here's the code to update our database sessions log. We use the same System DSN as the Web server, and the same user and password:

  ...
Sub WriteSessionData(strSQL)
   On Error Resume Next
   Set oConn = Server.CreateObject("ADODB.Connection")
   oConn.open "DSN=WWWLOG;UID=anonymous;PWD=;"
   oConn.Execute strSQL
   Set oConn = Nothing
End Sub
</SCRIPT>

It's reported that you can use Server Side Include files within global.asa. However, despite much experimentation, we haven’t managed to make this work reliably. This is why we haven't inserted the connection string into the page like we do in other examples in this book. However, as the DSN is also 'hard-coded' into the Web server Properties dialogs, this isn’t really a problem. Changing it is going to be a wholesale exercise in the MMC, so it will make little difference having to update the global.asa files for each site as well.

If we are using a text file to store session data, we'll need a routine to write this to the disk instead. We need to append it to the existing data. The following code demonstrates how this could be done:

  Sub WriteSessionString(strEntry)
  strFileName = "C:\InetPub\WWWRoot\Logfiles\Sessions.log"
  Set objFSO = CreateObject("Scripting.FileSystemObject")
  'open the file with vbForAppending (8) and 'Create if not existing' (True)
  Set objFile = objFSO.OpenTextFile(strFileName, 8, True)
  objFile.WriteLine strEntry
  objFile.Close   
End Function

Of course, you can collect most of this information as a text file log in IIS 4 by using the W3C Extended Logging format. Remember, however, that you will get an entry for every request this way, whereas our 'home-made' technique only creates one record per session. We'll only be interested in, for example, the referrer information when the user first hits our site, so that we can see which site they came from. There are also other advantages to setting up custom logging. For example, you can associate a particular log entry with a particular event—maybe in an on-line store you would record where people who made purchases came from, ignoring those who didn't buy anything.

© 1998 by Wrox Press. All rights reserved.