diff --git a/.gitignore b/.gitignore index 94673a6..dfa8cea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /*~ /deploy/ +.DS_Store diff --git a/components/InMemoryQueue.cfc b/components/InMemoryQueue.cfc new file mode 100644 index 0000000..ee202d4 --- /dev/null +++ b/components/InMemoryQueue.cfc @@ -0,0 +1,51 @@ +component implements="Queue" { + + variables.queue = 0; + variables.maxQueueSize = 0; + variables.instanceName = ""; + + // initialize queue + function init( + required config config, + required string instanceName + ) { + variables.queue = CreateObject("java","java.util.ArrayList").init(); + variables.maxQueueSize = arguments.config.getSetting("service.maxQueueSize"); + variables.instanceName = arguments.instanceName; + return this; + } + + // adds an item to the queue + void function add( + required rawEntryBean entryBean + ) { + lock name="#lockName()#" type="exclusive" timeout="10" { + if(arrayLen(variables.queue) lte variables.maxQueueSize) { + arrayAppend(variables.queue, arguments.entryBean); + } else { + throw(message="Queue full", type="buglog.queueFull"); + } + } + } + + // retrieves all elements on the queue and leaves it empty + array function flush() { + var items = []; + lock name="#lockName()#" type="exclusive" timeout="10" { + items = duplicate(variables.queue); + variables.queue = CreateObject("java","java.util.ArrayList").Init() + } + return items; + } + + // retrieves all elements on the queue without affecting them + array function getAll() { + return duplicate(variables.queue); + } + + // generate a name for the exclusive lock used for reading and flushing a queue + private string function lockName() { + return "buglog_queue_#variables.instanceName#"; + } + +} diff --git a/components/Queue.cfc b/components/Queue.cfc new file mode 100644 index 0000000..588ca18 --- /dev/null +++ b/components/Queue.cfc @@ -0,0 +1,26 @@ +interface { + // The Queue interface describes the behavior of a queue used to store all + // incoming messages. The queue is read and processed periocally. + + // initialize queue + function init( + required config config, + required string instanceName + ); + + // Adds an item to the queue + // may throw "buglog.queueFull" exception if no item can be added + // to the queue. + void function add( + required rawEntryBean entryBean + ); + + // Retrieves all elements on the queue and leaves it empty + // Returns an array of rawEntryBean objects + array function flush(); + + // Retrieves all elements on the queue without affecting them + // Returns an array of rawEntryBean objects + array function getAll(); + +} diff --git a/components/baseRule.cfc b/components/baseRule.cfc index 708bee2..681db22 100644 --- a/components/baseRule.cfc +++ b/components/baseRule.cfc @@ -1,41 +1,155 @@ - - + + // the internal config structure is used to store configuration values + // for the current instance of this rule + variables.config = {}; + + // the scope identifies the major properties for which a rule applies (application/host/severity) + variables.scope = {}; + + variables.ID_NOT_SET = -9999999; + variables.ID_NOT_FOUND = -9999990; + variables.SCOPE_KEYS = ["application","host","severity"]; + + - - - + + + + + // on buglog 1.8 rules were defined using severityCode instead of severity + arguments.severity = structKeyExists(arguments,"severityCode") + ? arguments.severityCode + : arguments.severity; + + // arguments to the constructor are used as the Rule configuration + config = duplicate(arguments); + + // initialize scope context + for(var key in variables.SCOPE_KEYS) { + if(structKeyExists(config, key)) { + var cfg = trim(config[key]); + if(!len(cfg)) + continue; + scope[key] = { + not_in = false, + items = {} + }; + if( left(cfg,1) == "-" ) { + cfg = trim(removechars(cfg,1,1)); + scope[key]["not_in"] = true; + } + for(var item in listToArray(cfg)) { + scope[key]["items"][trim(item)] = variables.ID_NOT_SET; + } + } + } + + return this; + - + - + + var rtn = true; + updateScope(); + if(matchScope(entry) && matchCondition(entry)) { + rtn = doAction(entry); + logTrigger(entry); + } + return rtn; + + + + + + + + + - + - - + + + + + + + + + + + + + + + + // get necessary IDs + for(var key in structKeyArray(scope)) { + var items = scope[key]["items"]; + for(var item in items) { + if(items[item] == ID_NOT_SET || items[item] == ID_NOT_FOUND) { + switch(key) { + case "application": + items[item] = getApplicationID(); + break; + case "severity": + items[item] = getHostID(); + break; + case "host": + items[item] = getSeverityID(); + break; + } + } + } + } + + + + + + + var matches = true; + var memento = entry.getMemento(); + for(var key in structKeyArray(scope)) { + var matchesLine = scope[key]["not_in"]; + for(var item in scope[key]["items"]) { + var id = scope[key]["items"][item]; + if(scope[key]["not_in"]) { + matchesLine = matchesLine && memento[key & "ID"] != id; + } else { + matchesLine = matchesLine || memento[key & "ID"] == id; + } + } + matches = matches && matchesLine; + } + return matches; + + + + + + - + @@ -49,8 +163,8 @@ var bugReportURL = ""; var body = ""; - if(structKeyExists(arguments,"rawEntryBean")) { - stEntry = arguments.rawEntryBean.getMemento(); + if(structKeyExists(arguments,"entry")) { + stEntry = arguments.entry.getMemento(); } if(arguments.recipient eq "") {writeToCFLog("Missing 'recipient' email address. Cannot send alert email!"); return;} @@ -72,7 +186,8 @@
- + + @@ -80,7 +195,7 @@ - + @@ -200,7 +315,6 @@ createdOn = now()); - @@ -215,7 +329,17 @@ - + + + + + + + + + + + @@ -231,5 +355,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/bugLogListener.cfc b/components/bugLogListener.cfc index b27436c..7aa1404 100644 --- a/components/bugLogListener.cfc +++ b/components/bugLogListener.cfc @@ -1,457 +1,587 @@ - +component output="false" { - - - - - - - - - - - - - - - - var cacheTTL = 300; // timeout in minutes for cache entries - - variables.oConfig = arguments.config; // global configuration - variables.instanceName = arguments.instanceName; - - logMessage("Starting BugLogListener (#instanceName#) service..."); - - // load settings - variables.maxLogSize = arguments.config.getSetting("service.maxLogSize"); - - // load DAO Factory - variables.oDAOFactory = createObject("component","bugLog.components.DAOFactory").init( variables.oConfig ); - - // load the finder objects - variables.oAppFinder = createObject("component","bugLog.components.appFinder").init( variables.oDAOFactory.getDAO("application") ); - variables.oHostFinder = createObject("component","bugLog.components.hostFinder").init( variables.oDAOFactory.getDAO("host") ); - variables.oSeverityFinder = createObject("component","bugLog.components.severityFinder").init( variables.oDAOFactory.getDAO("severity") ); - variables.oSourceFinder = createObject("component","bugLog.components.sourceFinder").init( variables.oDAOFactory.getDAO("source") ); - variables.oUserFinder = createObject("component","bugLog.components.userFinder").init( variables.oDAOFactory.getDAO("user") ); - - // load the rule processor - variables.oRuleProcessor = createObject("component","bugLog.components.ruleProcessor").init(); - - // create cache instances - variables.oAppCache = createObject("component","bugLog.components.lib.cache.cacheService").init(50, cacheTTL, false); - variables.oHostCache = createObject("component","bugLog.components.lib.cache.cacheService").init(50, cacheTTL, false); - variables.oSeverityCache = createObject("component","bugLog.components.lib.cache.cacheService").init(10, cacheTTL, false); - variables.oSourceCache = createObject("component","bugLog.components.lib.cache.cacheService").init(5, cacheTTL, false); - variables.oUserCache = createObject("component","bugLog.components.lib.cache.cacheService").init(50, cacheTTL, false); - - // load scheduler - variables.scheduler = createObject("component","bugLog.components.schedulerService").init( variables.oConfig, variables.instanceName ); - - // load the mailer service - variables.mailerService = createObject("component","bugLog.components.MailerService").init( variables.oConfig ); - - // load rules - loadRules(); - - // configure history purging - configureHistoryPurging(); - - // configure the digest sender - configureDigest(); - - // record the date at which the service started - variables.startedOn = Now(); - - logMessage("BugLogListener Service (#instanceName#) Started"); - - - - - - - - - - var bean = arguments.entryBean; - var oEntry = 0; - var oApp = 0; - var oHost = 0; - var oSeverity = 0; - var oSource = 0; - var oDF = variables.oDAOFactory; - - // get autocreate settings - var autoCreateApp = allowAutoCreate("application"); - var autoCreateHost = allowAutoCreate("host"); - var autoCreateSeverity = allowAutoCreate("severity"); - var autoCreateSource = allowAutoCreate("source"); - - // extract related objects from bean - oApp = getApplicationFromBean( bean, autoCreateApp ); - oHost = getHostFromBean( bean, autoCreateHost ); - oSeverity = getSeverityFromBean( bean, autoCreateSeverity ); - oSource = getSourceFromBean( bean, autoCreateSource ); - - // create entry - oEntry = createObject("component","bugLog.components.entry").init( oDF.getDAO("entry") ); - oEntry.setDateTime(bean.getdateTime()); - oEntry.setMessage(bean.getmessage()); - oEntry.setApplicationID(oApp.getApplicationID()); - oEntry.setSourceID(oSource.getSourceID()); - oEntry.setSeverityID(oSeverity.getSeverityID()); - oEntry.setHostID(oHost.getHostID()); - oEntry.setExceptionMessage(bean.getexceptionMessage()); - oEntry.setExceptionDetails(bean.getexceptionDetails()); - oEntry.setCFID(bean.getcfid()); - oEntry.setCFTOKEN(bean.getcftoken()); - oEntry.setUserAgent(bean.getuserAgent()); - oEntry.setTemplatePath(bean.gettemplate_Path()); - oEntry.setHTMLReport(bean.getHTMLReport()); - oEntry.setCreatedOn(bean.getReceivedOn()); - - // save entry - oEntry.save(); - - // process rules - variables.oRuleProcessor.processRules(bean, oEntry); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // validate API - if(getConfig().getSetting("service.requireAPIKey",false)) { - if(arguments.apiKey eq "") { - throw(message="Invalid API Key",type="bugLog.invalidAPIKey"); - } - var masterKey = getConfig().getSetting("service.APIKey"); - if(arguments.apiKey neq masterKey) { - var user = getUserByAPIKey(arguments.apiKey); - if(!user.getIsAdmin() and arrayLen(user.getAllowedApplications())) { - // key is good, but since the user is a non-admin - // we need to validate the user can post bugs to the requested - // application. - var app = getApplicationFromBean( entryBean, false ); - if(!user.isApplicationAllowed(app)) { - throw(message="Application not allowed",type="applicationNotAllowed"); - } - } + */ + + variables.startedOn = 0; + variables.oDAOFactory = 0; + variables.oRuleProcessor = 0; + variables.oConfig = 0; + variables.msgLog = arrayNew(1); + variables.maxLogSize = 10; + variables.instanceName = ""; + variables.autoCreateDefault = true; + variables.queue = 0; + variables.schedulerIntervalSecs = 0; + + // this is a simple protection to avoid calling processqueue() too easily. This is NOT the apiKey setting + variables.key = "123456knowthybugs654321"; + + // timeout in minutes for cache entries + variables.CACHE_TTL = 300; + + + // Constructor + bugLogListener function init( + required config config, + required string instanceName + ) { + + variables.oConfig = arguments.config; // global configuration + variables.instanceName = arguments.instanceName; + + logMessage("Starting BugLogListener (#instanceName#) service..."); + + // load settings + variables.maxLogSize = arguments.config.getSetting("service.maxLogSize"); + + // load DAO Factory + variables.oDAOFactory = createObject("component","bugLog.components.DAOFactory").init( variables.oConfig ); + + // load the finder objects + variables.oAppFinder = createObject("component","bugLog.components.appFinder").init( variables.oDAOFactory.getDAO("application") ); + variables.oHostFinder = createObject("component","bugLog.components.hostFinder").init( variables.oDAOFactory.getDAO("host") ); + variables.oSeverityFinder = createObject("component","bugLog.components.severityFinder").init( variables.oDAOFactory.getDAO("severity") ); + variables.oSourceFinder = createObject("component","bugLog.components.sourceFinder").init( variables.oDAOFactory.getDAO("source") ); + variables.oUserFinder = createObject("component","bugLog.components.userFinder").init( variables.oDAOFactory.getDAO("user") ); + variables.oEntryFinder = createObject("component","bugLog.components.entryFinder").init( variables.oDAOFactory.getDAO("entry") ); + + // load the rule processor + variables.oRuleProcessor = createObject("component","bugLog.components.ruleProcessor").init(); + + // create cache instances + variables.oAppCache = createObject("component","bugLog.components.lib.cache.cacheService").init(50, variables.CACHE_TTL, false); + variables.oHostCache = createObject("component","bugLog.components.lib.cache.cacheService").init(50, variables.CACHE_TTL, false); + variables.oSeverityCache = createObject("component","bugLog.components.lib.cache.cacheService").init(10, variables.CACHE_TTL, false); + variables.oSourceCache = createObject("component","bugLog.components.lib.cache.cacheService").init(5, variables.CACHE_TTL, false); + variables.oUserCache = createObject("component","bugLog.components.lib.cache.cacheService").init(50, variables.CACHE_TTL, false); + + // load scheduler + variables.scheduler = createObject("component","bugLog.components.schedulerService").init( variables.oConfig, variables.instanceName ); + + // load the mailer service + variables.mailerService = createObject("component","bugLog.components.MailerService").init( variables.oConfig ); + + // configure the incoming queue + configureQueue(); + + // configure history purging + configureHistoryPurging(); + + // configure the digest sender + configureDigest(); + + // configure the rule processor + configureRuleProcessor(); + + // record the date at which the service started + variables.startedOn = Now(); + + logMessage("BugLogListener Service (#instanceName#) Started"); + + return this; + } + + // This method adds a bug report entry to the incoming queue. Bug reports must be passed as RawEntryBeans + void function logEntry( + required rawEntryBean entryBean + ) { + try { + variables.queue.add( arguments.entryBean ); + } catch(bugLog.queueFull) { + logMessage("Queue full! Discarding entry."); + } + } + + // This method adds a bug report entry into BugLog. Bug reports must be passed as RawEntryBeans + void function addEntry( + required rawEntryBean entryBean + ) { + var bean = arguments.entryBean; + var oDF = variables.oDAOFactory; + + // get autocreate settings + var autoCreateApp = allowAutoCreate("application"); + var autoCreateHost = allowAutoCreate("host"); + var autoCreateSeverity = allowAutoCreate("severity"); + var autoCreateSource = allowAutoCreate("source"); + + // extract related objects from bean + var oApp = getApplicationFromBean( bean, autoCreateApp ); + var oHost = getHostFromBean( bean, autoCreateHost ); + var oSeverity = getSeverityFromBean( bean, autoCreateSeverity ); + var oSource = getSourceFromBean( bean, autoCreateSource ); + + // create entry + var oEntry = createObject("component","bugLog.components.entry").init( oDF.getDAO("entry") ); + oEntry.setDateTime(bean.getdateTime()); + oEntry.setMessage(bean.getmessage()); + oEntry.setApplicationID(oApp.getApplicationID()); + oEntry.setSourceID(oSource.getSourceID()); + oEntry.setSeverityID(oSeverity.getSeverityID()); + oEntry.setHostID(oHost.getHostID()); + oEntry.setExceptionMessage(bean.getexceptionMessage()); + oEntry.setExceptionDetails(bean.getexceptionDetails()); + oEntry.setCFID(bean.getcfid()); + oEntry.setCFTOKEN(bean.getcftoken()); + oEntry.setUserAgent(bean.getuserAgent()); + oEntry.setTemplatePath(bean.gettemplate_Path()); + oEntry.setHTMLReport(bean.getHTMLReport()); + oEntry.setCreatedOn(bean.getReceivedOn()); + oEntry.setUUID(bean.getUUID()); + + // save entry + oEntry.save(); + } + + // Processes all entries on the queue + numeric function processQueue( + required string key + ) { + var i = 0; + var errorQueue = arrayNew(1); + var count = 0; + var dp = variables.oDAOFactory.getDataProvider(); + + if(arguments.key neq variables.key) { + logMessage("Invalid key received. Exiting."); + return 0; + } + + // get a snapshot of the queue as of right now + var myQueue = variables.queue.flush(); + if(arrayLen(myQueue) gt 0) { + logMessage("Processing queue. Queue size: #arrayLen(myQueue)#"); + for(var i=1;i lte arrayLen(myQueue);i++) { + try { + addEntry(myQueue[i]); + count++; + } catch(any e) { + // log error and save entry in another queue + arrayAppend(errorQueue,myQueue[i]); + logMessage("ERROR: #e.message# #e.detail#. Original message in entry: '#myQueue[i].getMessage()#'"); } } - - // validate application - if(!allowAutoCreate("application")) { - getApplicationFromBean( entryBean, false ); + } + + // add back all entries on the error queue to the main queue + if(arrayLen(errorQueue)) { + for(var i=1;i lte arrayLen(errorQueue);i++) { + logEntry(errorQueue[i]); } - - // validate host - if(!allowAutoCreate("host")) { - getHostFromBean( entryBean, false ); + logMessage("Failed to add #arrayLen(errorQueue)# entries. Returned failed entries to main queue. To clear up queue and discard this entries reset the listener."); + } + + return count; + } + + // Performs any clean up action required + void function shutDown() { + logMessage("Stopping BugLogListener (#instanceName#) service..."); + logMessage("Stopping scheduled tasks..."); + scheduler.removeTask("bugLogProcessQueue"); + logMessage("Processing remaining elements in queue..."); + processQueue(variables.key); + logMessage("BugLogListener service (#instanceName#) stopped."); + } + + // this method appends an entry to the messages log as well as displays the message on the console + void function logMessage( + required string msg + ) { + var System = CreateObject('java','java.lang.System'); + var txt = timeFormat(now(), 'HH:mm:ss') & ": " & msg; + System.out.println("BugLogListener: " & txt); + lock name="bugLogListener_logMessage" type="exclusive" timeout="10" { + if(arrayLen(variables.msgLog) gt variables.maxLogSize) { + arrayDeleteAt(variables.msgLog, ArrayLen(variables.msgLog)); } - - // validte severity - if(!allowAutoCreate("severity")) { - getSeverityFromBean( entryBean, false ); + arrayPrepend(variables.msgLog,txt); + } + } + + // Validates that a bug report is valid, if not throws an error. + // This validates API key (if needed) and auto-create settings + boolean function validate( + required rawEntryBean entryBean, + required string apiKey + ) { + // validate API + if(getConfig().getSetting("service.requireAPIKey",false)) { + if(arguments.apiKey eq "") { + throw(message="Invalid API Key", type="bugLog.invalidAPIKey"); } - - // validate source - if(!allowAutoCreate("source")) { - getSourceFromBean( entryBean, false ); + var masterKey = getConfig().getSetting("service.APIKey"); + if(arguments.apiKey neq masterKey) { + var user = getUserByAPIKey(arguments.apiKey); + if(!user.getIsAdmin() and arrayLen(user.getAllowedApplications())) { + // key is good, but since the user is a non-admin + // we need to validate the user can post bugs to the requested + // application. + var app = getApplicationFromBean( entryBean, false ); + if(!user.isApplicationAllowed(app)) { + throw(message="Application not allowed",type="applicationNotAllowed"); + } + } + } + } + + // validate application + if(!allowAutoCreate("application")) { + getApplicationFromBean( entryBean, false ); + } + + // validate host + if(!allowAutoCreate("host")) { + getHostFromBean( entryBean, false ); + } + + // validte severity + if(!allowAutoCreate("severity")) { + getSeverityFromBean( entryBean, false ); + } + + // validate source + if(!allowAutoCreate("source")) { + getSourceFromBean( entryBean, false ); + } + + return true; + } + + // Reloads all rules + void function reloadRules() { + loadRules(); + } + + // Return the contents of the queue for inspection + array function getEntryQueue() { + return variables.queue.getAll(); + } + + // Processes rules for new added entries + numeric function processRules( + required string key + ) { + // validate key + if(arguments.key neq variables.key) { + logMessage("Invalid key received. Exiting."); + return 0; + } + + // get rows that have not been processed yet + var qryEntries = variables.oEntryFinder.search( + isProcessed=False + ); + + if(qryEntries.recordCount) + logMessage("Processing rules. Queue size: #qryEntries.recordCount#"); + + // convert to an array of entry beans + var entries = oEntryFinder.createBeansFromQuery(qryEntries); + + // process rules for all the entries + variables.oRuleProcessor.processRules( entries ); + + } + + + // Getter functions + config function getConfig() {return variables.oConfig;} + string function getInstanceName() {return variables.instanceName;} + array function getMessageLog() {return variables.msgLog;} + string function getKey() {return variables.key;} + date function getStartedOn() {return variables.startedOn;} + + + + /***** Private Methods ******/ + + // Uses the information on the rawEntryBean to retrieve the corresponding Application object + private app function getApplicationFromBean( + required rawEntryBean entryBean, + boolean createIfNeeded = false + ) { + var bean = arguments.entryBean; + var oApp = 0; + var oDF = variables.oDAOFactory; + + var key = bean.getApplicationCode(); + try { + // first we try to get it from the cache + oApp = variables.oAppCache.retrieve( key ); + + } catch(cacheService.itemNotFound e) { + // entry not in cache, so we get it from DB + try { + oApp = variables.oAppFinder.findByCode( key ); + + } catch(appFinderException.ApplicationCodeNotFound e) { + // code does not exist, so we need to create it (if autocreate enabled) + if(!arguments.createIfNeeded) throw(message="Invalid Application",type="invalidApplication"); + oApp = createObject("component","bugLog.components.app").init( oDF.getDAO("application") ); + oApp.setCode( key ); + oApp.setName( key ); + oApp.save(); } - return true; - - + // store entry in cache + variables.oAppCache.store( key, oApp ); + } - - - + return oApp; + } - - - + // Uses the information on the rawEntryBean to retrieve the corresponding Host object + private host function getHostFromBean( + required rawEntryBean entryBean, + boolean createIfNeeded = false + ) { + var bean = arguments.entryBean; + var oHost = 0; + var oDF = variables.oDAOFactory; - + var key = bean.getHostName(); - - - - - var key = ""; - var bean = arguments.entryBean; - var oApp = 0; - var oDF = variables.oDAOFactory; + try { + // first we try to get it from the cache + oHost = variables.oHostCache.retrieve( key ); - key = bean.getApplicationCode(); + } catch(cacheService.itemNotFound e) { + // entry not in cache, so we get it from DB try { - // first we try to get it from the cache - oApp = variables.oAppCache.retrieve( key ); + oHost = variables.oHostFinder.findByName( key ); + + } catch(hostFinderException.HostNameNotFound e) { + // code does not exist, so we need to create it (if autocreate enabled) + if(!arguments.createIfNeeded) throw(message="Invalid Host",type="invalidHost"); + oHost = createObject("component","bugLog.components.host").init( oDF.getDAO("host") ); + oHost.setHostName(key); + oHost.save(); + } - } catch(cacheService.itemNotFound e) { - // entry not in cache, so we get it from DB - try { - oApp = variables.oAppFinder.findByCode( key ); - - } catch(appFinderException.ApplicationCodeNotFound e) { - // code does not exist, so we need to create it (if autocreate enabled) - if(!arguments.createIfNeeded) throw(message="Invalid Application",type="invalidApplication"); - oApp = createObject("component","bugLog.components.app").init( oDF.getDAO("application") ); - oApp.setCode( key ); - oApp.setName( key ); - oApp.save(); - } + // store entry in cache + variables.oHostCache.store( key, oHost ); + } - // store entry in cache - variables.oAppCache.store( key, oApp ); - } - - - + return oHost; + } + + // Uses the information on the rawEntryBean to retrieve the corresponding Severity object + private severity function getSeverityFromBean( + required rawEntryBean entryBean, + boolean createIfNeeded = false + ) { + var bean = arguments.entryBean; + var oSeverity = 0; + var oDF = variables.oDAOFactory; - - - - - var key = ""; - var bean = arguments.entryBean; - var oHost = 0; - var oDF = variables.oDAOFactory; + var key = bean.getSeverityCode(); - key = bean.getHostName(); + try { + // first we try to get it from the cache + oSeverity = variables.oSeverityCache.retrieve( key ); + } catch(cacheService.itemNotFound e) { + // entry not in cache, so we get it from DB try { - // first we try to get it from the cache - oHost = variables.oHostCache.retrieve( key ); + oSeverity = variables.oSeverityFinder.findByCode( key ); + + } catch(severityFinderException.codeNotFound e) { + // code does not exist, so we need to create it (if autocreate enabled) + if(!arguments.createIfNeeded) throw(message="Invalid Severity",type="invalidSeverity"); + oSeverity = createObject("component","bugLog.components.severity").init( oDF.getDAO("severity") ); + oSeverity.setCode( key ); + oSeverity.setName( key ); + oSeverity.save(); + } - } catch(cacheService.itemNotFound e) { - // entry not in cache, so we get it from DB - try { - oHost = variables.oHostFinder.findByName( key ); - - } catch(hostFinderException.HostNameNotFound e) { - // code does not exist, so we need to create it (if autocreate enabled) - if(!arguments.createIfNeeded) throw(message="Invalid Host",type="invalidHost"); - oHost = createObject("component","bugLog.components.host").init( oDF.getDAO("host") ); - oHost.setHostName(key); - oHost.save(); - } + // store entry in cache + variables.oSeverityCache.store( key, oSeverity ); + } - // store entry in cache - variables.oHostCache.store( key, oHost ); - } - - - + return oSeverity; + } - - - - - var key = ""; - var bean = arguments.entryBean; - var oSeverity = 0; - var oDF = variables.oDAOFactory; + // Finds a user object using by its API Key + private user function getUserByAPIKey( + required string apiKey + ) { + var oUser = 0; - key = bean.getSeverityCode(); + try { + // first we try to get it from the cache + oUser = variables.oUserCache.retrieve( apiKey ); + } catch(cacheService.itemNotFound e) { + // entry not in cache, so we get it from DB try { - // first we try to get it from the cache - oSeverity = variables.oSeverityCache.retrieve( key ); + oUser = variables.oUserFinder.findByAPIKey( apiKey ); - } catch(cacheService.itemNotFound e) { - // entry not in cache, so we get it from DB - try { - oSeverity = variables.oSeverityFinder.findByCode( key ); - - } catch(severityFinderException.codeNotFound e) { - // code does not exist, so we need to create it (if autocreate enabled) - if(!arguments.createIfNeeded) throw(message="Invalid Severity",type="invalidSeverity"); - oSeverity = createObject("component","bugLog.components.severity").init( oDF.getDAO("severity") ); - oSeverity.setCode( key ); - oSeverity.setName( key ); - oSeverity.save(); - } + var qryUserApps = oDAOFactory.getDAO("userApplication").search(userID = oUser.getUserID()); + var apps = oAppFinder.findByIDList(valueList(qryUserApps.applicationID)); + oUser.setAllowedApplications(apps); - // store entry in cache - variables.oSeverityCache.store( key, oSeverity ); + } catch(userFinderException.usernameNotFound e) { + // code does not exist, so we need to create it (if autocreate enabled) + throw(message="Invalid API Key",type="bugLog.invalidAPIKey"); } - - - - - - - var oUser = 0; + // store entry in cache + variables.oUserCache.store( key, oUser ); + } - try { - // first we try to get it from the cache - oUser = variables.oUserCache.retrieve( apiKey ); + return oUser; + } - } catch(cacheService.itemNotFound e) { - // entry not in cache, so we get it from DB - try { - oUser = variables.oUserFinder.findByAPIKey( apiKey ); + // Uses the information on the rawEntryBean to retrieve the corresponding Source object + private source function getSourceFromBean( + required rawEntryBean entryBean, + boolean createIfNeeded = false + ) { + var bean = arguments.entryBean; + var oSource = 0; + var oDF = variables.oDAOFactory; - var qryUserApps = oDAOFactory.getDAO("userApplication").search(userID = oUser.getUserID()); - var apps = oAppFinder.findByIDList(valueList(qryUserApps.applicationID)); - oUser.setAllowedApplications(apps); + var key = bean.getSourceName(); - } catch(sourceFinderException.usernameNotFound e) { - // code does not exist, so we need to create it (if autocreate enabled) - throw(message="Invalid API Key",type="bugLog.invalidAPIKey"); - } + try { + // first we try to get it from the cache + oSource = variables.oSourceCache.retrieve( key ); - // store entry in cache - variables.oUserCache.store( key, oUser ); + } catch(cacheService.itemNotFound e) { + // entry not in cache, so we get it from DB + try { + oSource = variables.oSourceFinder.findByName( key ); + + } catch(sourceFinderException.codeNotFound e) { + // code does not exist, so we need to create it (if autocreate enabled) + if(!arguments.createIfNeeded) throw(message="Invalid Source",type="invalidSource"); + oSource = createObject("component","bugLog.components.source").init( oDF.getDAO("source") ); + oSource.setName( key ); + oSource.save(); } - return oUser; - - + // store entry in cache + variables.oSourceCache.store( key, oSource ); + } - - - - - var key = ""; - var bean = arguments.entryBean; - var oSource = 0; - var oDF = variables.oDAOFactory; + return oSource; + } - key = bean.getSourceName(); + // This method loads the rules into the rule processor + private void function loadRules() { + var oRule = 0; + var thisRule = 0; - try { - // first we try to get it from the cache - oSource = variables.oSourceCache.retrieve( key ); + // clear all existing rules + variables.oRuleProcessor.flushRules(); - } catch(cacheService.itemNotFound e) { - // entry not in cache, so we get it from DB - try { - oSource = variables.oSourceFinder.findByName( key ); - - } catch(sourceFinderException.codeNotFound e) { - // code does not exist, so we need to create it (if autocreate enabled) - if(!arguments.createIfNeeded) throw(message="Invalid Source",type="invalidSource"); - oSource = createObject("component","bugLog.components.source").init( oDF.getDAO("source") ); - oSource.setName( key ); - oSource.save(); - } + // get the rule definitions from the extensions service + var dao = variables.oDAOFactory.getDAO("extension"); + var oExtensionsService = createObject("component","bugLog.components.extensionsService").init( dao ); + var aRules = oExtensionsService.getRules(); + + // create rule objects and load them into the rule processor + for(var i=1; i lte arrayLen(aRules);i=i+1) { + thisRule = aRules[i]; + + if(thisRule.enabled) { + oRule = + thisRule.instance + .setListener(this) + .setDAOFactory( variables.oDAOFactory ) + .setMailerService( variables.mailerService ); - // store entry in cache - variables.oSourceCache.store( key, oSource ); + // add rule to processor + variables.oRuleProcessor.addRule(oRule); } - - - - - - - var oRule = 0; - var oExtensionsService = 0; - var aRules = arrayNew(1); - var i = 0; - var dao = 0; - var thisRule = 0; - - // clear all existing rules - variables.oRuleProcessor.flushRules(); - - // get the rule definitions from the extensions service - dao = variables.oDAOFactory.getDAO("extension"); - oExtensionsService = createObject("component","bugLog.components.extensionsService").init( dao ); - aRules = oExtensionsService.getRules(); - - // create rule objects and load them into the rule processor - for(i=1; i lte arrayLen(aRules);i=i+1) { - thisRule = aRules[i]; - - if(thisRule.enabled) { - oRule = - thisRule.instance - .setListener(this) - .setDAOFactory( variables.oDAOFactory ) - .setMailerService( variables.mailerService ); - - // add rule to processor - variables.oRuleProcessor.addRule(oRule); - } + } + } + + private boolean function allowAutoCreate( + required string entityType + ) { + var setting = "autocreate." & arguments.entityType; + return getConfig().getSetting(setting, variables.autoCreateDefault); + } + + private void function configureHistoryPurging() { + var enabled = getConfig().getSetting("purging.enabled"); + if( enabled ) { + scheduler.setupTask("bugLogPurgeHistory", + "util/purgeHistory.cfm", + "03:00", + "daily"); + } else { + scheduler.removeTask("bugLogPurgeHistory"); + } + } + + private void function configureDigest() { + var enabled = oConfig.getSetting("digest.enabled"); + var interval = oConfig.getSetting("digest.schedulerIntervalHours"); + var startTime = oConfig.getSetting("digest.schedulerStartTime"); + + if( enabled ) { + scheduler.setupTask("bugLogSendDigest", + "util/sendDigest.cfm", + startTime, + interval*3600); + } else { + scheduler.removeTask("bugLogSendDigest"); + } + } + + private void function configureQueue() { + // setup queueing service + var queueClass = oConfig.getSetting("service.queueClass"); + variables.queue = createObject("component",queueClass).init(oConfig, instanceName); + + // create a task to process the queue periodically + var schedulerIntervalSecs = oConfig.getSetting("service.schedulerIntervalSecs"); + scheduler.setupTask("bugLogProcessQueue", + "util/processQueue.cfm", + "00:00", + schedulerIntervalSecs, + [{name="key",value=variables.KEY}]); + } + + private void function configureRuleProcessor() { + // load rule instances + loadRules(); + } + + private any function parseInternalException( + required any e + ) { + // parses an internal error into a raw entry bean for logging + var error = createObject("component", "bugLog.components.rawEntryBean"); + error.setMessage(e.message); + error.setApplicationCode("BugLogListener"); + error.setSeverityCode("ERROR"); + error.setExceptionMessage(e.message); + error.setExceptionDetails(e.detail); + error.setHostName(CGI.SERVER_NAME); + + var htmlReport = "Type: " & e.type & "

"; + if(structKeyExists(e,"tagContext")) { + htmlReport &= "Stack Trace:
"; + for(var line in e.tagContext) { + htmlReport &= "#line.template# (#line.line#)
"; } -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ } + error.setHTMLReport(htmlReport); + + return error; + } + +} + diff --git a/components/bugLogListenerAsync.cfc b/components/bugLogListenerAsync.cfc deleted file mode 100644 index 41a41fd..0000000 --- a/components/bugLogListenerAsync.cfc +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - - - - - - - // initialize variables and read settings - //variables.queue = arrayNew(1); - variables.queue = CreateObject("java","java.util.ArrayList").Init(); - variables.msgLog = arrayNew(1); - variables.maxQueueSize = arguments.config.getSetting("service.maxQueueSize"); - variables.schedulerIntervalSecs = arguments.config.getSetting("service.schedulerIntervalSecs"); - - // do the normal initialization - super.init( arguments.config, arguments.instanceName ); - - // start scheduler - startScheduler(); - - return this; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ---> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/components/bugLogListenerRemote.cfc b/components/bugLogListenerRemote.cfc deleted file mode 100644 index e39a994..0000000 --- a/components/bugLogListenerRemote.cfc +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/components/db/entryDAO.cfc b/components/db/entryDAO.cfc index 474ef21..fdf40b0 100644 --- a/components/db/entryDAO.cfc +++ b/components/db/entryDAO.cfc @@ -17,6 +17,9 @@ + + + diff --git a/components/entry.cfc b/components/entry.cfc index 4aff947..f03f521 100644 --- a/components/entry.cfc +++ b/components/entry.cfc @@ -1,92 +1,145 @@ - - - - variables.oDAO = 0; - variables.instance.ID = 0; - variables.instance.mydateTime = now(); - variables.instance.message = ""; - variables.instance.applicationID = 0; - variables.instance.sourceID = 0; - variables.instance.severityID = 0; - variables.instance.hostID = 0; - variables.instance.exceptionMessage = ""; - variables.instance.exceptionDetails = ""; - variables.instance.CFID = ""; - variables.instance.CFTOKEN = ""; - variables.instance.userAgent = ""; - variables.instance.templatePath = ""; - variables.instance.HTMLReport = ""; - variables.instance.createdOn = now(); - - function setEntryID(data) {variables.instance.ID = arguments.data;} - function setDateTime(data) {variables.instance.mydateTime = arguments.data;} - function setMessage(data) {variables.instance.message = left(arguments.data,500);} - function setApplicationID(data) {variables.instance.applicationID = arguments.data;} - function setSourceID(data) {variables.instance.sourceID = arguments.data;} - function setSeverityID(data) {variables.instance.severityID = arguments.data;} - function setHostID(data) {variables.instance.hostID = arguments.data;} - function setExceptionMessage(data) {variables.instance.exceptionMessage = left(arguments.data,500);} - function setExceptionDetails(data) {variables.instance.exceptionDetails = left(arguments.data,5000);} - function setCFID(data) {variables.instance.CFID = left(arguments.data,255);} - function setCFTOKEN(data) {variables.instance.CFTOKEN = left(arguments.data,255);} - function setUserAgent(data) {variables.instance.userAgent = left(arguments.data,500);} - function setTemplatePath(data) {variables.instance.templatePath = left(arguments.data,500);} - function setHTMLReport(data) {variables.instance.HTMLReport = arguments.data;} - function setCreatedOn(data) {variables.instance.createdOn = arguments.data;} +component output="false" { + + variables.oDAO = 0; + + variables.instance = { + ID = 0, + mydateTime = now(), + message = "", + applicationID = 0, + applicationCode = "", + sourceID = 0, + severityID = 0, + severityCode = "", + hostID = 0, + hostName = "", + exceptionMessage = "", + exceptionDetails = "", + CFID = "", + CFTOKEN = "", + userAgent = "", + templatePath = "", + HTMLReport = "", + createdOn = now(), + updatedOn = now(), + UUID = "", + isProcessed = 0 + }; - function getEntryID() {return variables.instance.ID;} - function getDateTime() {return variables.instance.mydateTime;} - function getMessage() {return variables.instance.message;} - function getApplicationID() {return variables.instance.applicationID;} - function getSourceID() {return variables.instance.sourceID;} - function getSeverityID() {return variables.instance.severityID;} - function getHostID() {return variables.instance.hostID;} - function getExceptionMessage() {return variables.instance.exceptionMessage;} - function getExceptionDetails() {return variables.instance.exceptionDetails;} - function getCFID() {return variables.instance.CFID;} - function getCFTOKEN() {return variables.instance.CFTOKEN;} - function getUserAgent() {return variables.instance.userAgent;} - function getTemplate_Path() {return variables.instance.templatePath;} - function getHTMLReport() {return variables.instance.HTMLReport;} - function getCreatedOn() {return variables.instance.createdOn;} + function setEntryID(data) {variables.instance.ID = arguments.data; return this;} + function setDateTime(data) {variables.instance.mydateTime = arguments.data; return this;} + function setMessage(data) {variables.instance.message = left(arguments.data,500); return this;} + function setApplicationID(data) {variables.instance.applicationID = arguments.data; return this;} + function setSourceID(data) {variables.instance.sourceID = arguments.data; return this;} + function setSeverityID(data) {variables.instance.severityID = arguments.data; return this;} + function setHostID(data) {variables.instance.hostID = arguments.data; return this;} + function setExceptionMessage(data) {variables.instance.exceptionMessage = left(arguments.data,500); return this;} + function setExceptionDetails(data) {variables.instance.exceptionDetails = left(arguments.data,5000); return this;} + function setCFID(data) {variables.instance.CFID = left(arguments.data,255); return this;} + function setCFTOKEN(data) {variables.instance.CFTOKEN = left(arguments.data,255); return this;} + function setUserAgent(data) {variables.instance.userAgent = left(arguments.data,500); return this;} + function setTemplatePath(data) {variables.instance.templatePath = left(arguments.data,500); return this;} + function setHTMLReport(data) {variables.instance.HTMLReport = arguments.data; return this;} + function setCreatedOn(data) {variables.instance.createdOn = arguments.data; return this;} + function setUpdatedOn(data) {variables.instance.updatedOn = arguments.data; return this;} + function setIsProcessed(data) {variables.instance.isProcessed = arguments.data; return this;} + function setUUID(data) {variables.instance.UUID = arguments.data; return this;} + function setApplicationCode(data) {variables.instance.applicationCode = arguments.data; return this;} + function setSeverityCode(data) {variables.instance.severityCode = arguments.data; return this;} + function setHostName(data) {variables.instance.hostName = arguments.data; return this;} + + function getEntryID() {return variables.instance.ID;} + function getDateTime() {return variables.instance.mydateTime;} + function getMessage() {return variables.instance.message;} + function getApplicationID() {return variables.instance.applicationID;} + function getSourceID() {return variables.instance.sourceID;} + function getSeverityID() {return variables.instance.severityID;} + function getHostID() {return variables.instance.hostID;} + function getExceptionMessage() {return variables.instance.exceptionMessage;} + function getExceptionDetails() {return variables.instance.exceptionDetails;} + function getCFID() {return variables.instance.CFID;} + function getCFTOKEN() {return variables.instance.CFTOKEN;} + function getUserAgent() {return variables.instance.userAgent;} + function getTemplate_Path() {return variables.instance.templatePath;} + function getHTMLReport() {return variables.instance.HTMLReport;} + function getCreatedOn() {return variables.instance.createdOn;} + function getUpdatedOn() {return variables.instance.updatedOn;} + function getIsProcessed() {return variables.instance.isProcessed;} + function getUUID() {return variables.instance.UUID;} + function getApplicationCode() {return variables.instance.applicationCode;} + function getSeverityCode() {return variables.instance.severityCode;} + function getHostName() {return variables.instance.hostName;} - function getID() {return variables.instance.ID;} - + function getID() {return variables.instance.ID;} + + entry function init( + required bugLog.components.db.entryDAO dao + ) { + variables.oDAO = arguments.dao; + return this; + } - - - - - + void function save(){ + variables.instance.updatedOn = Now(); + variables.instance.ID = variables.oDAO.save(argumentCollection = variables.instance); + } - - - - - + // Returns the application object + app function getApplication() { + var dp = variables.oDAO.getDataProvider(); + var dao = createObject("component","bugLog.components.db.applicationDAO").init( dp ); + return createObject("component","bugLog.components.appFinder") + .init( dao ) + .findByID(variables.instance.applicationID); + } - - - - - + // Returns the host object + host function getHost() { + var dp = variables.oDAO.getDataProvider(); + var dao = createObject("component","bugLog.components.db.hostDAO").init( dp ); + return createObject("component","bugLog.components.hostFinder") + .init( dao ) + .findByID(variables.instance.hostID); + } - - - - - + // Returns the source object + source function getSource() { + var dp = variables.oDAO.getDataProvider(); + var dao = createObject("component","bugLog.components.db.sourceDAO").init( dp ); + return createObject("component","bugLog.components.sourceFinder") + .init( dao ) + .findByID(variables.instance.sourceID); + } - - - - - + // Returns the severity object + severity function getSeverity() { + var dp = variables.oDAO.getDataProvider(); + var dao = createObject("component","bugLog.components.db.severityDAO").init( dp ); + return createObject("component","bugLog.components.severityFinder") + .init( dao ) + .findByID(variables.instance.severityID); + } - - - - - - - \ No newline at end of file + struct function getMemento() { + return variables.instance; + } + + entry function setMemento( + required struct data + ) { + variables.instance = duplicate(arguments.data); + return this; + } + + void function flagAsProcessed() { + var id = getID(); + if(id) { + variables.instance.updatedOn = Now(); + variables.oDAO.save( + id = id, + isProcessed = 1 + ); + } + } + +} diff --git a/components/entryFinder.cfc b/components/entryFinder.cfc index d865327..eed9dd0 100644 --- a/components/entryFinder.cfc +++ b/components/entryFinder.cfc @@ -4,34 +4,55 @@ var qry = variables.oDAO.get(arguments.id); - var o = 0; + var beans = createBeansFromQuery(qry); - if(qry.recordCount gt 0) { - o = createObject("component","bugLog.components.entry").init( variables.oDAO ); - o.setEntryID(qry.entryID); - o.setDateTime(qry.mydateTime); - o.setMessage(qry.message); - o.setApplicationID(qry.ApplicationID); - o.setSourceID(qry.SourceID); - o.setSeverityID(qry.SeverityID); - o.setHostID(qry.HostID); - o.setExceptionMessage(qry.exceptionMessage); - o.setExceptionDetails(qry.exceptionDetails); - o.setCFID(qry.cfid); - o.setCFTOKEN(qry.cftoken); - o.setUserAgent(qry.userAgent); - o.setTemplatePath(qry.templatePath); - o.setHTMLReport(qry.HTMLReport); - o.setCreatedOn(qry.createdOn); - return o; + if(arrayLen(beans)) { + return beans[1]; } else { throw("ID not found","entryFinderException.IDNotFound"); } + + + + var rtn = []; + for(var i=1;i lte entries.recordCount;i++) { + var o = createObject("component","bugLog.components.entry").init( variables.oDAO ) + .setEntryID(entries.entryID[i]) + .setDateTime(entries.mydateTime[i]) + .setMessage(entries.message[i]) + .setApplicationID(entries.ApplicationID[i]) + .setSourceID(entries.SourceID[i]) + .setSeverityID(entries.SeverityID[i]) + .setHostID(entries.HostID[i]) + .setExceptionMessage(entries.exceptionMessage[i]) + .setExceptionDetails(entries.exceptionDetails[i]) + .setCFID(entries.cfid[i]) + .setCFTOKEN(entries.cftoken[i]) + .setUserAgent(entries.userAgent[i]) + .setTemplatePath(entries.templatePath[i]) + .setCreatedOn(entries.createdOn[i]) + .setUpdatedOn(entries.updatedOn[i]) + .setIsProcessed(entries.isProcessed[i]) + .setUUID(entries.UUID[i]); + if(structKeyExists(entries,"HTMLReport")) + o.setHTMLReport(entries.HTMLReport[i]); + if(structKeyExists(entries,"ApplicationCode")) + o.setApplicationCode(entries.applicationCode[i]); + if(structKeyExists(entries,"SeverityCode")) + o.setSeverityCode(entries.severityCode[i]); + if(structKeyExists(entries,"HostName")) + o.setHostName(entries.hostName[i]); + rtn.add(o); + } + return rtn; + + + - + @@ -46,11 +67,18 @@ + + + + + + + @@ -61,9 +89,21 @@ + + SELECT + ApplicationCode, ApplicationID, + HostName, HostID, + Message, + COUNT(entryID) AS bugCount, + MAX(createdOn) as createdOn, + MAX(entryID) AS EntryID, + MAX(severityCode) AS SeverityCode + FROM ( + SELECT e.entryID, e.message, e.cfid, e.cftoken, e.mydateTime, e.exceptionMessage, e.exceptionDetails, e.templatePath, e.userAgent, a.code as ApplicationCode, h.hostName, s.code AS SeverityCode, - src.name AS SourceName, e.applicationID, e.hostID, e.severityID, e.sourceID, e.createdOn, + src.name AS SourceName, e.applicationID, e.hostID, e.severityID, e.sourceID, e.createdOn, + e.updatedOn, e.isProcessed, e.UUID, datePart(year, e.createdOn) as entry_year, @@ -170,7 +210,20 @@ WHERE userID = ) - ORDER BY e.createdOn DESC, entryID DESC + + AND e.UUID = + + + AND e.isProcessed = + + + ) a + GROUP BY + ApplicationCode, ApplicationID, + HostName, HostID, + Message + + ORDER BY createdOn DESC, entryID DESC diff --git a/components/hq/appService.cfc b/components/hq/appService.cfc index cfa8484..d46704d 100644 --- a/components/hq/appService.cfc +++ b/components/hq/appService.cfc @@ -124,6 +124,9 @@ + + + var oEntryFinder = 0; var qry = 0; @@ -189,7 +192,16 @@ return entry; - + + + + + + var qryEntry = variables.oEntryDAO.search( uuid = arguments.uuid ); + return getEntry(qryEntry.entryID, arguments.user); + + + @@ -469,7 +481,6 @@ - var maxEntries = 20; @@ -477,8 +488,6 @@ // search bug reports var qryEntries = searchEntries(argumentCollection = criteria); - if(summary) - qryEntries = applyGroupings(qryEntries, criteria.groupByApp, criteria.groupByHost); // build rss feed var meta = { @@ -490,7 +499,7 @@ for(var i=1;i lte min(maxEntries, qryEntries.recordCount);i=i+1) { queryAddRow(data,1); - if(summary) { + if(criteria.groupByMsg) { querySetCell(data,"title", qryEntries.message[i] & " (" & qryEntries.bugCount[i] & ")"); querySetCell(data,"body", composeMessage(qryEntries.createdOn[i], qryEntries.applicationCode[i], diff --git a/components/listener.cfc b/components/listener.cfc new file mode 100644 index 0000000..1ffa5a5 --- /dev/null +++ b/components/listener.cfc @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + // get listener service wrapper + var serviceLoader = createObject("component", "service").init( instanceName = variables.instanceName ); + + // get handle to bugLogListener service + var bugLogListener = serviceLoader.getService(); + + // create entry bean + var rawEntry = createObject("component","rawEntryBean") + .init() + .setDateTime(arguments.dateTime) + .setMessage(arguments.message) + .setApplicationCode(arguments.applicationCode) + .setSourceName(arguments.source) + .setSeverityCode(arguments.severityCode) + .setHostName(arguments.hostName) + .setExceptionMessage(arguments.exceptionMessage) + .setExceptionDetails(arguments.exceptionDetails) + .setCFID(arguments.cfid) + .setCFTOKEN(arguments.cftoken) + .setUserAgent(arguments.userAgent) + .setTemplatePath(arguments.templatePath) + .setHTMLReport(arguments.HTMLReport) + .setReceivedOn(now()); + + // validate Entry + bugLogListener.validate(rawEntry, arguments.APIKey); + + // log entry + bugLogListener.logEntry(rawEntry); + + return rawEntry; + + + + diff --git a/components/rawEntryBean.cfc b/components/rawEntryBean.cfc index ffe2456..8bac570 100644 --- a/components/rawEntryBean.cfc +++ b/components/rawEntryBean.cfc @@ -17,6 +17,7 @@ variables.instance.HTMLReport = ""; variables.instance.sourceName = ""; variables.instance.receivedOn = now(); + variables.instance.UUID = createUUID(); function setDateTime(data) {variables.instance.dateTime = arguments.data; return this;} function setMessage(data) {variables.instance.message = arguments.data; return this;} @@ -49,6 +50,7 @@ function getHTMLReport() {return variables.instance.HTMLReport;} function getSourceName() {return variables.instance.sourceName;} function getReceivedOn() {return variables.instance.receivedOn;} + function getUUID() {return variables.instance.UUID;} @@ -65,4 +67,4 @@ - \ No newline at end of file + diff --git a/components/ruleProcessor.cfc b/components/ruleProcessor.cfc index 1c22d99..0e3b242 100644 --- a/components/ruleProcessor.cfc +++ b/components/ruleProcessor.cfc @@ -1,83 +1,95 @@ - +component { + // This component is in charge of evaluating a set of rules - - - + variables.aRules = []; + variables.buglogClient = 0; + variables.bugLogListenerEndpoint = "bugLog.listeners.bugLogListenerWS"; - - - - - - - - - - - - - - - - - - - - - + // constructor + ruleProcessor function init() { + variables.aRules = []; + variables.buglogClient = createObject("component","bugLog.client.bugLogService").init(variables.bugLogListenerEndpoint); + return this; + } - - - - + // adds a rule to be processed + void function addRule( + required baseRule rule + ) { + arrayAppend(variables.aRules, arguments.rule); + } - - - - - + // executes all rules for all entry bens in the given array + void function processRules( + required array entries + ) { + // process 'begin' event + process("queueStart", entries); - - - + // process rules for each entry + for(var oEntry in entries) { + process("rule", oEntry); + oEntry.flagAsProcessed(); + } + // process 'end' event + process("queueEnd", entries); + } - - - - - var rtn = false; - var ruleName = ""; - var thisRule = 0; - - for(var i=1;i lte arrayLen(variables.aRules);i=i+1) { - ruleName = "Rule #i#"; // a temporary name just in case the getMetaData() call fails - thisRule = variables.aRules[i]; - try { - ruleName = getMetaData(thisRule).name; - - // process rule - rtn = invokeRule(thisRule, arguments.method, args); + // clears all the loaded rules + void function flushRules() { + variables.aRules = arrayNew(1); + } - // if rule returns false, then that means that no more rules will be processed, so we exit - if(not rtn) break; - } catch(any e) { - // if an error occurs while a rule executes, then write to normal log file - buglogClient.notifyService("RuleProcessor Error: #e.message#", e); - writeToCFLog(ruleName & ": " & e.message & e.detail); + /** Private Methods **/ + + // internal function to process all rules + private void function process( + required string event, + required any arg + ) { + var rtn = false; + var ruleName = ""; + var thisRule = 0; + + for(var i=1; i lte arrayLen(variables.aRules); i++) { + ruleName = "Rule #i#"; // a temporary name just in case the getMetaData() call fails + thisRule = variables.aRules[i]; + try { + ruleName = getMetaData(thisRule).name; + + // process rule + switch(arguments.event) { + case "queueStart": + rtn = thisRule.processQueueStart(arg); + break; + case "rule": + rtn = thisRule.processRule(arg); + break; + case "queueEnd": + rtn = thisRule.processQueueEnd(arg); + break; } + + // if rule returns false, then that means that no more rules will be processed, so we exit + if(not rtn) break; + + } catch(any e) { + // if an error occurs while a rule executes, then write to normal log file + buglogClient.notifyService("RuleProcessor Error: #e.message#", e); + writeToCFLog(ruleName & ": " & e.message & e.detail); } - - + } + } + + // writes a message to the internal cf logs + private void function writeToCFLog( + required string message + ) { + writeLog(type="Info", file="bugLog_ruleProcessor", text="#arguments.message#", application=true); + writeDump(var="BugLog::RuleProcessor: #arguments.message#", output="console"); + } - - - - - - - - +} - \ No newline at end of file diff --git a/config/buglog-config.xml.cfm b/config/buglog-config.xml.cfm index e7593ae..8f983f0 100644 --- a/config/buglog-config.xml.cfm +++ b/config/buglog-config.xml.cfm @@ -22,7 +22,8 @@ - bugLog.components.bugLogListenerAsync + bugLog.components.bugLogListener + bugLog.components.InMemoryQueue true false 2CF20630-DD24-491F-BA44314842183AFC @@ -66,6 +67,11 @@ --> + + + + + + + diff --git a/extensions/rules/discard.cfc b/extensions/rules/discard.cfc index 9dd1b66..0a5c8d7 100644 --- a/extensions/rules/discard.cfc +++ b/extensions/rules/discard.cfc @@ -2,64 +2,50 @@ displayName="Discard message" hint="This rule removes items from the queue that matches the given parameters"> - + - - - - - - - - - + + arguments.message = trim(arguments.message); + arguments.text = trim(arguments.text); + super.init(argumentCollection = arguments); + return this; + - - + + - for(var i=1;i lte arrayLen(queue);i++) { - if(matches(queue[i])) { - arrayDeleteat(queue,i); - i--; - } - } - return true; + var alltext = entry.getMessage() & entry.getExceptionMessage() & entry.getExceptionDetails() & entry.getHTMLReport(); + var rtn = (config.message eq "" or (config.message neq "" and listFindNoCase(config.message, entry.getMessage()))) + and + (config.text eq "" or (config.text neq "" and findNoCase(config.text, alltext))); + return rtn; - - - + + + - var alltext = item.getMessage() & item.getExceptionMessage() & item.getExceptionDetails() & item.getHTMLReport(); - var rtn = (config.application eq "" or (config.application neq "" and listFindNoCase(config.application, item.getApplicationCode()))) - and - (config.severityCode eq "" or (config.severityCode neq "" and listFindNoCase(config.severityCode, item.getSeverityCode()))) - and - (config.host eq "" or (config.host neq "" and listFindNoCase(config.host, item.getHostName()))) - and - (config.message eq "" or (config.message neq "" and listFindNoCase(config.message, item.getMessage()))) - and - (config.text eq "" or (config.text neq "" and findNoCase(config.text, alltext))); - - return rtn; + var oEntryDAO = getDAOFactory().getDAO("entry"); + oEntryDAO.delete( entry.getEntryID() ); + return false; - + - - + + @@ -74,4 +60,4 @@ - \ No newline at end of file + diff --git a/extensions/rules/firstMessageAlert.cfc b/extensions/rules/firstMessageAlert.cfc index f23001f..9b2e850 100644 --- a/extensions/rules/firstMessageAlert.cfc +++ b/extensions/rules/firstMessageAlert.cfc @@ -9,85 +9,64 @@ - - + - - - - - - - - - - - - - - + + arguments.timespan = max(val(arguments.timespan),1); + arguments.includeHTMLReport = (isBoolean(arguments.includeHTMLReport) && arguments.includeHTMLReport); + super.init(argumentCollection = arguments); + return this; + - - + - var qry = 0; - var oEntryFinder = 0; - var oEntryDAO = 0; - var args = structNew(); - - // check fast fail conditions - if(variables.config.application neq "" and arguments.rawEntry.getApplicationCode() neq variables.config.application) return true; - if(variables.config.host neq "" and arguments.rawEntry.getHostName() neq variables.config.host) return true; - if(variables.config.severity neq "" and arguments.rawEntry.getSeverityCode() neq variables.config.severity) return true; + var oEntryDAO = getDAOFactory().getDAO("entry"); + var oEntryFinder = createObject("component","bugLog.components.entryFinder").init(oEntryDAO); + var createdOn = entry.getCreatedOn(); - // get necessary IDs - if(variables.config.application neq "" and (variables.applicationID eq ID_NOT_SET or variables.applicationID eq ID_NOT_FOUND)) { - variables.applicationID = getApplicationID(); - } - if(variables.config.host neq "" and (variables.hostID eq ID_NOT_SET or variables.hostID eq ID_NOT_FOUND)) { - variables.hostID = getHostID(); - } - if(variables.config.severity neq "" and (variables.severityID eq ID_NOT_SET or variables.severityID eq ID_NOT_FOUND)) { - variables.severityID = getSeverityID(); + var args = { + message = arguments.entry.getMessage(), + startDate = dateAdd("n", variables.config.timespan * (-1), createdOn ), + endDate = createdOn + }; + + for(var key in structKeyArray(scope)) { + var ids = []; + for(var item in scope[key]["items"]) { + ids.add( scope[key]["items"][item] ); + } + if(arrayLen(ids)) { + args[key & "id"] = (scope[key]["not_in"] ? "-" : "") & listToArray(ids); + } } - - oEntryDAO = getDAOFactory().getDAO("entry"); - oEntryFinder = createObject("component","bugLog.components.entryFinder").init(oEntryDAO); + var qry = oEntryFinder.search(argumentCollection = args); - - args = structNew(); - args.searchTerm = ""; - args.message = arguments.rawEntry.getMessage(); - args.startDate = dateAdd("n", variables.config.timespan * (-1), now() ); - args.endDate = now(); - if(variables.applicationID neq ID_NOT_SET) args.applicationID = variables.applicationID; - if(variables.hostID neq ID_NOT_SET) args.hostID = variables.hostID; - if(variables.severityID neq ID_NOT_SET) args.severityID = variables.severityID; + return (qry.recordCount == 1 + || (qry.recordCount > 1 + && dateDiff("n", variables.lastEmailTimestamp, now()) > variables.config.timespan)); + + - qry = oEntryFinder.search(argumentCollection = args); - - if(qry.recordCount eq 1 or (qry.recordCount gt 1 and dateDiff("n", variables.lastEmailTimestamp, now()) gt variables.config.timespan)) { - logTrigger(entry); - sendEmail(qry, rawEntry); - variables.lastEmailTimestamp = now(); - } - + + + + sendEmail(entry); + variables.lastEmailTimestamp = now(); return true; - - + - + @@ -112,55 +91,16 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -183,4 +123,4 @@ - \ No newline at end of file + diff --git a/extensions/rules/frequencyAlert.cfc b/extensions/rules/frequencyAlert.cfc index 8d556bb..2c750d7 100644 --- a/extensions/rules/frequencyAlert.cfc +++ b/extensions/rules/frequencyAlert.cfc @@ -9,93 +9,91 @@ - - - + + + - - - - - + + - - - - - - - - - - - - - - - + + arguments.count = val(arguments.count); + arguments.timespan = max(val(arguments.timespan),1); + arguments.sameMessage = (isBoolean(arguments.sameMessage) && arguments.sameMessage); + super.init(argumentCollection = arguments); + return this; + - - + - var qry = 0; - var oEntryFinder = 0; - var oEntryDAO = 0; - var args = structNew(); - + var matches = false; + // only evaluate this rule if the amount of timespan minutes has passed after the last email was sent - if( dateDiff("n", variables.lastEmailTimestamp, now()) gt variables.config.timespan ) { - - oEntryDAO = getDAOFactory().getDAO("entry"); - oEntryFinder = createObject("component","bugLog.components.entryFinder").init(oEntryDAO); - - if(variables.config.application neq "" and (variables.applicationID eq ID_NOT_SET or variables.applicationID eq ID_NOT_FOUND)) { - variables.applicationID = getApplicationID(); - } - if(variables.config.host neq "" and (variables.hostID eq ID_NOT_SET or variables.hostID eq ID_NOT_FOUND)) { - variables.hostID = getHostID(); - } - if(variables.config.severity neq "" and (variables.severityID eq ID_NOT_SET or variables.severityID eq ID_NOT_FOUND)) { - variables.severityID = getSeverityID(); - } - - args = structNew(); - args.searchTerm = ""; - args.startDate = dateAdd("n", variables.config.timespan * (-1), now() ); - args.endDate = now(); - if(variables.applicationID neq ID_NOT_SET) args.applicationID = variables.applicationID; - if(variables.hostID neq ID_NOT_SET) args.hostID = variables.hostID; - if(variables.severityID neq ID_NOT_SET) args.severityID = variables.severityID; + if( dateDiff("n", variables.lastEmailTimestamp, now()) > variables.config.timespan ) { - qry = oEntryFinder.search(argumentCollection = args); + var qry = findMessages(entry); - if(qry.recordCount gt 0) { - if(variables.sameMessage) { + if(qry.recordCount > 0) { + if(variables.config.sameMessage) { qry = groupMessages(qry, variables.config.count); - if(qry.recordCount gt 0) { - logTrigger(entry); - sendEmail(qry); - sendAlert(qry); + if(qry.recordCount > 0) { + matches = true; } - } else if(qry.recordCount gt variables.config.count) { - logTrigger(entry); - sendEmail(qry); - sendAlert(qry); + } else if(qry.recordCount > variables.config.count) { + matches = true; } } - } + + return matches; + + + + + + + var qry = findMessages(entry); + sendEmail(qry); + sendAlert(qry); return true; + + + + var oEntryDAO = getDAOFactory().getDAO("entry"); + var oEntryFinder = createObject("component","bugLog.components.entryFinder").init(oEntryDAO); + var createdOn = entry.getCreatedOn(); + + var args = { + startDate = dateAdd("n", variables.config.timespan * (-1), createdOn ), + endDate = createdOn + }; + + for(var key in structKeyArray(scope)) { + var ids = []; + for(var item in scope[key]["items"]) { + ids.add( scope[key]["items"][item] ); + } + if(arrayLen(ids)) { + args[key & "id"] = (scope[key]["not_in"] ? "-" : "") & listToArray(ids); + } + } + + var qry = oEntryFinder.search(argumentCollection = args); + + return qry; + + + @@ -117,7 +115,7 @@ BugLog has received more than #variables.config.count# bug reports - + with the same message @@ -133,7 +131,7 @@

- • [#qryEntries.severityCode#][#qryEntries.applicationCode#][#qryEntries.hostName#] #qryEntries.message# (#qryEntries.bugCount#)
+ • [#qryEntries.severityCode#][#qryEntries.applicationCode#][#qryEntries.hostName#] #qryEntries.message# (#qryEntries.bugCount#)
@@ -147,45 +145,6 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -270,4 +229,4 @@ - \ No newline at end of file + diff --git a/extensions/rules/heartbeatMonitor.cfc b/extensions/rules/heartbeatMonitor.cfc index 48b6437..46c1e50 100644 --- a/extensions/rules/heartbeatMonitor.cfc +++ b/extensions/rules/heartbeatMonitor.cfc @@ -9,70 +9,68 @@ + + - - - - + - - - - - - - - - - - + + arguments.timespan = max(val(arguments.timespan),1); + arguments.alertInterval = max(val(arguments.alertInterval),1); + super.init(argumentCollection = arguments); + return this; + - - + + - var qry = 0; - var oEntryFinder = 0; - var oEntryDAO = 0; - var args = structNew(); - + var matches = false; + var oEntryDAO = getDAOFactory().getDAO("entry"); + var oEntryFinder = createObject("component","bugLog.components.entryFinder").init(oEntryDAO); + // only evaluate this rule if 'alertInterval' minutes has passed after the last email was sent if( dateDiff("n", variables.lastEmailTimestamp, now()) gt variables.config.alertInterval ) { - // get necessary IDs - if(variables.config.application neq "" and variables.applicationID eq -1) { - variables.applicationID = getApplicationID(); - } - if(variables.config.host neq "" and variables.hostID eq -1) { - variables.hostID = getHostID(); - } - if(variables.config.severity neq "" and variables.severityID eq -1) { - variables.severityID = getSeverityID(); + var args = { + startDate = dateAdd("n", variables.config.timespan * (-1), now() ), + endDate = now() + }; + + for(var key in structKeyArray(scope)) { + var ids = []; + for(var item in scope[key]["items"]) { + ids.add( scope[key]["items"][item] ); + } + if(arrayLen(ids)) { + args[key & "id"] = (scope[key]["not_in"] ? "-" : "") & listToArray(ids) + } } - - - oEntryDAO = getDAOFactory().getDAO("entry"); - oEntryFinder = createObject("component","bugLog.components.entryFinder").init(oEntryDAO); - - - args = structNew(); - args.searchTerm = ""; - args.startDate = dateAdd("n", variables.config.timespan * (-1), now() ); - args.endDate = now(); - if(variables.applicationID gt 0) args.applicationID = variables.applicationID; - if(variables.hostID gt 0) args.hostID = variables.hostID; - if(variables.severityID gt 0) args.severityID = variables.severityID; - - qry = oEntryFinder.search(argumentCollection = args); - - if(qry.recordCount eq 0) { + + var qry = oEntryFinder.search(argumentCollection = args); + + if(qry.recordCount == 0) { sendEmail(); variables.lastEmailTimestamp = now(); } - + } - + + return true; + + + + + + + + + + + + sendEmail(); + variables.lastEmailTimestamp = now(); return true; @@ -112,45 +110,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -175,4 +134,4 @@ - \ No newline at end of file + diff --git a/extensions/rules/mailAlert.cfc b/extensions/rules/mailAlert.cfc index ab0a723..d2d8358 100644 --- a/extensions/rules/mailAlert.cfc +++ b/extensions/rules/mailAlert.cfc @@ -2,7 +2,7 @@ hint="This rule sends an email everytime a bug matching a given set of conditions is received"> - + @@ -10,58 +10,37 @@ - - - - - - - - - - + + arguments.includeHTMLReport = (isBoolean(arguments.includeHTMLReport) && arguments.includeHTMLReport); + super.init(argumentCollection = arguments); + return this; + - - - + + - var stEntry = arguments.rawEntry.getMemento(); - var evalCond1 = true; - var evalCond2 = true; - var evalCond3 = true; - var evalCond4 = true; - - // evaluate conditions - evalCond1 = !len(variables.config.application) - or listFindNoCase(variables.config.application, stEntry.applicationCode); - - evalCond2 = !len(variables.config.severityCode) - or listFindNoCase(variables.config.severityCode, stEntry.severityCode); - - evalCond3 = !len(variables.config.host) - or listFindNoCase(variables.config.host, stEntry.hostName); + var stEntry = arguments.entry.getMemento(); + var matches = !(arrayLen(listToArray(variables.config.keywords)) > 0); - for(var i=1;i lte listLen(variables.config.keywords);i=i+1) { - evalCond4 = evalCond3 and findNoCase(listGetAt(variables.config.keywords,i), stEntry.message); - if(not evalCond4) break; + for(var keyword in listToArray(variables.config.keywords)) { + matches = matches || findNoCase(keyword, stEntry.message); } + return matches; + + - // if all conditions are met, then send the alert - if(evalCond1 and evalCond2 and evalCond3 and evalCond4) { - logTrigger(entry); - sendToEmail(rawEntryBean = arguments.rawEntry, - recipient = variables.config.recipientEmail, - subject = "BugLog: #arguments.rawEntry.getMessage()#", - comment = getAlertMessage(), - entryID = arguments.entry.getEntryID(), - includeHTMLReport = variables.config.includeHTMLReport); - - writeToCFLog("'MailAlertRule' rule fired. Email sent. Msg: '#arguments.rawEntry.getMessage()#'"); - } - + + + + sendToEmail(entry = arguments.entry, + recipient = variables.config.recipientEmail, + subject = "BugLog: #arguments.entry.getMessage()#", + comment = getAlertMessage(), + entryID = arguments.entry.getEntryID(), + includeHTMLReport = variables.config.includeHTMLReport); return true; @@ -78,8 +57,8 @@ - - + + @@ -89,12 +68,12 @@ - + - - + + @@ -106,4 +85,4 @@ - \ No newline at end of file + diff --git a/extensions/rules/mailRelay.cfc b/extensions/rules/mailRelay.cfc index 69b75cb..7917568 100644 --- a/extensions/rules/mailRelay.cfc +++ b/extensions/rules/mailRelay.cfc @@ -7,23 +7,29 @@ - - - + + arguments.includeHTMLReport = (isBoolean(arguments.includeHTMLReport) && arguments.includeHTMLReport); + super.init(argumentCollection = arguments); + return this; + - - + - - - - - + + + + + + + sendToEmail(entry = arguments.entry, + recipient = variables.config.recipientEmail, + subject = "BugLog: #arguments.entry.getMessage()#", + entryId = arguments.entry.getEntryId(), + includeHTMLReport = variables.config.includeHTMLReport); + writeToCFLog("'mailRelay' rule fired. Email sent. Msg: '#arguments.entry.getMessage()#'"); + return true; + @@ -34,4 +40,4 @@ - \ No newline at end of file + diff --git a/hq/config/config.xml.cfm b/hq/config/config.xml.cfm index 15266a4..e8a6669 100644 --- a/hq/config/config.xml.cfm +++ b/hq/config/config.xml.cfm @@ -3,7 +3,7 @@ - + diff --git a/hq/handlers/general.cfc b/hq/handlers/general.cfc index 97a0206..0d98fef 100644 --- a/hq/handlers/general.cfc +++ b/hq/handlers/general.cfc @@ -109,6 +109,9 @@ // get current filters selected criteria = getValue("criteria"); + // ensure that we are grouping at least by message + criteria.groupByMsg = true; + qryEntries = appService.searchEntries(argumentCollection = criteria); setValue("qryEntries",qryEntries); @@ -135,6 +138,11 @@ // get current filters selected var criteria = getValue("criteria"); + // for the dashboard, we need the raw data to do adhoc queries + criteria.groupByMsg = false; + criteria.groupByApp = false; + criteria.groupByHost = false; + var qryEntries = appService.searchEntries(argumentCollection = criteria); var qryTriggers = appService.getExtensionsLog(criteria.startDate, getValue("currentUser")); @@ -163,6 +171,9 @@ // get current filters selected var criteria = getValue("criteria"); + // ensure that we are grouping at least by message + criteria.groupByMsg = true; + // perform search var qryEntries = appService.searchEntries(argumentCollection = criteria); @@ -179,9 +190,6 @@ else setValue("pageTitle", "Summary"); - // perform grouping for summary display - qryEntries = appService.applyGroupings(qryEntries, criteria.groupByApp, criteria.groupByHost); - setValue("qryEntries", qryEntries); setValue("refreshSeconds",refreshSeconds); setValue("rowsPerPage",rowsPerPage); @@ -220,6 +228,11 @@ setValue("msgFromEntryID", ""); } + // ensure that we are not grouping anything + criteria.groupByMsg = false; + criteria.groupByApp = false; + criteria.groupByHost = false; + // perform search var qryEntries = appService.searchEntries(argumentCollection = criteria); @@ -253,17 +266,55 @@ try { var appService = getService("app"); var entryID = getValue("entryID"); + var entryUUID = getValue("uuid"); var argsSearch = {}; - var qryEntriesUA = queryNew(""); var currentUser = getValue("currentUser"); + var oEntry = 0; - if(val(entryID) eq 0) { + // get requested entry object + if(entryID gt 0) { + oEntry = appService.getEntry(entryID, currentUser); + } else if(len(entryUUID)) { + oEntry = appService.getEntryByUUID(entryUUID, currentUser); + } else { setMessage("warning","Please select an entry to view"); setNextEvent("main"); } + // update lastread setting + if(structKeyExists(cookie, "lastbugread") and entryID gte cookie.lastbugread) { + cookie.lastbugread = entryID; + } + + // set values + setValue("ruleTypes", getService("app").getRules()); + setValue("jiraEnabled", getService("jira").getSetting("enabled")); + setValue("oEntry", oEntry); + setView("entry"); + + } catch(notAllowed e) { + setMessage("warning","You are not allowed to view this bug report"); + setNextEvent("main"); + + } catch(any e) { + setMessage("error",e.message); + getService("bugTracker").notifyService(e.message, e); + setNextEvent("main"); + } + + + + + + try { + var appService = getService("app"); + var entryID = getValue("entryID"); + var argsSearch = {}; + var qryEntriesUA = queryNew(""); + var currentUser = getValue("currentUser"); + // get requested entry object - oEntry = appService.getEntry(entryID, currentUser); + var oEntry = appService.getEntry(entryID, currentUser); // search for recent ocurrences (last 24 hours) args.message = "__EMPTY__"; @@ -284,30 +335,20 @@ user = currentUser); } - - // update lastread setting - if(structKeyExists(cookie, "lastbugread") and entryID gte cookie.lastbugread) { - cookie.lastbugread = entryID; - } - // set values - setValue("ruleTypes", getService("app").getRules()); - setValue("jiraEnabled", getService("jira").getSetting("enabled")); + setLayout(""); setValue("oEntry", oEntry); setValue("qryEntriesLast24", qryEntriesLast24); setValue("qryEntriesAll", qryEntriesAll); setValue("qryEntriesUA", qryEntriesUA); - setValue("oEntry", oEntry); - setView("entry"); + setView("entryStats"); } catch(notAllowed e) { setMessage("warning","You are not allowed to view this bug report"); - setNextEvent("main"); - } catch(any e) { + } catch(lock e) { setMessage("error",e.message); getService("bugTracker").notifyService(e.message, e); - setNextEvent("main"); } @@ -342,7 +383,9 @@ } var criteria = normalizeCriteria(); - var rssXML = appService.buildRSSFeed(criteria, summary, rssService); + criteria.groupByMsg = summary; + + var rssXML = appService.buildRSSFeed(criteria, rssService); setValue("rssXML", rssXML); setView("feed"); diff --git a/hq/views/entry.cfm b/hq/views/entry.cfm index 1ec834b..306345d 100644 --- a/hq/views/entry.cfm +++ b/hq/views/entry.cfm @@ -21,33 +21,24 @@ + + - - - - SELECT * - FROM rs.qryEntriesAll - WHERE entryID < - ORDER BY createdOn DESC - - - SELECT hostID, hostName, count(hostID) as numEntries - FROM rs.qryEntriesAll - GROUP BY hostID, hostName - - - - SELECT * - FROM rs.qryEntriesUA - WHERE entryID <> - ORDER BY createdOn DESC - - - - + + + + + + - @@ -58,7 +49,7 @@
Message:
Date/Time:#showDateTime(stEntry.receivedOn)##createdOn#
Application:
- + @@ -198,78 +167,7 @@
- + Send to email @@ -112,12 +103,8 @@ DELETE BUG REPORT? - - - - - - + +
@@ -154,25 +141,7 @@
User Agent: - #oEntry.getUserAgent()# - -
#rs.qryEntriesUA.recordCount# other reports from the same user agent (last 24hrs) - -
-
#oEntry.getUserAgent()#
- -
-

Stats

- - -
- Host Distribution:
- - - - - - - - -
#hostName##numEntries# (#round(numEntries/totalEntries*100)#%)
-
-
-
- + diff --git a/hq/views/entryStats.cfm b/hq/views/entryStats.cfm new file mode 100644 index 0000000..04f7e40 --- /dev/null +++ b/hq/views/entryStats.cfm @@ -0,0 +1,124 @@ + + + + + + + + + SELECT * + FROM rs.qryEntriesAll + WHERE entryID < + ORDER BY createdOn DESC + + + + SELECT hostID, hostName, count(hostID) as numEntries + FROM rs.qryEntriesAll + GROUP BY hostID, hostName + + + + + SELECT * + FROM rs.qryEntriesUA + WHERE entryID <> + ORDER BY createdOn DESC + + + + + + +
+

Stats

+ + +
+ Host Distribution:
+ + + + + + + + +
#hostName##numEntries# (#round(numEntries/totalEntries*100)#%)
+
+
+
+
+ diff --git a/install/migration/1_8_to_2_0/mysql.sql b/install/migration/1_8_to_2_0/mysql.sql new file mode 100644 index 0000000..4d395c3 --- /dev/null +++ b/install/migration/1_8_to_2_0/mysql.sql @@ -0,0 +1,10 @@ +ALTER TABLE `bl_Entry` +ADD COLUMN `UUID` VARCHAR(50) NULL, +ADD COLUMN `updatedOn` DATETIME NULL, +ADD COLUMN `isProcessed` TINYINT NOT NULL DEFAULT 0, +ADD INDEX `IX_Entry_isProcessed` (`isProcessed` ASC), +ADD INDEX `IX_Entry_UUID` (`UUID` ASC); + +UPDATE `bl_Entry` +SET isProcessed = 1; + diff --git a/listener/index.cfm b/listener/index.cfm new file mode 100644 index 0000000..b276296 --- /dev/null +++ b/listener/index.cfm @@ -0,0 +1,78 @@ + + + + try { + + instance = ""; + status = { + code = 200, + text = "OK" + }; + response = { + "status" = "OK", + "id" = "" + }; + + // This endpoint must respond to both JSON and regular HTTP requests + requestBody = toString( getHttpRequestData().content ); + if( isJSON(requestBody) ) { + // json is what we got + args = deserializeJSON(requestBody); + } else { + // This is a regular http request, so let's build a + // collection of all incoming URL & Form parameters + args = duplicate(form); + structAppend(args, url); + } + + // at minimum we need a message + if(!structKeyExists(args, "message")) { + throw(type="bugLog.invalidFormat", message="Message field is required"); + } + + // set default values + param name="args.applicationCode" default="Unknown"; + param name="args.dateTime" type="date" default=now(); + param name="args.severityCode" default="ERROR"; + param name="args.hostName" default=cgi.REMOTE_ADDR; + + // log how we got this report + args.source = ucase(cgi.request_method); + + // See if we this is a named instance of buglog + if( structKeyExists(request,"bugLogInstance") and len(request.bugLogInstance) ) { + instance = request.bugLogInstance; + } + + // log entry + listener = createObject("component","bugLog.components.listener").init( instance ); + rawEntryBean = listener.logEntry( argumentCollection = args ); + response.id = rawEntryBean.getUUID(); + + + // Handle exceptions + } catch("bugLog.invalidFormat" e) { + status = {code = 400, text = "Bad Request"}; + response["status"] = "Error"; + response["error"] = e.message; + + } catch("bugLog.invalidAPIKey" e) { + status = {code = 401, text = "Unauthorized"}; + response["status"] = "Error"; + response["error"] = e.message; + + } catch("applicationNotAllowed" e) { + status = {code = 403, text = "Forbidden"}; + response["status"] = "Error"; + response["error"] = e.message; + + } catch(any e) { + status = {code = 500, text = "Server Error"}; + response["status"] = "Error"; + response["error"] = e.message; + } + + + + +#serializeJson(response)# diff --git a/listeners/bugLogListenerREST.cfm b/listeners/bugLogListenerREST.cfm index e2bf9db..abdb9b0 100644 --- a/listeners/bugLogListenerREST.cfm +++ b/listeners/bugLogListenerREST.cfm @@ -39,7 +39,7 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - // get listener service wrapper - var serviceLoader = createObject("component", "bugLog.components.service").init( instanceName = variables.instanceName ); - - // get handle to bugLogListener service - var bugLogListener = serviceLoader.getService(); - - // create entry bean - var rawEntry = createObject("component","bugLog.components.rawEntryBean") - .init() - .setDateTime(arguments.dateTime) - .setMessage(arguments.message) - .setApplicationCode(arguments.applicationCode) - .setSourceName(arguments.source) - .setSeverityCode(arguments.severityCode) - .setHostName(arguments.hostName) - .setExceptionMessage(arguments.exceptionMessage) - .setExceptionDetails(arguments.exceptionDetails) - .setCFID(arguments.cfid) - .setCFTOKEN(arguments.cftoken) - .setUserAgent(arguments.userAgent) - .setTemplatePath(arguments.templatePath) - .setHTMLReport(arguments.HTMLReport) - .setReceivedOn(now()); - - // validate Entry - bugLogListener.validate(rawEntry, arguments.APIKey); - - // log entry - bugLogListener.logEntry(rawEntry); - - - -
\ No newline at end of file diff --git a/test/client.cfm b/test/client.cfm index 57733ca..110ca87 100644 --- a/test/client.cfm +++ b/test/client.cfm @@ -4,11 +4,9 @@ of how to use the buglog client. URL Parameters: - protocol: determines which buglog listener to use. values are cfc, soap and rest severity: type of error to send. Values are ERROR, FATAL, CRITICAL and INFO ---> - @@ -22,15 +20,12 @@ - - - + - - + @@ -57,12 +52,11 @@ Creating client instance...
- ... Listener type is: #protocol#
- ... Listener is: #bugLogListener[protocol]#
+ ... Listener URL: #bugLogListener#
... Listener API Key is: #apikey#
- ... Adding a checkpoint @@ -73,11 +67,11 @@ Throwing sample error message...
- + - Notify service via [#protocol#] using severity [#severity#]....
+ Notify service using severity [#severity#]....

- Send test bug report via: - - #key# -  |  - + Test Again +  | 

diff --git a/util/processQueue.cfm b/util/processQueue.cfm index cc1b27a..f2853e8 100644 --- a/util/processQueue.cfm +++ b/util/processQueue.cfm @@ -1,13 +1,28 @@ - + - - + + bugLogClient = createObject("component", "bugLog.client.bugLogService").init("bugLog.listeners.bugLogListenerWS"); - - - - - + try { + // Handle service initialization if necessary + oService = createObject("component", "bugLog.components.service").init( instanceName = url.instance ); + + // process pending queue + if(oService.isRunning()) { + oBugLogListener = oService.getService(); + + // process all new incoming bug reports + oBugLogListener.processQueue( url.key ); + + // process rules for newly added reports + oBugLogListener.processRules( url.key ); + } + + } catch(any e) { + // report the error + bugLogClient.notifyService(e.message, e); + } +