Skip to content

Instantly share code, notes, and snippets.

@misterdai
Created February 2, 2012 15:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save misterdai/1724012 to your computer and use it in GitHub Desktop.
Save misterdai/1724012 to your computer and use it in GitHub Desktop.
Adobe ColdFusion, CFML based file that could be called periodically to log session activity for an application. (AdobeCF only, tested on 8.0.1 should work on 9).
<cfscript>
start = Now();
/*
=== Session Logger ===
== ChangeLog ==
* More config options.
* Supports CREATED, ACCESSED, UPDATED, REMOVED.
* Detects changes in the log keys and redisplays in log if changed.
* Windows newline ending, configurable.
* Versioning to help switch between versions / upgrade.
* Combines ACCESSED and UPDATED to avoid double entries.
* Should detect sessions that have been updated with being accessed.
== Installation ==
Configure this as a scheduled task to run as often as you require.
Note that this hasn't been tested against a large amount of sessions.
== Notes ==
* Loops through all sessions for an application
* Logs the following:
* Any sessions that no longer exist.
* Any new sessions.
* Any sessions that have been accessed.
* Currently there is no log rotation.
* Ideally, the log on session access would only occur if values have
changed. Requires hash comparison.
== Configuration ==
config.targetApp: This is the application name to session log.
config.srvScopeKey: Where this code stores information.
config.logKeys: Additional log columns (session metadata / session vals)
config.logFile: Path to the log file
*/
// config.targetApp
config = {
version = "0.5",
targetApp = "CFTTEST",
srvScopeKey = "cft_session_log",
logKeys = ["*lastAccessed", "*clientIp", "st.test", 'csv1', 'csv2', 'csv3'],
logFile = ExpandPath("./log.txt"),
newline = Chr(13) & Chr(10),
logActions = {
CREATED = true,
ACCESSED = true,
UPDATED = true,
REMOVED = true
}
};
config.logColLen = ArrayLen(config.logKeys);
function LogMsg() {
var lc = {};
lc.line = [DateFormat(Now(), "yyyy-mm-dd") & " " & TimeFormat(Now(), "HH:mm:ss")];
lc.argCount = ArrayLen(arguments);
for (lc.a = 1; lc.a <= lc.argCount; lc.a++) {
if (IsArray(arguments[lc.a])) {
lc.elCount = ArrayLen(arguments[lc.a]);
for (lc.i = 1; lc.i <= lc.elCount; lc.i++) {
ArrayAppend(lc.line, FormatCsv(arguments[lc.a][lc.i]));
}
} else {
ArrayAppend(lc.line, FormatCsv(arguments[lc.a]));
}
}
FileWrite(variables.logHandle, ArrayToList(lc.line) & variables.config.newline);
}
function FormatCsv(txt) {
var result = arguments.txt;
// Does the text contain a double quote or comma?
if (ReFind('(,|")', result)) {
// Double up any double quotes and then surround with them
result = '"' & Replace(result, '"', '""', "all") & '"';
}
return result;
}
function GetSessionInfo(session, key) {
var lc = {};
lc.result = [];
lc.mirror0 = [];
for (lc.c = 1; lc.c <= variables.config.logColLen; lc.c++) {
lc.col = variables.config.logKeys[lc.c];
switch(lc.col) {
case "*lastAccessed":
ArrayAppend(lc.result, server[variables.config.srvScopeKey].sessions[key].lastAccessed);
break;
case "*clientIp"://,*expired,*isNew,*idFromUrl":
lc.temp = server[variables.config.srvScopeKey].methods[Replace(lc.col, '*', '')].invoke(arguments.session, lc.mirror0);
if (Not StructKeyExists(lc, "temp")) lc.temp = "Unknown";
ArrayAppend(lc.result, lc.temp);
break;
case '*timeAlive':
lc.timeAlive = server[variables.config.srvScopeKey].methods.timeAlive.invoke(arguments.session, lc.mirror0);
ArrayAppend(lc.result, DateAdd('s', -lc.timeAlive / 1000, Now()));
break;
case '*idleTimeout':
lc.idleTimeout = server[variables.config.srvScopeKey].methods.idleTimeout.invoke(arguments.session, lc.mirror0);
lc.lastAccessed = server[variables.config.srvScopeKey].methods.lastAccessed.invoke(arguments.session, lc.mirror0);
ArrayAppend(lc.result, DateAdd('s', lc.idleTimeout, DateAdd('s', -lc.lastAccessed / 1000, Now())));
break;
default:
// Normal session keys caught here
if (Not StructKeyExists(lc, 'table')) {
// Grab the session scope, WITHOUT causing the last accessed time to change
lc.table = server[variables.config.srvScopeKey].methods.getTable.get(arguments.session);
}
if (IsDefined("lc.table." & lc.col)) {
lc.temp = Evaluate("lc.table." & lc.col);
if (Not StructKeyExists(lc, 'temp')) {
lc.temp = "NULL";
} else if (Not IsSimpleValue(lc.temp)) {
try {
lc.temp = "COMPLEX-" & SerializeJson(lc.temp);
} catch (Any e) {
lc.temp = "COMPLEX-CANNOT-JSON";
}
}
} else {
lc.temp = "NOTFOUND";
}
ArrayAppend(lc.result, lc.temp);
}
}
return lc.result;
}
logHandle = FileOpen(config.logFile, "append");
// Maintain data and useful in the server scope.
if (Not StructKeyExists(server, config.srvScopeKey) || StructKeyExists(url, 'reset')
|| Not StructKeyExists(server[config.srvScopeKey], 'version')
|| server[config.srvScopeKey].version != config.version) {
server[config.srvScopeKey] = {
version = config.version,
lastRun = CreateDate(1970, 1, 1),
lastHeaders = "",
sessions = {},
jSessTracker = CreateObject("java", "coldfusion.runtime.SessionTracker"),
jAppTracker = CreateObject("java", "coldfusion.runtime.ApplicationScopeTracker")
};
// Use Java reflection to grab copies of the methods we need
mirror = [];
class = mirror.getClass().forName("coldfusion.runtime.SessionScope");
server[config.srvScopeKey].methods = {
timeAlive = class.getMethod("getElapsedTime", mirror),
lastAccessed = class.getMethod("getTimeSinceLastAccess", mirror),
idleTimeout = class.getMethod("getMaxInactiveInterval", mirror),
expired = class.getMethod("expired", mirror),
clientIp = class.getMethod("getClientIp", mirror),
idFromUrl = class.getMethod("isIdFromURL", mirror),
isNew = class.getMethod("isNew", mirror),
getTable = class.getDeclaredField('mTable')
};
server[config.srvScopeKey].methods.getTable.setAccessible(true);
mirror = [];
mirror[1] = CreateObject("java", "java.lang.String").getClass();
server[config.srvScopeKey].methods.getValue = class.getMethod("getValueWIthoutChange", mirror);
}
if (server[config.srvScopeKey].lastHeaders != ArrayToList(config.logKeys)
|| GetFileInfo(config.logFile).size == 0) {
FileWrite(logHandle, "timestamp,action,sessionId," & ArrayToList(config.logKeys) & config.newline);
server[config.srvScopeKey].lastHeaders = ArrayToList(config.logKeys);
}
mirror = [];
sessions = server[config.srvScopeKey].jSessTracker.getSessionCollection(JavaCast("string", config.targetApp));
keys = StructKeyArray(sessions);
sessCount = ArrayLen(keys);
// Loop previously seen sessions, check that they still exist
for (key in server[config.srvScopeKey].sessions) {
if (Not StructKeyExists(sessions, key)) {
// Session no longer exists, log and remove
if (config.logActions.removed) logMsg(key, 'REMOVED');
StructDelete(server[config.srvScopeKey].sessions, key);
}
}
for (s = 1; s <= sessCount; s++) {
key = keys[s];
lastAccessed = DateAdd("s", Fix(-server[config.srvScopeKey].methods.lastAccessed.invoke(sessions[key], mirror) / 1000), Now());
if (Not StructKeyExists(server[config.srvScopeKey].sessions, key)) {
// Must be a new session or first logging run
server[config.srvScopeKey].sessions[key] = {
lastAccessed = lastAccessed,
hashcode = server[config.srvScopeKey].methods.getTable.get(sessions[key]).hashcode()
};
if (config.logActions.CREATED) logMsg(key, 'CREATED', getSessionInfo(sessions[key], key));
} else {
flags = [];
if (config.logActions.UPDATED) {
hashcode = server[config.srvScopeKey].methods.getTable.get(sessions[key]).hashcode();
if (server[config.srvScopeKey].sessions[key].hashcode != hashcode) {
server[config.srvScopeKey].sessions[key].lastAccessed = lastAccessed;
server[config.srvScopeKey].sessions[key].hashcode = hashcode;
ArrayAppend(flags, 'UPDATED');
}
}
if (config.logActions.ACCESSED && server[config.srvScopeKey].lastRun <= lastAccessed) {
server[config.srvScopeKey].sessions[key].lastAccessed = lastAccessed;
ArrayAppend(flags, 'ACCESSED');
}
if (ArrayLen(flags) > 0) {
logMsg(key, ArrayToList(flags, "+"), getSessionInfo(sessions[key], key));
}
}
}
FileClose(logHandle);
/*
Update the last log execution time.
We use "start" to make sure we don't miss any changes that
occurred during the log loop.
*/
//server[config.srvScopeKey].lastRun = start;
</cfscript>
<!---<cfdump var="#server[config.srvScopeKey].lastRun#" />
<cfdump var="#server[config.srvScopeKey].sessions#" />
<cfdump var="#keys#" />--->
<cfset server[config.srvScopeKey].lastRun = start />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment