Twitter: raymondcamden


Address: Lafayette, LA, USA

Session metrics with Application.cfc

12-19-2006 3,794 views ColdFusion 9 Comments

A few months ago I blogged (How ColdFusion can save your business) about how you can use the onSessionEnd feature of Application.cfc to track the success of your ecommerce site. Today I thought I'd follow up with another example that can be useful for measuring the success of your web site.

Most web log programs will provide information about user sessions on your site. Each program has it's own voodoo to determine what a "session", but by applying their rules they can tell you things like what pages were used to enter the site - what pages were hit last, and how long the user spent on the site.

Lets looks at a simple example of how we can provide similar metrics. First off we need a database table to store the information. I created a table with 6 columns:

  • sessionid/varchar/255: This stores the session ID for the user.
  • entrypage/varchar/255: The URL for the first page the user hit in the session. 255 may be a bit short.
  • entrytime/datetime: The time when the user first hit the site.
  • exitpage/varchar/255: The last URL the user hit.
  • exittime/datetime: The last hit from the user.
  • hits/int: The number of page hits from the user.

I named my table entryexitlog. Now lets look at an Application.cfc file that will use this table. I'll post the entire CFC as a download at the end, but first lets look at each method of the CFC. (Each method with something interesting in it of course!)

view plain print about
1<cffunction name="onApplicationStart" returnType="boolean" output="false">
2    <cfset application.dsn = "test">
3    <cfreturn true>
4</cffunction>

The first method simply creates the DSN. Nothing too special there so lets just move along.

view plain print about
1<cffunction name="onSessionStart" returnType="void" output="false">
2    <cfset var thispage = cgi.script_name>
3    
4    <cfif len(cgi.query_string)>
5        <cfset thispage = thispage & "?" & cgi.query_string>
6    </cfif>
7    
8    <cfset session.hits = 0>
9    
10    <!--- Log the entry page --->
11    <cfquery datasource="#application.dsn#">
12    insert into entryexitlog(sessionid,entrypage,entrytime)
13    values(
14        <cfqueryparam cfsqltype="cf_sql_varchar" value="#session.urltoken#">,
15        <cfqueryparam cfsqltype="cf_sql_varchar" value="#thispage#">,
16        <cfqueryparam cfsqltype="cf_sql_timestamp" value="#now()#">)
17    </cfquery>
18    
19</cffunction>

The onSessionStart code will fire when you first hit the site. The first thing I do is figure out what page you are visiting. This is done by checking cgi.script_name and cgi.query_string. I initialize your hits value to 0. Lastly I insert a new record into the database. Note the use of session.urltoken. This is a value that ColdFusion creates for you and is unique per session.

view plain print about
1<cffunction name="onRequestStart" returnType="boolean" output="false">
2    <cfargument name="thePage" type="string" required="true">
3    <cfset var thispage = arguments.thePage>
4    
5    <cfif len(cgi.query_string)>
6        <cfset thispage = thispage & "?" & cgi.query_string>
7    </cfif>
8
9    <!--- store for later --->
10    <cfset session.lastpage = thispage>
11    <cfset session.lasthit = now()>
12    <cfset session.hits = session.hits + 1>
13    
14    <cfreturn true>
15</cffunction>

