I posted an earlier blog item to Twitter via a bitly URL to see if I could gather better data on visits. The experiment wasn't entirely successful because I had few visitors to that blog item and fewer still via the bitly URI. If you want to see how few, the visits are shown on a Simile Timeline (scroll back a bit) and a GoogleMap . If you click on this bitly link, you will appear on the Timeline and more slowly thanks to caching on the map.
One thing I really like about working in XQuery is that data persistance is transparent. Just update the data structure in place, with no need to convert to and from a storage format. This feature is nicely illustrated by the ip geo-coding code.
As each request is logged, it now calls a function to get the ip address data. The function first checks the cache but if it misses, it calls the geo-coding service, http://ipinfodb.com, constructs an address record and adds it to the cache.
declare variable $log:ipcache := doc("/db/cache/ipcache.xml")/cache;
declare function log:geocode-ip($ip as xs:string) as element(address) {
let $address := $log:ipcache/address[@ip = $ip]
return
if ($address)
then $address
else
if (empty ($ip) or $ip eq "")
then ()
else
let $response := doc(concat("http://ipinfodb.com/ip_query.php?ip=",$ip))/Response
let $address :=
element address {
attribute ip {$ip},
attribute latitude {$response/Latitude},
attribute longitude {$response/Longitude},
attribute country {$response/CountryName},
if ($response/City) then attribute city {$response/City} else ()
}
let $update := update insert $address into $log:ipcache
return $address
};
I added this code after the first set of requests had been received, but since the same function is used in the visualisation scripts, the missing ip addresses were geo-coded the first time the scripts were run. The script to generate the Simile Timeline xml becomes:
import module namespace log = "http://www.cems.uwe.ac.uk/xmlwiki/log" at "../lib/log.xqm";
let $logfile := request:get-parameter("logfile",())
let $login := xmldb:login(..)
let $log := doc (concat(..,logfile) )/log
return
<data date-time-format = "iso8601">
{for $logrecord in $log/logrecord
let $address := log:geocode-ip($logrecord/@referer)
return
<event start="{$logrecord/@dateTime}" title="{($address/@city,$address/@country,$logrecord/@referer)[1]}">
{string($logrecord)}
</event>
}
</data>
The approach is probably OK for low levels of traffic, particularly if there are repeat visitors. I've realised that a better use of this logger is to add it to XQuery scripts whose usage I want to track. Blog visit tracking is probably best left to Google Analyics but I dont like the latency which this imposes on page loads.