diff --git a/.travis.yml b/.travis.yml index a844c192..a7e264a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,8 @@ matrix: rvm: - 2.3.4 - 2.5.3 + - 2.6.5 + - 2.7.0 services: - redis-server diff --git a/README.md b/README.md index d565e328..d2978544 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,10 @@ Logster can be configured using `Logster.config`: - `Logster.config.rate_limit_error_reporting` : controls automatic 1 minute rate limiting for JS error reporting. - `Logster.config.web_title` : `` tag for logster error page. - `Logster.config.enable_custom_patterns_via_ui` : enable the settings page (`/settings`) where you can add suppression and grouping patterns. -- `Logster.config.maximum_message_size_bytes` : specifiy a size in bytes that a message cannot exceed. Note this isn't 100% accurate, meaning a message may still grow above the limit, but it shouldn't grow by more tha, say, 2000 bytes. +- `Logster.config.maximum_message_size_bytes` : specify a size in bytes that a message cannot exceed. Note this isn't 100% accurate, meaning a message may still grow above the limit, but it shouldn't grow by more than, say, 2000 bytes. +- `Logster.config.project_directories` : This should be an array of hashes that map paths on the local filesystem to GitHub repository URLs. If this feature is enabled, Logster will parse backtraces and try to construct a GitHub URL to the exact file and line number for each line in the backtrace. For a Rails app, the config may look like this: `Logster.config.project_directories = [{ path: Rails.root.to_s, url: "https://github.com/<your_org>/<your_repo>" }]`. The GitHub links that are constructed will use the `master` branch. If you want Logster to use the `application_version` attribute from the `env` tab so that the GitHub links point to the exact version of the app when the log message is created, add `main_app: true` key to the hash. +- `Logster.config.enable_backtrace_links` : Enable/disable the backtrace links feature. +- `Logster.config.gems_dir` : The value of this config is `Gem.dir + "/gems/"` by default. You probably don't need to change this config, but it's available in case your app gems are installed in a different directory. An example where this config is needed is Logster [demo site](http://logster.info/logs/): [https://github.com/discourse/logster/blob/master/website/sample.rb#L77](https://github.com/discourse/logster/blob/master/website/sample.rb#L77). ### Tracking Error Rate Logster allows you to register a callback when the rate of errors has exceeded diff --git a/Rakefile b/Rakefile index c964d896..cde55328 100644 --- a/Rakefile +++ b/Rakefile @@ -17,6 +17,7 @@ task :client_dev do Process.wait pid Process.wait pid2 rescue Interrupt => e + sleep 0.5 puts "Done!" exit 0 end diff --git a/assets/javascript/client-app.js b/assets/javascript/client-app.js index 0907be8f..384b316e 100644 --- a/assets/javascript/client-app.js +++ b/assets/javascript/client-app.js @@ -6,7 +6,24 @@ e.default=i}),define("client-app/components/actions-menu",["exports"],function(e var t=Ember.Component.extend({showMenu:!1,tagName:"span",init:function(){this._super.apply(this,arguments),this.bindingFunction=this.bindingFunction.bind(this)},bindingFunction:function(e){var t=this.$()[0] Em.$.contains(t,e.target)||t===e.target||this.set("showMenu",!1)},bindDocument:Ember.observer("showMenu",function(){var e=Em.$(document) this.get("showMenu")?e.on("click",this.get("bindingFunction")):e.off("click",this.get("bindingFunction"))}),willDestroyElement:function(){this._super.apply(this,arguments),Em.$(document).off("click",this.get("bindingFunction"))},actions:{expandMenu:function(){this.toggleProperty("showMenu")},share:function(){this.share()}}}) -e.default=t}),define("client-app/components/env-tab",["exports","client-app/lib/utilities","client-app/lib/preload"],function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=t}),define("client-app/components/back-trace",["exports","@babel/runtime/helpers/esm/slicedToArray","client-app/lib/preload"],function(e,t,n){function a(e,t){return!(!e||!t||t.length>e.length)&&e.substring(0,t.length)==t}function s(){return n.default.get("backtrace_links_enabled")}function i(e){return e&&"/"!==e[e.length-1]?e+"/":e}function r(e){var t=e.repo,n=e.path,a=e.filename,s=e.lineNumber,r=e.commitSha,o=void 0===r?null:r,l=i(t) +return/\/tree\//.test(l)||(l+="blob/",l+=o?"".concat(o,"/"):"master/"),l+=n+a,/^[0-9]+$/.test(s)&&(l+="#L".concat(s)),l}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +var o=Ember.Component.extend({GithubURLForGem:function(e){var i=null +if(!s())return i +var o=e.match(/([^\/]+)\/(.+\/)(.+):(\d+):.*/)||[],l=(0,t.default)(o,5),u=l[1],c=l[2],d=l[3],p=l[4],f=n.default.get("gems_data").filter(function(e){return a(u,"".concat(e.name,"-"))}).sortBy("name.length").reverse()[0] +return f&&(i=r({repo:f.url,path:c,filename:d,lineNumber:p})),i},GithubURLForApp:function(e){var o=null +if(!s())return o +var l=n.default.get("directories").filter(function(t){return a(e,t.path)}).sortBy("path.length").reverse()[0] +if(l){var u,c,d,p=i(l.path),f=e.substring(p.length),m="",h=-1!==f.indexOf("/"),v=h?/(.+\/)(.+):(\d+)(:.*)/:/(.+):(\d+)(:.*)/ +if(h){var g=f.match(v)||[],b=(0,t.default)(g,5) +m=b[1],u=b[2],c=b[3],d=b[4]}else{var y=f.match(v)||[],E=(0,t.default)(y,4) +u=E[1],c=E[2],d=E[3]}if(u&&c&&d){var x=l.main_app?this.commitSha:null +o=r({repo:l.url,path:m,filename:u,lineNumber:c,commitSha:x})}}return o},findGithubURL:function(e,t){return a(e,n.default.get("gems_dir"))?this.GithubURLForGem(t):this.GithubURLForApp(e)},commitSha:Ember.computed("env",function(){var e=null +return Array.isArray(this.env)?e=this.env.map(function(e){return e.application_version}).filter(function(e){return e})[0]:this.env&&(e=this.env.application_version),e||n.default.get("application_version")}),lines:Ember.computed("backtrace","commitSha",function(){var e=this +return this.backtrace&&0!==this.backtrace.length?this.backtrace.split("\n").map(function(t){var s=function(e){if(a(e,n.default.get("gems_dir"))){var t=n.default.get("gems_dir") +return e.substring(t.length)}return e}(t) +return{line:s,url:e.findGithubURL(t,s)}}):[]})}) +e.default=o}),define("client-app/components/env-tab",["exports","client-app/lib/utilities","client-app/lib/preload"],function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var a=Ember.Component.extend({didUpdateAttrs:function(){this.set("expanded",null)},currentEnv:Ember.computed("isEnvArray","currentEnvPosition",function(){return this.isEnvArray?this.message.env[this.currentEnvPosition]:this.message.env}),isEnvArray:Ember.computed("message.env",function(){return Array.isArray(this.get("message.env"))}),html:Ember.computed("isEnvArray","currentEnv","expanded.[]",function(){var e=this if(this.isEnvArray){var a=Em.$.extend({},this.currentEnv) return(n.default.get("env_expandable_keys")||[]).forEach(function(t){if(a.hasOwnProperty(t)&&!Array.isArray(a[t])){var n=[a[t]] @@ -101,7 +118,8 @@ var a={name:"export-application-global",initialize:n} e.default=a}),define("client-app/instance-initializers/ember-data",["exports","ember-data/initialize-store-service"],function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n={name:"ember-data",initialize:t.default} e.default=n}),define("client-app/lib/preload",["exports"],function(e){var t -Object.defineProperty(e,"__esModule",{value:!0}),e.init=a,e.default=void 0 +Object.defineProperty(e,"__esModule",{value:!0}),e.init=a,e.mutatePreload=function(e,s){n||a() +Em.set(t,e,s)},e.uninitialize=function(){n=!1},e.default=void 0 var n=!1 function a(){var e=document.getElementById("preloaded-data").dataset t=Em.$.extend(JSON.parse(e.preloaded),{rootPath:e.rootPath}),n=!0}var s={get:function(e){return n||a(),Em.get(t,e)}} @@ -153,7 +171,8 @@ return this.search&&this.search.length>0||e&&e.length<6}),moreBefore:Ember.compu this.load({before:a,knownGroups:s}).then(function(t){return e.updateCanLoadMore(t)})},regexSearch:Ember.computed("search",function(){var e=this.search if(e&&e.length>2&&"/"===e[0]){var t=e.match(/\/(.*)\/(.*)/) if(t&&3===t.length)try{return new RegExp(t[1],t[2])}catch(n){}}}),toObjects:function(e){return e.map(function(e){return e.group?a.default.create(e):n.default.create(e)})}}) -e.default=i}),define("client-app/models/message",["exports","client-app/lib/utilities","client-app/lib/preload"],function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=i}) +define("client-app/models/message",["exports","client-app/lib/utilities","client-app/lib/preload"],function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var a=Em.Object.extend({MAX_LEN:200,expand:function(){this.set("expanded",!0)},solve:function(){return(0,t.ajax)("/solve/"+this.get("key"),{type:"PUT"})},destroy:function(){return(0,t.ajax)("/message/"+this.get("key"),{type:"DELETE"})},protect:function(){return this.set("protected",!0),(0,t.ajax)("/protect/"+this.get("key"),{type:"PUT"})},unprotect:function(){return this.set("protected",!1),(0,t.ajax)("/unprotect/"+this.get("key"),{type:"DELETE"})},showCount:Ember.computed("count",function(){return this.get("count")>1}),hasMore:Ember.computed("message","expanded",function(){var e=this.get("message") return!this.get("expanded")&&e.length>this.MAX_LEN}),shareUrl:Ember.computed("key",function(){return n.default.get("rootPath")+"/show/"+this.get("key")}),displayMessage:Ember.computed("message","expanded",function(){var e=this.get("message") return!this.get("expanded")&&e.length>this.MAX_LEN&&(e=e.substr(0,this.MAX_LEN)),e}),updateFromObject:function(e){this.set("count",e.get("count"))},canSolve:Ember.computed("env.{application_version,length}",function(){var e=this.get("backtrace"),t=this.get("env") @@ -165,8 +184,7 @@ case 4:return"fatal"}}),glyph:Ember.computed("severity",function(){switch(this.g case 2:return"<i class='fa fa-exclamation-circle warning'></i>" case 3:return"<i class='fa fa-times-circle error'></i>" case 4:return"<i class='fa fa-times-circle fatal'></i>"}})}) -e.default=a}) -define("client-app/models/pattern-item",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=a}),define("client-app/models/pattern-item",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var t=Ember.Object.extend({isNew:!1,value:"",valueBuffer:"",error:null,saving:!1,count:0,init:function(){this._super.apply(this,arguments),this.set("valueBuffer",this.get("value"))},updateValue:function(e){this.setProperties({value:e,valueBuffer:e})},hasBuffer:Ember.computed("value","valueBuffer",function(){return this.get("value")!==this.get("valueBuffer")}),zeroCount:Ember.computed("count",function(){return this.get("count")<=0})}) e.default=t}),define("client-app/resolver",["exports","ember-resolver"],function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=t.default @@ -190,10 +208,12 @@ e.default=a}),define("client-app/services/ajax",["exports","ember-ajax/services/ var t=Ember.HTMLBars.template({id:"yOsrYpw5",block:'{"symbols":[],"statements":[[1,[21,"update-time"],false],[0,"\\n"],[1,[21,"outlet"],false],[0,"\\n"]],"hasEval":false}',meta:{moduleName:"client-app/templates/application.hbs"}}) e.default=t}),define("client-app/templates/components/actions-menu",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var t=Ember.HTMLBars.template({id:"iO5WYaED",block:'{"symbols":["&default"],"statements":[[4,"if",[[23,["actionsInMenu"]]],null,{"statements":[[4,"if",[[23,["showMenu"]]],null,{"statements":[[0," "],[7,"div"],[11,"class","actions-menu"],[9],[0,"\\n "],[14,1],[0,"\\n "],[10],[0,"\\n"]],"parameters":[]},null],[0," "],[7,"button"],[11,"class","expand btn no-text"],[9],[7,"i"],[11,"class","fa fa-ellipsis-h"],[9],[10],[3,"action",[[22,0,[]],"expandMenu"]],[10],[0,"\\n "],[7,"button"],[11,"class","share btn"],[9],[7,"i"],[11,"class","fa fa-share"],[9],[10],[7,"span"],[9],[0,"Share"],[10],[3,"action",[[22,0,[]],"share"]],[10],[0,"\\n"]],"parameters":[]},{"statements":[[0," "],[14,1],[0,"\\n "],[7,"button"],[11,"class","share btn"],[9],[7,"i"],[11,"class","fa fa-share"],[9],[10],[7,"span"],[9],[0,"Share"],[10],[3,"action",[[22,0,[]],"share"]],[10],[0,"\\n"]],"parameters":[]}]],"hasEval":false}',meta:{moduleName:"client-app/templates/components/actions-menu.hbs"}}) +e.default=t}),define("client-app/templates/components/back-trace",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +var t=Ember.HTMLBars.template({id:"AdYyABa6",block:'{"symbols":["line"],"statements":[[4,"each",[[23,["lines"]]],null,{"statements":[[7,"div"],[11,"class","backtrace-line"],[9],[1,[22,1,["line"]],false],[4,"if",[[22,1,["url"]]],null,{"statements":[[7,"a"],[12,"href",[28,[[22,1,["url"]]]]],[11,"target","_blank"],[11,"class","line-link"],[9],[7,"i"],[11,"class","fa fa-external-link-square"],[9],[10],[10]],"parameters":[]},null],[10]],"parameters":[1]},null]],"hasEval":false}',meta:{moduleName:"client-app/templates/components/back-trace.hbs"}}) e.default=t}),define("client-app/templates/components/env-tab",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var t=Ember.HTMLBars.template({id:"f/S09Y5R",block:'{"symbols":[],"statements":[[4,"if",[[23,["isEnvArray"]]],null,{"statements":[[0," "],[1,[27,"page-nav",null,[["list","position","extraClasses","navigate"],[[23,["message","env"]],[23,["currentEnvPosition"]],"env-nav",[23,["envChangedAction"]]]]],false],[0,"\\n"]],"parameters":[]},null],[1,[21,"html"],true],[0,"\\n"]],"hasEval":false}',meta:{moduleName:"client-app/templates/components/env-tab.hbs"}}) e.default=t}),define("client-app/templates/components/message-info",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -var t=Ember.HTMLBars.template({id:"w2mnjd51",block:'{"symbols":["btn"],"statements":[[7,"div"],[11,"class","message-info"],[9],[0,"\\n"],[4,"tabbed-section",null,[["onTabChange"],[[27,"action",[[22,0,[]],"tabChanged"],null]]],{"statements":[[4,"tab-contents",null,[["name","hint","currentMessage"],["info","show info",[23,["currentMessage"]]]],{"statements":[[4,"if",[[23,["showTitle"]]],null,{"statements":[[0," "],[7,"h3"],[9],[0,"Message\\n"],[4,"if",[[23,["currentMessage","showCount"]]],null,{"statements":[[0," ("],[1,[23,["currentMessage","count"]],false],[0," copies reported)\\n"]],"parameters":[]},null],[0," "],[10],[0,"\\n"]],"parameters":[]},null],[0," "],[7,"pre"],[9],[1,[23,["currentMessage","message"]],false],[10],[0,""]],"parameters":[]},null],[4,"tab-contents",null,[["name","defaultTab","hint","currentMessage"],["backtrace","true","show backtrace",[23,["currentMessage"]]]],{"statements":[[4,"if",[[23,["showTitle"]]],null,{"statements":[[0," "],[7,"h3"],[9],[0,"Backtrace"],[10],[0,"\\n"]],"parameters":[]},null],[0," "],[7,"pre"],[9],[1,[23,["currentMessage","backtrace"]],false],[10],[0,""]],"parameters":[]},null],[4,"tab-contents",null,[["className","name","hint","currentMessage"],["env","env","show environment",[23,["currentMessage"]]]],{"statements":[[4,"if",[[23,["currentMessage"]]],null,{"statements":[[4,"if",[[23,["currentMessage","env"]]],null,{"statements":[[4,"if",[[23,["showTitle"]]],null,{"statements":[[0," "],[7,"h3"],[9],[0,"Env"],[10],[0,"\\n"]],"parameters":[]},null],[0," "],[1,[27,"env-tab",null,[["message","currentEnvPosition","envChangedAction"],[[23,["currentMessage"]],[23,["currentEnvPosition"]],[23,["envChangedAction"]]]]],false],[0,"\\n"]],"parameters":[]},{"statements":[[4,"if",[[23,["loadingEnv"]]],null,{"statements":[[0," Loading env...\\n"]],"parameters":[]},{"statements":[[0," No env for this message.\\n "]],"parameters":[]}]],"parameters":[]}]],"parameters":[]},null]],"parameters":[]},null]],"parameters":[]},null],[0,"\\n"],[4,"if",[[23,["currentMessage"]]],null,{"statements":[[0," "],[7,"div"],[11,"class","message-actions"],[9],[0,"\\n"],[4,"actions-menu",null,[["actionsInMenu","share"],[[23,["actionsInMenu"]],[27,"action",[[22,0,[]],"share"],null]]],{"statements":[[4,"each",[[23,["buttons"]]],null,{"statements":[[0," "],[7,"button"],[12,"class",[28,[[22,1,["klass"]]," btn ",[27,"if",[[22,1,["danger"]],"danger",""],null]]]],[9],[0,"\\n "],[7,"i"],[12,"class",[28,["fa fa-",[22,1,["icon"]]]]],[9],[10],[0,"\\n "],[7,"span"],[9],[1,[22,1,["label"]],false],[10],[0,"\\n "],[3,"action",[[22,0,[]],[22,1,["action"]]]],[10],[0,"\\n"]],"parameters":[1]},null]],"parameters":[]},null],[0," "],[10],[0,"\\n"]],"parameters":[]},null],[10],[0,"\\n"]],"hasEval":false}',meta:{moduleName:"client-app/templates/components/message-info.hbs"}}) +var t=Ember.HTMLBars.template({id:"JDXfe1on",block:'{"symbols":["btn"],"statements":[[7,"div"],[11,"class","message-info"],[9],[0,"\\n"],[4,"tabbed-section",null,[["onTabChange"],[[27,"action",[[22,0,[]],"tabChanged"],null]]],{"statements":[[4,"tab-contents",null,[["name","hint","currentMessage"],["info","show info",[23,["currentMessage"]]]],{"statements":[[4,"if",[[23,["showTitle"]]],null,{"statements":[[0," "],[7,"h3"],[9],[0,"Message\\n"],[4,"if",[[23,["currentMessage","showCount"]]],null,{"statements":[[0," ("],[1,[23,["currentMessage","count"]],false],[0," copies reported)\\n"]],"parameters":[]},null],[0," "],[10],[0,"\\n"]],"parameters":[]},null],[0," "],[7,"pre"],[9],[1,[23,["currentMessage","message"]],false],[10],[0,""]],"parameters":[]},null],[4,"tab-contents",null,[["name","defaultTab","hint","currentMessage"],["backtrace","true","show backtrace",[23,["currentMessage"]]]],{"statements":[[4,"if",[[23,["showTitle"]]],null,{"statements":[[0," "],[7,"h3"],[9],[0,"Backtrace"],[10],[0,"\\n"]],"parameters":[]},null],[0," "],[7,"pre"],[9],[1,[27,"back-trace",null,[["class","backtrace","env"],["backtrace",[23,["currentMessage","backtrace"]],[23,["currentMessage","env"]]]]],false],[10],[0,""]],"parameters":[]},null],[4,"tab-contents",null,[["className","name","hint","currentMessage"],["env","env","show environment",[23,["currentMessage"]]]],{"statements":[[4,"if",[[23,["currentMessage"]]],null,{"statements":[[4,"if",[[23,["currentMessage","env"]]],null,{"statements":[[4,"if",[[23,["showTitle"]]],null,{"statements":[[0," "],[7,"h3"],[9],[0,"Env"],[10],[0,"\\n"]],"parameters":[]},null],[0," "],[1,[27,"env-tab",null,[["message","currentEnvPosition","envChangedAction"],[[23,["currentMessage"]],[23,["currentEnvPosition"]],[23,["envChangedAction"]]]]],false],[0,"\\n"]],"parameters":[]},{"statements":[[4,"if",[[23,["loadingEnv"]]],null,{"statements":[[0," Loading env...\\n"]],"parameters":[]},{"statements":[[0," No env for this message.\\n "]],"parameters":[]}]],"parameters":[]}]],"parameters":[]},null]],"parameters":[]},null]],"parameters":[]},null],[0,"\\n"],[4,"if",[[23,["currentMessage"]]],null,{"statements":[[0," "],[7,"div"],[11,"class","message-actions"],[9],[0,"\\n"],[4,"actions-menu",null,[["actionsInMenu","share"],[[23,["actionsInMenu"]],[27,"action",[[22,0,[]],"share"],null]]],{"statements":[[4,"each",[[23,["buttons"]]],null,{"statements":[[0," "],[7,"button"],[12,"class",[28,[[22,1,["klass"]]," btn ",[27,"if",[[22,1,["danger"]],"danger",""],null]]]],[9],[0,"\\n "],[7,"i"],[12,"class",[28,["fa fa-",[22,1,["icon"]]]]],[9],[10],[0,"\\n "],[7,"span"],[9],[1,[22,1,["label"]],false],[10],[0,"\\n "],[3,"action",[[22,0,[]],[22,1,["action"]]]],[10],[0,"\\n"]],"parameters":[1]},null]],"parameters":[]},null],[0," "],[10],[0,"\\n"]],"parameters":[]},null],[10],[0,"\\n"]],"hasEval":false}',meta:{moduleName:"client-app/templates/components/message-info.hbs"}}) e.default=t}),define("client-app/templates/components/message-row",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var t=Ember.HTMLBars.template({id:"pF0hQ9a+",block:'{"symbols":[],"statements":[[7,"div"],[11,"class","count"],[9],[0,"\\n"],[4,"if",[[23,["model","showCount"]]],null,{"statements":[[0," "],[1,[23,["model","count"]],false],[0,"\\n"]],"parameters":[]},null],[10],[0,"\\n"],[7,"div"],[11,"class","severity"],[9],[1,[23,["model","glyph"]],true],[10],[0,"\\n"],[7,"div"],[11,"class","message-body"],[9],[0,"\\n "],[1,[23,["model","displayMessage"]],false],[0,"\\n"],[10],[0,"\\n"],[7,"div"],[11,"class","protected"],[9],[0,"\\n"],[4,"if",[[23,["model","protected"]]],null,{"statements":[[0," "],[7,"i"],[11,"title","message is protected, clearing will not remove it"],[11,"class","fa fa-lock"],[9],[10],[0,"\\n"]],"parameters":[]},null],[10],[0,"\\n"],[7,"div"],[11,"class","time"],[9],[1,[27,"time-formatter",null,[["timestamp"],[[23,["model","timestamp"]]]]],false],[10],[0,"\\n"]],"hasEval":false}',meta:{moduleName:"client-app/templates/components/message-row.hbs"}}) e.default=t}),define("client-app/templates/components/page-nav",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 @@ -213,4 +233,4 @@ var t=Ember.HTMLBars.template({id:"VCEsnuWV",block:'{"symbols":[],"statements":[ e.default=t}),define("client-app/templates/show",["exports"],function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var t=Ember.HTMLBars.template({id:"Z9BtSx7U",block:'{"symbols":[],"statements":[[4,"link-to",["index"],null,{"statements":[[0,"Recent"]],"parameters":[]},null],[0,"\\n"],[7,"div"],[11,"id","bottom-panel"],[11,"class","full"],[9],[0,"\\n "],[1,[27,"message-info",null,[["currentMessage","showTitle","envChangedAction","currentEnvPosition","actionsInMenu"],[[23,["model"]],"true",[27,"action",[[22,0,[]],"envChanged"],null],[23,["envPosition"]],false]]],false],[0,"\\n"],[10],[0,"\\n"]],"hasEval":false}',meta:{moduleName:"client-app/templates/show.hbs"}}) e.default=t}),define("client-app/config/environment",[],function(){try{var e="client-app/config/environment",t=document.querySelector('meta[name="'+e+'"]').getAttribute("content"),n={default:JSON.parse(decodeURIComponent(t))} -return Object.defineProperty(n,"__esModule",{value:!0}),n}catch(a){throw new Error('Could not read config from meta tag with name "'+e+'".')}}),runningTests||require("client-app/app").default.create({name:"client-app",version:"0.0.0+43e54768"}) +return Object.defineProperty(n,"__esModule",{value:!0}),n}catch(a){throw new Error('Could not read config from meta tag with name "'+e+'".')}}),runningTests||require("client-app/app").default.create({name:"client-app",version:"0.0.0+39c1cae6"}) diff --git a/assets/stylesheets/client-app.css b/assets/stylesheets/client-app.css index 85757f6f..e523cfac 100644 --- a/assets/stylesheets/client-app.css +++ b/assets/stylesheets/client-app.css @@ -1 +1 @@ -.divider,.message-info,.nav-controls.group-nav{border-bottom:1px solid #ddd}body{font-family:Arial,"Liberation Sans","DejaVu Sans",sans-serif;font-size:12px}body.mobile,body.mobile .message{font-size:14px}pre{font-family:"Roboto Mono",Consolas,Monaco,Ubuntu Mono,monospace}table.env-table tbody tr td{border-top:none;line-height:18px;height:18px;vertical-align:top}table.env-table,table.env-table table{border-spacing:0;border-collapse:collapse}table.env-table td{padding-right:5px}tbody tr{width:98%}.message-row{font-family:Roboto;display:flex}.pattern-wrapper .pattern-input,.settings-section .api-error{font-family:Consolas,"Roboto Mono",Monaco,Ubuntu Mono,monospace}.message-row .protected,.message-row .severity{text-align:center;width:25px;flex-grow:0;flex-shrink:0;font-size:12px}.message-row div{border-top:.5px #e9e9e9 solid;padding-top:1px;padding-bottom:1px;line-height:25px}.message-row .count{width:30px;flex-grow:0;flex-shrink:0;padding-right:4px;box-sizing:border-box;font-size:11px;font-weight:700;text-align:right}.message-row .message-body{flex-grow:1;flex-shrink:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:13px}.message-row .time{flex-grow:0;flex-shrink:0;color:#999;vertical-align:top;font-size:12px;padding-right:8px}.action-panel .search,.action-panel i.fa,.btn .fa,.btn span,.pattern-wrapper .shrink,.search-clear-all .clear,input,label span{vertical-align:middle}.message-row:hover{background-color:#f8f8f8;cursor:pointer}.message-row.selected{background-color:#dfdfdf}i.fatal{color:#e00}i.error{color:#900}i.warning{color:#feb800}.debug{color:#777}.btn,.tabs a{text-decoration:none;color:#333}.action-panel .search{border:1px solid #ddd;padding:3px;box-sizing:border-box}#log-table .show-more{text-align:center;height:30px;line-height:30px;text-decoration:none;background-color:#ddd;cursor:pointer;margin-top:8px}#overlay,.divider{cursor:row-resize}#bottom-panel{position:fixed;bottom:0;left:0;right:0;height:300px;background-color:#f1f1f1;padding:0 8px 8px;z-index:2}#bottom-panel.full{position:static;background-color:inherit;height:90%}#bottom-panel.full>div{padding-bottom:40px}#bottom-panel.full .tabs{display:none}#bottom-panel.full .message-info{position:static}#bottom-panel.full .message-info .content{display:block;position:static}#bottom-panel.full button.delete,.hidden{display:none}#bottom-panel.full .save,#bottom-panel.full .share{bottom:10px}#bottom-panel.full .message-actions button{margin-top:8px}#bottom-panel.full .message-actions{position:fixed;height:40px;width:100%;left:0;bottom:0;background-color:#eee;border-top:1px solid #dfdfdf;padding-left:10px}.divider,.tabs{border-top:1px solid #ddd}.message-actions{position:absolute;bottom:5px;right:0;margin-right:10px}.message-actions button{margin-left:5px}.divider{position:fixed;bottom:310px;left:0;right:0;height:15px;background-color:#fafafa}.divider div{margin:auto;width:24px;height:1px;background-color:#ccc;position:relative}.divider .line-1{top:5px}.divider .line-2{top:6px}.divider .line-3{top:7px}#top-panel{position:fixed;top:0;left:0;right:0;bottom:320px;overflow:auto}.action-panel,.message-info,.nav-controls.group-nav{position:absolute;left:0;right:0}.message-info{top:0}#bottom-panel.group-view .message-info{top:43px}.action-panel{bottom:0;font-weight:700}.action-panel input{margin:0}.severity-filters label{margin-right:18px}.search-clear-all .clear{float:right}#log-table{margin:auto;width:99%}.message-info .env-table,.message-info pre{position:relative;margin:5px 10px 10px}#overlay{position:fixed;z-index:99999;top:0;bottom:0;left:0;right:0;opacity:0}.message-info .content,.tabs{position:absolute;left:0;right:0}.message-info .content{top:0;bottom:35px;overflow:auto;display:none}.message-info .content.active{display:block}.tabs{bottom:13px;list-style-type:none;margin:0 0 5px;padding:0 0 0 10px}.tabs a,.tabs li{position:relative}.tabs li{float:left;padding-right:5px;margin:0}.tabs a{top:6px;border:1px solid #ddd;border-top:none;border-bottom-left-radius:5px;border-bottom-right-radius:5px;padding:6px;background-color:#e1e1e1}.tabs a.active{border-top:1px solid #f1f1f1;background-color:#f1f1f1}.btn{display:inline-block;margin:0;padding:5px 12px;font-size:1em;line-height:0;text-align:center;cursor:pointer;transition:all .25s;background-color:#ddd;border:none;font-weight:400}.btn:hover{color:#000;background-color:#ccc}.btn .fa{margin-right:7px}.btn:active{text-shadow:none}.btn.danger:hover{background-color:#c63c1b;color:#eee}.btn.ok{background-color:#3781dc;color:#fff}.btn.ok:hover{background-color:#286dc2;color:#fff}.search-clear-all .clear,.search-clear-all .search{height:100%}.search-clear-all .clear,.search-clear-all .search,.severity-filters label{align-self:center}.search-clear-all .footer-btns .settings{margin:0 7px}.search-clear-all{display:flex;justify-content:space-between}.search-clear-all .search{min-width:0;flex-shrink:2}.footer-btns{flex-grow:0;flex-shrink:0}@media (min-width:770px){.search-clear-all,.severity-filters{height:100%}.more-wrapping,.severity-filters{display:flex}.severity-filters{float:left}.action-panel{padding:10px;box-sizing:border-box;height:42px}.message-info{bottom:42px}}@media (max-width:770px){.severity-filters{padding:10px}.search-clear-all{padding:0 10px 10px}.action-panel{height:69px}.message-info{bottom:69px}}@media (max-width:430px){.severity-filters{overflow-x:scroll;overflow-y:hidden;white-space:nowrap}.more-wrapping:after,.more-wrapping:before{content:"";position:absolute;height:18px}.more-wrapping:before{width:15px;margin-left:-10px;background:linear-gradient(to right,#f1f1f1 0,rgba(241,241,241,.001) 100%)}.more-wrapping:after{right:0;width:23px;background:linear-gradient(to left,#f1f1f1 0,rgba(241,241,241,.001) 100%)}}.btn.no-text .fa{margin:0}.btn[disabled]{opacity:.5}.actions-menu{position:absolute;background:#fafafa;display:inline-flex;flex-direction:column;bottom:27px;right:45px;width:115px;padding:5px 5px 0;box-shadow:0 4px 14px rgba(0,0,0,.15);z-index:3}.actions-menu button{margin:0 0 5px;height:27px}.nav-controls{padding:10px}#bottom-panel:not(.full) .nav-controls.env-nav{position:sticky;position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;top:0;background:#f1f1f1;z-index:1;border-bottom:1px solid #ddd}.current-number{margin:0 7px}.expand-list{text-decoration:underline;color:#00f;cursor:pointer}.settings-page{max-width:1110px;margin-right:auto;margin-left:auto;font-size:15px;padding:0 10px 70px}.settings-header{display:flex;align-items:center;margin-bottom:5px}.settings-header .header-title{flex-grow:1}.settings-header .header-logo{width:50px;height:50px}.settings-section{border-top:1px solid #ddd;padding-top:15px}.settings-section .section-title{margin-top:0}.settings-section .subsection-title{margin-bottom:10px}.settings-section .tip{font-style:italic;font-size:13px;color:grey}.settings-section .pattern-wrapper{margin-top:10px;font-size:16px;display:flex;height:33px}.settings-section .api-error{margin-top:5px;color:red}.pattern-wrapper .pattern-input{width:600px;font-size:inherit;padding:5px 0;height:100%;box-sizing:border-box;vertical-align:middle;flex-grow:1;flex-shrink:1}.retro-checkbox .checkbox{margin:0}.retro-checkbox{margin-top:7px;margin-bottom:15px}.btn.new-pattern{height:100%;line-height:18px}.pattern-wrapper .shrink{height:100%;width:40px;text-align:center;box-sizing:border-box;flex-grow:0;flex-shrink:0;margin-left:10px}.fa{opacity:.7}.pattern-wrapper .shrink.reset{background:unset;width:unset;padding:0;margin-left:8px}.grouping-patterns button.new-pattern{margin-top:10px} \ No newline at end of file +.divider,.message-info,.nav-controls.group-nav{border-bottom:1px solid #ddd}body{font-family:Arial,"Liberation Sans","DejaVu Sans",sans-serif;font-size:12px}body.mobile,body.mobile .message{font-size:14px}pre{font-family:"Roboto Mono",Consolas,Monaco,Ubuntu Mono,monospace}table.env-table tbody tr td{border-top:none;line-height:18px;height:18px;vertical-align:top}table.env-table,table.env-table table{border-spacing:0;border-collapse:collapse}table.env-table td{padding-right:5px}tbody tr{width:98%}.message-row{font-family:Roboto;display:flex}.pattern-wrapper .pattern-input,.settings-section .api-error{font-family:Consolas,"Roboto Mono",Monaco,Ubuntu Mono,monospace}.message-row .protected,.message-row .severity{text-align:center;width:25px;flex-grow:0;flex-shrink:0;font-size:12px}.message-row div{border-top:.5px #e9e9e9 solid;padding-top:1px;padding-bottom:1px;line-height:25px}.message-row .count{width:30px;flex-grow:0;flex-shrink:0;padding-right:4px;box-sizing:border-box;font-size:11px;font-weight:700;text-align:right}.message-row .message-body{flex-grow:1;flex-shrink:1;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:13px}.message-row .time{flex-grow:0;flex-shrink:0;color:#999;vertical-align:top;font-size:12px;padding-right:8px}.action-panel .search,.action-panel i.fa,.btn .fa,.btn span,.pattern-wrapper .shrink,.search-clear-all .clear,input,label span{vertical-align:middle}.message-row:hover{background-color:#f8f8f8;cursor:pointer}.message-row.selected{background-color:#dfdfdf}i.fatal{color:#e00}i.error{color:#900}i.warning{color:#feb800}.debug{color:#777}.btn,.tabs a{text-decoration:none;color:#333}.action-panel .search{border:1px solid #ddd;padding:3px;box-sizing:border-box}#log-table .show-more{text-align:center;height:30px;line-height:30px;text-decoration:none;background-color:#ddd;cursor:pointer;margin-top:8px}#overlay,.divider{cursor:row-resize}#bottom-panel{position:fixed;bottom:0;left:0;right:0;height:300px;background-color:#f1f1f1;padding:0 8px 8px;z-index:2}#bottom-panel.full{position:static;background-color:inherit;height:90%}#bottom-panel.full>div{padding-bottom:40px}#bottom-panel.full .tabs{display:none}#bottom-panel.full .message-info{position:static}#bottom-panel.full .message-info .content{display:block;position:static}#bottom-panel.full button.delete,.hidden{display:none}#bottom-panel.full .save,#bottom-panel.full .share{bottom:10px}#bottom-panel.full .message-actions button{margin-top:8px}#bottom-panel.full .message-actions{position:fixed;height:40px;width:100%;left:0;bottom:0;background-color:#eee;border-top:1px solid #dfdfdf;padding-left:10px}.divider,.tabs{border-top:1px solid #ddd}.message-actions{position:absolute;bottom:5px;right:0;margin-right:10px}.message-actions button{margin-left:5px}.divider{position:fixed;bottom:310px;left:0;right:0;height:15px;background-color:#fafafa}.divider div{margin:auto;width:24px;height:1px;background-color:#ccc;position:relative}.divider .line-1{top:5px}.divider .line-2{top:6px}.divider .line-3{top:7px}#top-panel{position:fixed;top:0;left:0;right:0;bottom:320px;overflow:auto}.action-panel,.message-info,.nav-controls.group-nav{position:absolute;left:0;right:0}.message-info{top:0}#bottom-panel.group-view .message-info{top:43px}.action-panel{bottom:0;font-weight:700}.action-panel input{margin:0}.severity-filters label{margin-right:18px}.search-clear-all .clear{float:right}#log-table{margin:auto;width:99%}.message-info .env-table,.message-info pre{position:relative;margin:5px 10px 10px}#overlay{position:fixed;z-index:99999;top:0;bottom:0;left:0;right:0;opacity:0}.message-info .content,.tabs{position:absolute;left:0;right:0}.message-info .content{top:0;bottom:35px;overflow:auto;display:none}.message-info .content.active{display:block}.tabs{bottom:13px;list-style-type:none;margin:0 0 5px;padding:0 0 0 10px}.tabs a,.tabs li{position:relative}.tabs li{float:left;padding-right:5px;margin:0}.tabs a{top:6px;border:1px solid #ddd;border-top:none;border-bottom-left-radius:5px;border-bottom-right-radius:5px;padding:6px;background-color:#e1e1e1}.tabs a.active{border-top:1px solid #f1f1f1;background-color:#f1f1f1}.btn{display:inline-block;margin:0;padding:5px 12px;font-size:1em;line-height:0;text-align:center;cursor:pointer;transition:all .25s;background-color:#ddd;border:none;font-weight:400}.btn:hover{color:#000;background-color:#ccc}.btn .fa{margin-right:7px}.btn:active{text-shadow:none}.btn.danger:hover{background-color:#c63c1b;color:#eee}.btn.ok{background-color:#3781dc;color:#fff}.btn.ok:hover{background-color:#286dc2;color:#fff}.search-clear-all .clear,.search-clear-all .search{height:100%}.search-clear-all .clear,.search-clear-all .search,.severity-filters label{align-self:center}.search-clear-all .footer-btns .settings{margin:0 7px}.search-clear-all{display:flex;justify-content:space-between}.search-clear-all .search{min-width:0;flex-shrink:2}.footer-btns{flex-grow:0;flex-shrink:0}@media (min-width:770px){.search-clear-all,.severity-filters{height:100%}.more-wrapping,.severity-filters{display:flex}.severity-filters{float:left}.action-panel{padding:10px;box-sizing:border-box;height:42px}.message-info{bottom:42px}}@media (max-width:770px){.severity-filters{padding:10px}.search-clear-all{padding:0 10px 10px}.action-panel{height:69px}.message-info{bottom:69px}}@media (max-width:430px){.severity-filters{overflow-x:scroll;overflow-y:hidden;white-space:nowrap}.more-wrapping:after,.more-wrapping:before{content:"";position:absolute;height:18px}.more-wrapping:before{width:15px;margin-left:-10px;background:linear-gradient(to right,#f1f1f1 0,rgba(241,241,241,.001) 100%)}.more-wrapping:after{right:0;width:23px;background:linear-gradient(to left,#f1f1f1 0,rgba(241,241,241,.001) 100%)}}.btn.no-text .fa{margin:0}.btn[disabled]{opacity:.5}.actions-menu{position:absolute;background:#fafafa;display:inline-flex;flex-direction:column;bottom:27px;right:45px;width:115px;padding:5px 5px 0;box-shadow:0 4px 14px rgba(0,0,0,.15);z-index:3}.actions-menu button{margin:0 0 5px;height:27px}.nav-controls{padding:10px}#bottom-panel:not(.full) .nav-controls.env-nav{position:sticky;position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;top:0;background:#f1f1f1;z-index:1;border-bottom:1px solid #ddd}.current-number{margin:0 7px}.expand-list{text-decoration:underline;color:#00f;cursor:pointer}.settings-page{max-width:1110px;margin-right:auto;margin-left:auto;font-size:15px;padding:0 10px 70px}.settings-header{display:flex;align-items:center;margin-bottom:5px}.settings-header .header-title{flex-grow:1}.settings-header .header-logo{width:50px;height:50px}.settings-section{border-top:1px solid #ddd;padding-top:15px}.settings-section .section-title{margin-top:0}.settings-section .subsection-title{margin-bottom:10px}.settings-section .tip{font-style:italic;font-size:13px;color:grey}.settings-section .pattern-wrapper{margin-top:10px;font-size:16px;display:flex;height:33px}.settings-section .api-error{margin-top:5px;color:red}.pattern-wrapper .pattern-input{width:600px;font-size:inherit;padding:5px 0;height:100%;box-sizing:border-box;vertical-align:middle;flex-grow:1;flex-shrink:1}.retro-checkbox .checkbox{margin:0}.retro-checkbox{margin-top:7px;margin-bottom:15px}.btn.new-pattern{height:100%;line-height:18px}.pattern-wrapper .shrink{height:100%;width:40px;text-align:center;box-sizing:border-box;flex-grow:0;flex-shrink:0;margin-left:10px}.fa{opacity:.7}.pattern-wrapper .shrink.reset{background:unset;width:unset;padding:0;margin-left:8px}.grouping-patterns button.new-pattern{margin-top:10px}.backtrace-line .line-link{color:#9e9e9e;margin-left:3px} \ No newline at end of file diff --git a/client-app/app/components/back-trace.js b/client-app/app/components/back-trace.js new file mode 100644 index 00000000..653c228b --- /dev/null +++ b/client-app/app/components/back-trace.js @@ -0,0 +1,148 @@ +import Component from "@ember/component"; +import Preloaded from "client-app/lib/preload"; +import { computed } from "@ember/object"; + +function startsWith(str, search) { + if (!str || !search || search.length > str.length) { + return false; + } + return str.substring(0, search.length) == search; +} + +function backtraceLinksEnabled() { + return Preloaded.get("backtrace_links_enabled"); +} + +function appendSlash(str) { + if (str && str[str.length - 1] !== "/") { + return str + "/"; + } else { + return str; + } +} + +function assembleURL({ repo, path, filename, lineNumber, commitSha = null }) { + let url = appendSlash(repo); + if (!/\/tree\//.test(url)) { + url += "blob/"; + url += commitSha ? `${commitSha}/` : "master/"; + } + url += path + filename; + if (/^[0-9]+$/.test(lineNumber)) { + url += `#L${lineNumber}`; + } + return url; +} + +function shortenLine(line) { + const isGem = startsWith(line, Preloaded.get("gems_dir")); + if (isGem) { + const gemsDir = Preloaded.get("gems_dir"); + return line.substring(gemsDir.length); + } else { + return line; + } +} + +export default Component.extend({ + GithubURLForGem(line) { + let url = null; + if (!backtraceLinksEnabled()) { + return url; + } + + const regexResults = line.match(/([^/]+)\/(.+\/)(.+):(\d+):.*/); + const [, gemWithVersion, path, filename, lineNumber] = regexResults || []; + const gemsData = Preloaded.get("gems_data"); + const match = gemsData + .filter(g => startsWith(gemWithVersion, `${g.name}-`)) + .sortBy("name.length") + .reverse()[0]; + + if (match) { + url = assembleURL({ repo: match.url, path, filename, lineNumber }); + } + return url; + }, + + GithubURLForApp(line) { + let url = null; + + if (!backtraceLinksEnabled()) { + return url; + } + + const projectDirs = Preloaded.get("directories"); + + const match = projectDirs + .filter(dir => startsWith(line, dir.path)) + .sortBy("path.length") + .reverse()[0]; + + if (match) { + const root = appendSlash(match.path); + const lineWithoutRoot = line.substring(root.length); + + let path = "", + filename, + lineNumber, + remaining; + + const hasSlash = lineWithoutRoot.indexOf("/") !== -1; + const regex = hasSlash ? /(.+\/)(.+):(\d+)(:.*)/ : /(.+):(\d+)(:.*)/; + + if (hasSlash) { + [, path, filename, lineNumber, remaining] = + lineWithoutRoot.match(regex) || []; + } else { + [, filename, lineNumber, remaining] = + lineWithoutRoot.match(regex) || []; + } + + if (filename && lineNumber && remaining) { + const commitSha = match.main_app ? this.commitSha : null; + + url = assembleURL({ + repo: match.url, + path, + filename, + lineNumber, + commitSha + }); + } + } + return url; + }, + + findGithubURL(line, shortenedLine) { + const isGem = startsWith(line, Preloaded.get("gems_dir")); + if (isGem) { + return this.GithubURLForGem(shortenedLine); + } else { + return this.GithubURLForApp(line); + } + }, + + commitSha: computed("env", function() { + let env = null; + if (Array.isArray(this.env)) { + env = this.env.map(e => e.application_version).filter(e => e)[0]; + } else if (this.env) { + env = this.env.application_version; + } + return env || Preloaded.get("application_version"); + }), + + lines: computed("backtrace", "commitSha", function() { + if (!this.backtrace || this.backtrace.length === 0) { + return []; + } + return this.backtrace.split("\n").map(line => { + const shortenedLine = shortenLine(line); + return { + line: shortenedLine, + url: this.findGithubURL(line, shortenedLine) + }; + }); + }) +}); diff --git a/client-app/app/index.html b/client-app/app/index.html index 03c476d8..90231fac 100644 --- a/client-app/app/index.html +++ b/client-app/app/index.html @@ -6,7 +6,7 @@ <title>ClientApp - + {{content-for "head"}} diff --git a/client-app/app/lib/preload.js b/client-app/app/lib/preload.js index 4431da3a..3a67d0eb 100644 --- a/client-app/app/lib/preload.js +++ b/client-app/app/lib/preload.js @@ -18,3 +18,15 @@ export default { return Em.get(CONTAINER, key); } }; + +// used in tests +export function mutatePreload(key, value) { + if (!isInitialized) { + init(); + } + Em.set(CONTAINER, key, value); +} + +export function uninitialize() { + isInitialized = false; +} diff --git a/client-app/app/styles/app.css b/client-app/app/styles/app.css index c62b4112..1c01f908 100644 --- a/client-app/app/styles/app.css +++ b/client-app/app/styles/app.css @@ -669,3 +669,8 @@ label span { .grouping-patterns button.new-pattern { margin-top: 10px; } + +.backtrace-line .line-link { + color: #9e9e9e; + margin-left: 3px; +} diff --git a/client-app/app/templates/components/back-trace.hbs b/client-app/app/templates/components/back-trace.hbs new file mode 100644 index 00000000..2222cd9b --- /dev/null +++ b/client-app/app/templates/components/back-trace.hbs @@ -0,0 +1,8 @@ +{{~#each lines as |line|~}} +
+ {{~line.line~}} + {{~#if line.url~}} + + {{~/if~}} +
+{{~/each~}} diff --git a/client-app/app/templates/components/message-info.hbs b/client-app/app/templates/components/message-info.hbs index e31cc8a3..ad9af9c3 100644 --- a/client-app/app/templates/components/message-info.hbs +++ b/client-app/app/templates/components/message-info.hbs @@ -14,7 +14,12 @@ {{#if showTitle}}

Backtrace

{{/if}} -
{{currentMessage.backtrace}}
+
+        {{~back-trace
+          class="backtrace"
+          backtrace=currentMessage.backtrace
+          env=currentMessage.env~}}
+      
{{/tab-contents}} {{#tab-contents className="env" name="env" hint="show environment" currentMessage=currentMessage }} {{#if currentMessage}} diff --git a/client-app/preload-json-manager.rb b/client-app/preload-json-manager.rb new file mode 100644 index 00000000..dd542716 --- /dev/null +++ b/client-app/preload-json-manager.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +# This script takes care of updating the content of the preloaded json in app/index.html and tests/index.html +# All you need to do is update the ruby hash of the corresponding file you want to update and run the script + +require 'bundler/inline' +require 'json' +require 'cgi' + +gemfile do + source 'https://rubygems.org' + gem 'nokogiri' +end + +tests_index_html = { + env_expandable_keys: [], + gems_dir: "/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/", + backtrace_links_enabled: true, + gems_data: [ + { + name: "activerecord", + url: "https://github.com/rails/rails/tree/v6.0.1/activerecord" + } + ], + directories: [ + { + path: "/var/www/discourse", + url: "https://github.com/discourse/discourse", + main_app: true + }, + { + path: "/var/www/discourse/plugins/discourse-prometheus", + url: "https://github.com/discourse/discourse-prometheus" + } + ], + application_version: "ce512452b512b909c38e9c63f2a0e1f8c17a2399" +} + +app_index_html = { + env_expandable_keys: [], + patterns_enabled: true, + gems_dir: "/home/sam/.rbenv/versions/2.1.2.discourse/lib/ruby/gems/2.1.0/gems/", + backtrace_links_enabled: true, + gems_data: [], + directories: [ + { + path: "/home/sam/Source/discourse", + url: "https://github.com/discourse/discourse", + main_app: true + } + ], + application_version: "b329e23f8511b7248c0e4aee370a9f8a249e1b84" +} + +types = { app: app_index_html, tests: tests_index_html } + +%i{app tests}.each do |type| + content = File.read("#{type}/index.html") + json = CGI.escapeHTML(JSON.generate(types[type])) + content.sub!(/data-preloaded=".*">$/, "data-preloaded=\"#{json}\">") + File.write("#{type}/index.html", content) +end diff --git a/client-app/tests/index.html b/client-app/tests/index.html index 6f7bdab3..9b0c371f 100644 --- a/client-app/tests/index.html +++ b/client-app/tests/index.html @@ -6,7 +6,7 @@ ClientApp Tests - + {{content-for "head"}} {{content-for "test-head"}} diff --git a/client-app/tests/integration/components/back-trace-test.js b/client-app/tests/integration/components/back-trace-test.js new file mode 100644 index 00000000..b4937a23 --- /dev/null +++ b/client-app/tests/integration/components/back-trace-test.js @@ -0,0 +1,109 @@ +import { module, test } from "qunit"; +import { setupRenderingTest } from "ember-qunit"; +import { render, find, findAll } from "@ember/test-helpers"; +import hbs from "htmlbars-inline-precompile"; +import { uninitialize, mutatePreload } from "client-app/lib/preload"; + +module("Integration | Component | back-trace", function(hooks) { + setupRenderingTest(hooks); + + hooks.beforeEach(function() { + uninitialize(); + }); + + hooks.afterEach(function() { + uninitialize(); + }); + + test("backtrace lines display and work correctly", async function(assert) { + const backtrace = `/var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activerecord-6.0.1/lib/active_record/relation/finder_methods.rb:317:in \`exists?' +/var/www/discourse/lib/permalink_constraint.rb:6:in \`matches?' +/var/www/discourse/plugins/discourse-prometheus/lib/middleware/metrics.rb:17:in \`call'`; + this.set("backtrace", backtrace); + await render(hbs`{{back-trace backtrace=backtrace}}`); + + const [gem, app, plugin] = findAll("a"); + assert.equal( + gem.href, + "https://github.com/rails/rails/tree/v6.0.1/activerecord/lib/active_record/relation/finder_methods.rb#L317" + ); + + assert.equal( + app.href, + "https://github.com/discourse/discourse/blob/ce512452b512b909c38e9c63f2a0e1f8c17a2399/lib/permalink_constraint.rb#L6" + ); + + assert.equal( + plugin.href, + "https://github.com/discourse/discourse-prometheus/blob/master/lib/middleware/metrics.rb#L17" + ); + + let gemLine = find("div.backtrace-line"); + assert.equal( + gemLine.textContent, + "activerecord-6.0.1/lib/active_record/relation/finder_methods.rb:317:in `exists?'", + "gem lines are truncated" + ); + }); + + test("non-ruby backtraces don't break things", async function(assert) { + this.set( + "backtrace", + `m/<@https://discourse-cdn.com/assets/application-f59d2.br.js:1:27448 +m@https://discourse-cdn.com/assets/application-f59d2.br.js:1:27560 +string@https://discourse-cdn.com/assets/application-f59d2.br.js:1:27869` + ); + await render(hbs`{{back-trace backtrace=backtrace}}`); + const lines = this.backtrace.split("\n"); + findAll("div.backtrace-line").forEach((node, index) => { + assert.equal(node.textContent.trim(), lines[index]); + }); + }); + + test("Github links use commit sha", async function(assert) { + const backtrace = `/var/www/discourse/lib/permalink_constraint.rb:6:in \`matches?'`; + let env = [ + { application_version: "123abc" }, + { application_version: "abc123" } + ]; + this.setProperties({ + backtrace, + env + }); + await render(hbs`{{back-trace backtrace=backtrace env=env}}`); + let href = find("a").href; + assert.equal( + href, + "https://github.com/discourse/discourse/blob/123abc/lib/permalink_constraint.rb#L6", + "uses the first application_version if there are multiple versions" + ); + + env = { application_version: "567def" }; + this.set("env", env); + await render(hbs`{{back-trace backtrace=backtrace env=env}}`); + href = find("a").href; + assert.equal( + href, + "https://github.com/discourse/discourse/blob/567def/lib/permalink_constraint.rb#L6", + "uses application_version when env is only a hash" + ); + + this.set("env", null); + await render(hbs`{{back-trace backtrace=backtrace env=env}}`); + href = find("a").href; + assert.equal( + href, + "https://github.com/discourse/discourse/blob/ce512452b512b909c38e9c63f2a0e1f8c17a2399/lib/permalink_constraint.rb#L6", + "falls back to preload if env doesn't contain application_version" + ); + + mutatePreload("application_version", null); + await render(hbs`{{back-trace backtrace=backtrace}}`); + href = find("a").href; + assert.equal( + href, + "https://github.com/discourse/discourse/blob/master/lib/permalink_constraint.rb#L6", + "falls back to master branch when neither preload nor application_version in env are available" + ); + }); +}); diff --git a/client-app/tests/integration/components/message-info-test.js b/client-app/tests/integration/components/message-info-test.js index 5ee65be6..d252b5ba 100644 --- a/client-app/tests/integration/components/message-info-test.js +++ b/client-app/tests/integration/components/message-info-test.js @@ -36,7 +36,7 @@ module("Integration | Component | message-info", function(hooks) { ); let activeTab = find(".message-info .content.active pre"); assert.equal( - activeTab.textContent, + activeTab.textContent.trim(), backtrace, "default active tab is backtrace" ); diff --git a/client-app/tests/integration/components/patterns-list-test.js b/client-app/tests/integration/components/patterns-list-test.js index b424fa98..c9caa89d 100644 --- a/client-app/tests/integration/components/patterns-list-test.js +++ b/client-app/tests/integration/components/patterns-list-test.js @@ -12,7 +12,9 @@ module("Integration | Component | patterns-list", function(hooks) { mutable: true, patterns: [] }); - await render(hbs`{{patterns-list patterns=patterns mutable=mutable}}`); + await render( + hbs`{{patterns-list patterns=patterns mutable=mutable showCounter=true}}` + ); assert .dom(".pattern-input") .exists("It shows an input when patterns are emtpy"); @@ -33,7 +35,10 @@ module("Integration | Component | patterns-list", function(hooks) { .doesNotExist("No save buttons are shown when there is 0 buffer"); const counters = findAll("input.count"); assert.equal(counters.length, 3, "counters shown for all patterns"); - assert.ok(counters.every(c => c.disabled), "counters are disabled"); + assert.ok( + counters.every(c => c.disabled), + "counters are disabled" + ); pattern1.set("count", 6); this.set("patterns", [pattern1, pattern2]); diff --git a/lib/logster/configuration.rb b/lib/logster/configuration.rb index 923a640b..5bf36239 100644 --- a/lib/logster/configuration.rb +++ b/lib/logster/configuration.rb @@ -12,7 +12,10 @@ class Configuration :environments, :rate_limit_error_reporting, :web_title, - :maximum_message_size_bytes + :maximum_message_size_bytes, + :project_directories, + :enable_backtrace_links, + :gems_dir ) attr_writer :subdirectory @@ -27,6 +30,9 @@ def initialize @rate_limit_error_reporting = true @enable_js_error_reporting = true @maximum_message_size_bytes = 60_000 + @project_directories = [] + @enable_backtrace_links = true + @gems_dir = Gem.dir + "/gems/" @allow_grouping = false diff --git a/lib/logster/middleware/viewer.rb b/lib/logster/middleware/viewer.rb index 63f16f43..010bf637 100644 --- a/lib/logster/middleware/viewer.rb +++ b/lib/logster/middleware/viewer.rb @@ -317,6 +317,21 @@ def to_json_and_escape(payload) Rack::Utils.escape_html(JSON.fast_generate(payload)) end + def preload_backtrace_data + gems_data = [] + Gem::Specification.find_all do |gem| + url = gem.metadata["source_code_uri"] || gem.homepage + if url && url.match(/^https?:\/\/github.com\//) + gems_data << { name: gem.name, url: url } + end + end + { + gems_data: gems_data, + directories: Logster.config.project_directories, + application_version: Logster.config.application_version + } + end + def body(preload) root_url = @logs_path root_url += "/" if root_url[-1] != "/" @@ -324,6 +339,14 @@ def body(preload) env_expandable_keys: Logster.config.env_expandable_keys, patterns_enabled: Logster.config.enable_custom_patterns_via_ui ) + backtrace_links_enabled = Logster.config.enable_backtrace_links + gems_dir = Logster.config.gems_dir + gems_dir += "/" if gems_dir[-1] != "/" + preload.merge!(gems_dir: gems_dir, backtrace_links_enabled: backtrace_links_enabled) + + if backtrace_links_enabled + preload.merge!(preload_backtrace_data) + end <<~HTML diff --git a/website/Gemfile.lock b/website/Gemfile.lock index 0c0290ad..a60a9ef0 100644 --- a/website/Gemfile.lock +++ b/website/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - logster (2.5.0) + logster (2.5.1) GEM remote: https://rubygems.org/ diff --git a/website/sample.rb b/website/sample.rb index de75e273..8fa2bc3d 100644 --- a/website/sample.rb +++ b/website/sample.rb @@ -73,6 +73,15 @@ def load_error SampleLoaderInstance.load_samples unless ENV['NO_DATA'] Logster.config.allow_grouping = true Logster.config.enable_custom_patterns_via_ui = ENV["LOGSTER_ENABLE_CUSTOM_PATTERNS_VIA_UI"] == "1" +Logster.config.application_version = "b329e23f8511b7248c0e4aee370a9f8a249e1b84" +Logster.config.gems_dir = "/home/sam/.rbenv/versions/2.1.2.discourse/lib/ruby/gems/2.1.0/gems/" +Logster.config.project_directories = [ + { + path: "/home/sam/Source/discourse", + url: "https://github.com/discourse/discourse", + main_app: true + } +] class Sample < Sinatra::Base use Logster::Middleware::Viewer