The onRequestStart method will fire every time you hit a page. Note that once again I get the page (although this time I don't need to use cgi.script_name as the page is passed to the method automatically). I record the page as your "lastpage" value, store the time, and increase your hits. Do note that this code is not threadsafe. If you had a site with frames (lord forbid!) then it is possible the values will not be 100% correct. I made the call that this was so a) unlikely and b) not mission critical that I didn't bother using the locks. Now lets move on to the last method:

view plain print about
1<cffunction name="onSessionEnd" returnType="void" output="false">
2    <cfargument name="sessionScope" type="struct" required="true">
3    <cfargument name="appScope" type="struct" required="false">
4    
5    <!--- Log the exit page --->
6    <cfquery datasource="#arguments.appScope.dsn#">
7    update entryexitlog
8    set
9    exitpage = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.sessionScope.lastPage#">,
10    exittime = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#arguments.sessionScope.lastHit#">,
11    hits = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.sessionScope.hits#">
12    where sessionid = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.sessionScope.urltoken#">
13    </cfquery>
14
15</cffunction>

This method will fire when the session ends. All I do is update the database record, but do note that I do not directly access the Application or Session scope. Instead I have to use the copies passed to me in the method itself.

So what can we get from this? A whole heck of a lot of information:

  • Top entry pages (most common way folks enter your site)
  • Bottom entry pages (least common way folks enter your site)
  • Top exit pages (the pages that folks end up on - this could be very important as it could reflect a page that is either boring or confusing)
  • Bottom exist pages (not quite as important)
  • Average number of hits per use (how many pages do people visit on your site?)
  • Average time on site (how long do folks stay on your site?) One cool thing is that since I store the actual time of your last page hit, the session end value will be more accurate. The default timeout for sessions is 20 minutes, which means that for most folks their sessions would be 20 minutes or longer, even if they just stayed for one minute. By noting their last hit timestamp your reports will be more accurate.

Even nicer - you can provide reports that are more up to date then traditional log processors. Even Google Analytics is a bit behind for the reports it generates.

Related Blog Entries

9 Comments

These comments will soon be imported into Disqus. To add a comment, use Disqus above.
  • Tom Mollerus #
    Commented on 12-19-2006 at 12:17 PM
    Ray,

    This is a really nice piece of work, even as simple as it is. Another great feature to add would be to track campaign codes from marketing efforts (e.g., capture a specific URL variable that is appended to the first page the person calls) and to track goals/conversions (e.g., did this person register, did they put anything in their shopping cart/look at the upgrade page, did they pay for premium services). Hmm... you could perhaps embed some A/B testing in there, too... mind if I take your code to create a separate CFC to take a stab at it?
  • Tom Mollerus #
    Commented on 12-19-2006 at 12:27 PM
    Oops, I see that your previous article already addressed the conversions issue to some degree, but I wonder if it could be made a little more flexible by abstracting out the URLs which represent the conversion goals.
  • Commented on 12-19-2006 at 1:02 PM
    Good point. One thing I forgot to mention, and I'll do it in a follow up post, is that you can create a function to map URLs to friendly names. So test.cfm would be Test, and news.cfm?id=54 would be News: Adobe buys Microsoft.

    I'll blog about that tomorrow.
  • Commented on 12-19-2006 at 1:48 PM
    Could you not add entrypage and entrytime to the session scope in onSessionStart and use onSessionEnd to upload everything to the database, resulting in only one database call.
  • Commented on 12-19-2006 at 1:55 PM
    Sure. Although it is just 2 db calls now. But yeah, that would be even better.
  • Geoff #
    Commented on 12-20-2006 at 3:28 AM
    Just wondering is session.urltoken always totally unique? (Between reboots of the server, for example?)

    i.e. in your entryexitlog database table, have you made the "sessionid" column your unique primary key?

    I wonder if a <cfset session.myid=createuuid()> might be better.
  • Commented on 12-20-2006 at 6:23 AM
    Geoff, I kinda assume most folks use j2ee sessions, which should be - but - shoot - it is so easy to use createUUID() you might as well anyway. I was going to do an update today with the other db mod a commenter made, and I'll do this one as well.

    Everyone - GREAT feedback. Thank you!
  • duncan #
    Commented on 12-20-2006 at 8:14 AM
    some typos in your code:
    <cfset thispagethispage
    <cfset sessionsession.hits
  • Commented on 12-20-2006 at 8:19 AM
    That is the code formatter. If you click the view plain link it will go away. I've pinged the author of the code formatter but I'll ping him again to see if he has a new version.