Using ColdFusion to get the end of a file

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#”> </code>

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)) &lt;= total) && pos &gt; -1) {
	c = raFile.read();
	//if(c != -1) writeOutput("#c#=" & chr(c) & "&lt;br/&gt;");
	if(c != -1) line &= chr(c);
	raFile.seek(pos--);	
}

line = reverse(line);
lines = listToArray(line, chr(10));
arrayDeleteAt(lines,1);
return lines; } &lt;/cfscript&gt;

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

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.)

Raymond Camden's Picture

About Raymond Camden

Raymond is a developer advocate. He focuses on JavaScript, serverless and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Comments