A week or so ago I wrote a blog entry on converting ColdFusion logs to RSS feeds (ColdFusion Logs to RSS). In the article I converted lines of a log file into an RSS feed. I mentioned that it was a bit silly to convert the beginning of a log file into RSS since new data is added to the end of a file. How can we grab the end of a file?

One way would be to simply read in the entire file and just create a new array from the last 10 (or whatever) lines. Consider:

<cfset l = "/Users/ray/Desktop/cfserver.log.1">

<cfset lines = []> <cfloop index="l" file="#l#"> <cfset arrayAppend(lines,l)> </cfloop> <cfset tail = []> <cfloop index="x" from="#arrayLen(lines)#" to="#max(arrayLen(lines)-10+1,1)#" step="-1"> <cfset arrayPrepend(tail, lines[x])> </cfloop> <cfdump var="#tail#">

I begin by creating a variable, l, that points to my file. Next, I use a loop to read ine ach line of the file. This is new syntax that was added to ColdFusion 8. Notice I don't have to worry about end of line markers or anything like that. It just plain works.

Once done, I create a new array, tail, and populate it by reading backwards from the larger array. When done you get a nice array of lines representing the end of the file.

The file I used was a bit large, 13 megs, so the operation takes a few seconds to run. Could we do it quicker? In the dusty, foggy part of my brain I remember using some Java about 10 years ago to read in the file backwards. I did some digging in the API docs and figured out that I probably needed RandomAccessFile. It lets you seek to any position in a file and read in data. Using that, I wrote up a simple UDF that seeks, and then steps back one character a time. This is not very safe - I should probably try/catch the read, but it seemed to work well enough.

<cfscript> function tailFile(filename) { var line = ""; var lines = ""; var theFile = createObject("java","java.io.File").init(filename); var raFile = createObject("java","java.io.RandomAccessFile").init(theFile,"r"); var pos = theFile.length(); var c = ""; var total = 10;

if(arrayLen(arguments) gte 2) total = arguments[2]; raFile.seek(pos-1);

while( (listLen(line,chr(10)) <= total) && pos > -1) { c = raFile.read(); //if(c != -1) writeOutput("#c#=" & chr(c) & "<br/>"); if(c != -1) line &= chr(c); raFile.seek(pos--); }

line = reverse(line); lines = listToArray(line, chr(10)); arrayDeleteAt(lines,1); return lines; } </cfscript>

<cfset l = "/Users/ray/Desktop/cfserver.log.1"> <cfdump var="#tailFile(l)#">

What was interesting was the speed comparison. Using cftimer, I checked them both. Check out the results:

Pretty significant difference there. (And yes, that's ColdFire in action there.)