Created
February 17, 2011 02:40
-
-
Save mhenke/830841 to your computer and use it in GitHub Desktop.
hoth report interface - coldfusion
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| Aaron Greenlee | |
| http://aarongreenlee.com/ | |
| This work is licensed under a Creative Commons Attribution-Share-Alike 3.0 | |
| Unported License. | |
| // Original Info ----------------------------------------------------------- | |
| Author : Aaron Greenlee | |
| Created : 01/12/2011 | |
| HothReporter offers a simple Web UI to report the errors observed by Hoth. | |
| // Modifications :--------------------------------------------------------- | |
| */ | |
| component | |
| name='HothReporter' | |
| accessors=false | |
| output="false" | |
| { | |
| public Hoth.HothReporter function init (HothConfig) | |
| { | |
| // If a config object was not provided we | |
| // will use our default. | |
| variables.Config = (structKeyExists(arguments, 'HothConfig')) | |
| ? arguments.HothConfig | |
| : new Hoth.object.HothConfig(); | |
| VARIABLES._NAME = 'Hoth_' & variables.Config.getApplicationName(); | |
| variables.exceptionKeys = ['detail','type','tagcontext','stacktrace','message'];// Required exception keys | |
| variables.paths.LogPath = variables.Config.getLogPathExpanded(); // Get the root location for our logging. | |
| variables.paths.Exceptions = variables.Config.getPath('exceptions'); // Track the unique exceptions. | |
| variables.paths.Incidents = variables.Config.getPath('incidents'); // Track the hits per exception. | |
| variables.paths.Report = variables.Config.getPath('exceptionReport'); // The actual report | |
| variables.paths.Activity = variables.Config.getPath('exceptionReportActivity'); // Track when we save things. Helps understand volume. | |
| //variables.paths.Index = variables.Config.getPath('exceptionIndex'); // Tracks the exception keys to prevent duplication | |
| return this; | |
| } | |
| /** Quick report. Really a work in process. | |
| * @exception Accepts a hash value or 'all' | |
| **/ | |
| public struct function report (required string exception) | |
| { | |
| if (arguments.exception=='all') | |
| { | |
| return generateExceptionIndex(); | |
| } else { | |
| local.filepath = variables.paths.Exceptions & '/' & arguments.exception; | |
| local.exception = (fileExists(local.filepath)) | |
| ? fileRead(local.filepath) | |
| : serializeJSON ({'message'="That report no longer exists."}); | |
| return deserializeJSON (local.exception); | |
| } | |
| } | |
| /** Quick report. Really a work in process. | |
| * @exception Accepts a hash value or 'all' | |
| **/ | |
| public array function delete (required string exception) | |
| { | |
| if (arguments.exception=='all') | |
| { | |
| // hahah - not yet. | |
| } else { | |
| local.exceptionPath = variables.paths.Exceptions & '/' & arguments.exception; | |
| local.incidentPath = variables.paths.Incidents & '/' & arguments.exception; | |
| local.response = []; | |
| if (fileExists(local.exceptionPath)) | |
| { | |
| fileDelete(local.exceptionPath); | |
| arrayAppend(local.response, "Exception file deleted."); | |
| } else { | |
| arrayAppend(local.response, "Exception file did not exist!"); | |
| } | |
| if (fileExists(local.incidentPath)) | |
| { | |
| fileDelete(local.incidentPath); | |
| arrayAppend(local.response, "Incident record file deleted."); | |
| } else { | |
| arrayAppend(local.response, "Incident record file not exist!"); | |
| } | |
| return local.response; | |
| } | |
| } | |
| /** Return the report's view **/ | |
| public string function getReportView () { | |
| local.view = fileRead(expandPath('/Hoth') & '/views/report.html'); | |
| return local.view; | |
| } | |
| // ------------------------------------------------------------------------- | |
| private struct function generateExceptionIndex() { | |
| // Read our file system | |
| local.exceptions = directoryList (variables.paths.Exceptions,false); | |
| local.incidents = directoryList (variables.paths.Exceptions,false); | |
| local.report = {}; | |
| for (i=1;i LTE ArrayLen(local.exceptions);i=i+1) { | |
| local.instance = {}; | |
| local.instance.filename = | |
| listLast(local.exceptions[i],'\/'); | |
| if (left(local.instance.filename, 1) != '_') | |
| { | |
| //local.instance.exceptionDetail = | |
| //fileRead (local.exceptions[i]); | |
| if (!fileExists(variables.paths.Incidents & '/' & local.instance.filename)) | |
| { | |
| local.instances = ''; | |
| } else { | |
| local.instances = | |
| fileRead(variables.paths.Incidents & '/' & local.instance.filename); | |
| } | |
| local.instance.incidentCount = listLen(local.instances,chr(10)); | |
| // Save our report | |
| local.report[local.instance.filename] = local.instance; | |
| } | |
| } | |
| return local.report; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // This CFC offers no protection--thus, anyone can access this. | |
| // You can implement this by renaming the CFC with a UUID--that can secure | |
| // this through obscurity. But, I prefer the approach shown in the ColdBox | |
| // example. That is what I use. I have not tried this code :( | |
| component | |
| { | |
| /** Loads the Web UI (HTML) **/ | |
| remote function index () returnformat='plain' { | |
| local.HothReport = new Hoth.HothReporter( new config.HothConfig() ); | |
| return local.HothReport.getReportView(); | |
| } | |
| /** Access Hoth report data as JSON. | |
| @exception If not provided a list of exceptions will be returned. | |
| If provided, the value should be an exception hash which | |
| modified the behavior to return information for only | |
| that exception. **/ | |
| remote function report (string exception) returnformat='JSON' { | |
| local.report = (structKeyExists(arguments, 'exception') | |
| ? arguments.exception | |
| : 'all'); | |
| local.HothReport = new Hoth.HothReporter( new config.HothConfig() ); | |
| return local.HothReport.report(local.report); | |
| } | |
| /** Delete a report. **/ | |
| remote function delete (string exception)returnformat='JSON' { | |
| if (!structKeyExists(arguments, 'exception')) | |
| { | |
| // We can delete all exceptions at once! | |
| arguments.exception = 'all'; | |
| } | |
| local.HothReport = new Hoth.HothReporter( new config.HothConfig() ); | |
| // Delete! | |
| return local.HothReporter.delete(arguments.exception); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <html> | |
| <head><title>Exceptions Observed by Hoth</title> | |
| <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script> | |
| <style> | |
| /** Reset **/ | |
| html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,figure,footer,header,hgroup,menu,nav,section,menu,time,mark,audio,video{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}article,aside,figure,footer,header,hgroup,nav,section{display:block}nav ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}a{margin:0;padding:0;font-size:100%;vertical-align:baseline;background:transparent}ins{background-color:#ff9;color:#000;text-decoration:none}mark{background-color:#ff9;color:#000;font-style:italic;font-weight:bold}del{text-decoration:line-through}abbr[title],dfn[title]{border-bottom:1px dotted #000;cursor:help}table{border-collapse:collapse;border-spacing:0}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0}input,select{vertical-align:middle}body{font:13px sans-serif;*font-size:small;*font:x-small;line-height:1.22}table{font-size:inherit;font:100%}select,input,textarea{font:99% sans-serif}pre,code,kbd,samp{font-family:monospace,sans-serif}body,select,input,textarea{color:#444}h1,h2,h3,h4,h5,h6{font-weight:bold;text-rendering:optimizeLegibility}html{-webkit-font-smoothing:antialiased}a:hover,a:active{outline:none}a,a:active,a:visited{color:#607890}a:hover{color:#036}ul{margin-left:30px}ol{margin-left:30px;list-style-type:decimal}small{font-size:85%}strong,th{font-weight:bold}td,td img{vertical-align:top}sub{vertical-align:sub;font-size:smaller}sup{vertical-align:super;font-size:smaller}pre{padding:15px;white-space:pre;white-space:pre-wrap;white-space:pre-line;word-wrap:break-word}input[type="radio"]{vertical-align:text-bottom}input[type="checkbox"]{vertical-align:bottom;*vertical-align:baseline}.ie6 input{vertical-align:text-bottom}label,input[type=button],input[type=submit],button{cursor:pointer}::-moz-selection{background:#f26c4f;color:#000;text-shadow:none}::selection{background:#f26c4f;color:#000;text-shadow:none}a:link{-webkit-tap-highlight-color:#ff5e99}html{overflow-y:scroll}button{width:auto;overflow:visible}.ie7 img{-ms-interpolation-mode:bicubic}.ir{display:block;text-indent:-999em;overflow:hidden;background-repeat:no-repeat}.hidden{display:none;visibility:hidden}.visuallyhidden{position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}.invisible{visibility:hidden}.clearfix:after{content:".";display:block;height:0;clear:both;visibility:hidden}.clearfix{display:inline-block}* html .clearfix{height:1%}/*\*/.clearfix{display:block}/**/@media print{*{background:transparent!important;color:#444!important;text-shadow:none}a,a:visited{color:#444!important;text-decoration:underline}a:after{content:" (" attr(href) ")"}abbr:after{content:" (" attr(title) ")"}.ir a:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}img{page-break-inside:avoid}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}@media screen and (max-device-width:480px){html{-webkit-text-size-adjust:none;-ms-text-size-adjust:none}} | |
| /** ----------------------------------------------------------- **/ | |
| body { | |
| background-color: #e5e8f1; | |
| font-family: Arial, Helvetica, sans-serif; | |
| padding: 10px 30px; | |
| } | |
| div#container { | |
| overflow: hidden; | |
| } | |
| div#list { | |
| float: left; | |
| width: 140px; | |
| font-size: 14px; | |
| } | |
| div#list ul { | |
| margin: 0; | |
| } | |
| div#list ul li { | |
| list-style: none; | |
| } | |
| div#detail { | |
| margin-left: 160px; | |
| } | |
| div#detail dl { | |
| font-family: monospace; | |
| } | |
| div#detail dl dt { | |
| font-weight: 700; | |
| margin: 10px 0 10px 5px; | |
| font-size: 12px; | |
| text-transform: uppercase; | |
| font-family: Arial, Helvetica, sans-serif; | |
| } | |
| div#detail dl dd { | |
| margin: 0; | |
| padding: 10px; | |
| background-color: #fff; | |
| -moz-border-radius: 10px; | |
| border-radius: 10px; | |
| } | |
| div#detail dl dd div.mainline { | |
| padding: 2px 5px; | |
| border-bottom: 1px solid #ccc; | |
| font-weight: 700; | |
| } | |
| div#detail dl dd div.subline { | |
| font-size: 10px; | |
| padding: 2px 5px; | |
| color: #45454c; | |
| } | |
| div.linegroup { | |
| border: 1px solid #d0d2eb; | |
| padding: 5px; | |
| -moz-border-radius: 10px; | |
| border-radius: 10px; | |
| margin-bottom: 5px; | |
| } | |
| div.linegroup:nth-child(odd) { | |
| background-color: #dbdef1; | |
| } | |
| h1 { | |
| font-size: 28px; | |
| line-height: 1em; | |
| margin: 10px 0; | |
| padding: 10px; | |
| background-color: #554a50; | |
| color: #fff; | |
| -moz-border-radius: 10px; | |
| border-radius: 10px; | |
| } | |
| h3 { | |
| font-size: 18px; | |
| margin: 10px 0; | |
| padding: 10px 0; | |
| } | |
| h4 { | |
| text-align: right; | |
| padding: 5px 10px; | |
| } | |
| h4 a { | |
| color: #000; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h4>Visit the main site at <a href="http://aarongreenlee.com/hoth">http://aarongreenlee.com/hoth</a></h4> | |
| <h1>Hoth: ColdFusion Exception Tracking</h1> | |
| <div id="container"> | |
| <div id="list"> | |
| <h3>Exceptions</h3> | |
| <div id="listing"><ul></ul></div> | |
| </div> | |
| <div id="detail"> | |
| <h3>Details</h3> | |
| <div id="details">Click an exception from the list on the left.</div> | |
| </div> | |
| </div> | |
| <script type="text/javascript"> | |
| $().ready(function() { | |
| /** Format a EPOCH into mm/dd/yyyy **/ | |
| Date.prototype.formatIntoDate = function () { | |
| var r = (this.getMonth() + 1) + '/' + this.getDate() + '/' + this.getFullYear(); | |
| return r; | |
| } | |
| var Hoth = { | |
| Exceptions : {} | |
| }; | |
| $.ajax({ | |
| url: '/Hoth/Report.cfc?method=report', | |
| success: function(response){ | |
| populatePage(response); | |
| } | |
| }); | |
| var populatePage = function (ServerExceptions) { | |
| Hoth.Exceptions = ServerExceptions; | |
| alert(Hoth.Exceptions); | |
| /** Prepare **/ | |
| Hoth.ExceptionsByVolume = []; | |
| // Parse our exceptions sent by the server | |
| for (var e in Hoth.Exceptions) | |
| { | |
| var ex = Hoth.Exceptions[e]; | |
| // Seperate JavaScript information from the data provided by | |
| // the server | |
| alert(ex); | |
| ex.js = {}; | |
| alert(ex.js); | |
| ex.js.short = e.substring(0,8).toUpperCase(); | |
| ex.js.instances = []; | |
| // Create dates from the server EPOCH for our error report | |
| for (var d in ex.incidentdetail) | |
| { | |
| try { | |
| ex.js.instances.push(new Date(d)); | |
| } catch (e) { | |
| // silent. | |
| } | |
| } | |
| // Sort the dates in descending order | |
| ex.js.instances.sort(); | |
| ex.js.instances.reverse(); | |
| // Simplify our reference to the last exception date/time | |
| ex.js.lastErrorOccured = ex.js.instances[0]; | |
| Hoth.ExceptionsByVolume.push(ex); | |
| } | |
| // Sort all of our exceptions by last date observed | |
| Hoth.ExceptionsByVolume.sort(compare); | |
| // Print the HTML links | |
| var exLinksHTML = ''; | |
| for (var i in Hoth.ExceptionsByVolume) | |
| { | |
| var ex = Hoth.ExceptionsByVolume[i]; | |
| exLinksHTML += '<li><a href="#' + ex.filename + '" data-id="' + ex.filename + '">' + | |
| ex.js.short + '</a> (' + ex.incidentcount + ')</li>'; | |
| $('#listing ul').html(exLinksHTML); | |
| } | |
| } | |
| // ----------------------------------------------------------------- | |
| $('#listing a').live('click',function(){ | |
| var id = $(this).attr('data-id').toLowerCase(); | |
| formatException(id); | |
| }); | |
| function formatException(id) { | |
| $.ajax( | |
| { | |
| url: '/Hoth/Report.cfc?method=report&exception='+id | |
| ,async : false | |
| ,success: function (ex) | |
| { | |
| var bar = '<div id="exceptionMenu">\ | |
| <a href="/Hoth/report/exception/' + id + '">View Raw JSON</a> | \ | |
| <a href="/Hoth/delete/exception/' + | |
| id + | |
| '">Delete Reports</a></div>'; | |
| var detail = '<dl>' | |
| + '<dt>URL</dt><dd>' + ex.url + '</dd>' | |
| + '<dt>User Agent</dt><dd>' + ex.client + '</dd>' | |
| + '<dt>Message</dt><dd>' + ex.message + '</dd>' | |
| + '<dt>Detail</dt><dd>' + ex.detail + '</dd>' | |
| + '<dt>Context</dt><dd>' + iterateExceptionContext(ex.context) + '</dd>' | |
| + '<dt>Stack</dt><dd>' + ex.stack + '</dd>'; | |
| $('#details').html(bar + detail); | |
| } | |
| } | |
| ); | |
| } | |
| function iterateExceptionContext(context) { | |
| var output = []; | |
| for (var i in context) { | |
| var c = context[i]; | |
| var mainline = c.template + '[' + c.line + ']'; | |
| var subline = c.type + '(' + c.raw_trace + ')'; | |
| output.push('<div class="linegroup"><div class="mainline">' + mainline + '</div><div class="subline">' + subline + '</div></div>'); | |
| } | |
| return output.join(""); | |
| } | |
| function parseExceptionString(s) { | |
| var r = s.replace('#',''); | |
| return r.replace('.log',''); | |
| } | |
| function compare(a,b) { | |
| if (a.incidentcount < b.incidentcount) | |
| return 1; | |
| if (a.incidentcount > b.incidentcount) | |
| return -1; | |
| return 0; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment