\x20\t\r\n\f]*)/i,pe=/^$|^module$|\/(?:java|ecma)script/i;ue=m.createDocumentFragment().appendChild(m.createElement("div")),(de=m.createElement("input")).setAttribute("type","radio"),de.setAttribute("checked","checked"),de.setAttribute("name","t"),ue.appendChild(de),h.checkClone=ue.cloneNode(!0).cloneNode(!0).lastChild.checked,ue.innerHTML="",h.noCloneChecked=!!ue.cloneNode(!0).lastChild.defaultValue,ue.innerHTML=" ",h.option=!!ue.lastChild;var ge={thead:[1,""],col:[2,""],tr:[2,""],td:[3,""],_default:[0,"",""]};function me(e,t){var n;return n=void 0!==e.getElementsByTagName?e.getElementsByTagName(t||"*"):void 0!==e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&k(e,t)?x.merge([e],n):n}function ve(e,t){for(var n=0,i=e.length;n",""]);var be=/<|?\w+;/;function ye(e,t,n,i,o){for(var r,a,s,l,c,u,d=t.createDocumentFragment(),f=[],h=0,p=e.length;h \s*$/g;function Me(e,t){return k(e,"table")&&k(11!==t.nodeType?t:t.firstChild,"tr")&&x(e).children("tbody")[0]||e}function $e(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function De(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,i,o,r,a,s;if(1===t.nodeType){if(K.hasData(e)&&(s=K.get(e).events))for(o in K.remove(t,"handle events"),s)for(n=0,i=s[o].length;n").attr(e.scriptAttrs||{}).prop({charset:e.scriptCharset,src:e.url}).on("load error",n=function(e){t.remove(),n=null,e&&o("error"===e.type?404:200,e.type)}),m.head.appendChild(t[0])},abort:function(){n&&n()}}}));var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||x.expando+"_"+Et.guid++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",(function(t,n,i){var o,r,a,s=!1!==t.jsonp&&(Vt.test(t.url)?"url":"string"==typeof t.data&&0===(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(t.data)&&"data");if(s||"jsonp"===t.dataTypes[0])return o=t.jsonpCallback=p(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,s?t[s]=t[s].replace(Vt,"$1"+o):!1!==t.jsonp&&(t.url+=(Ct.test(t.url)?"&":"?")+t.jsonp+"="+o),t.converters["script json"]=function(){return a||x.error(o+" was not called"),a[0]},t.dataTypes[0]="json",r=e[o],e[o]=function(){a=arguments},i.always((function(){void 0===r?x(e).removeProp(o):e[o]=r,t[o]&&(t.jsonpCallback=n.jsonpCallback,Xt.push(o)),a&&p(r)&&r(a[0]),a=r=void 0})),"script"})),h.createHTMLDocument=((Ut=m.implementation.createHTMLDocument("").body).innerHTML="",2===Ut.childNodes.length),x.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(h.createHTMLDocument?((i=(t=m.implementation.createHTMLDocument("")).createElement("base")).href=m.location.href,t.head.appendChild(i)):t=m),r=!n&&[],(o=N.exec(e))?[t.createElement(o[1])]:(o=ye([e],t,r),r&&r.length&&x(r).remove(),x.merge([],o.childNodes)));var i,o,r},x.fn.load=function(e,t,n){var i,o,r,a=this,s=e.indexOf(" ");return-1").append(x.parseHTML(e)).find(i):e)})).always(n&&function(e,t){a.each((function(){n.apply(this,r||[e.responseText,t,e])}))}),this},x.expr.pseudos.animated=function(e){return x.grep(x.timers,(function(t){return e===t.elem})).length},x.offset={setOffset:function(e,t,n){var i,o,r,a,s,l,c=x.css(e,"position"),u=x(e),d={};"static"===c&&(e.style.position="relative"),s=u.offset(),r=x.css(e,"top"),l=x.css(e,"left"),("absolute"===c||"fixed"===c)&&-1<(r+l).indexOf("auto")?(a=(i=u.position()).top,o=i.left):(a=parseFloat(r)||0,o=parseFloat(l)||0),p(t)&&(t=t.call(e,n,x.extend({},s))),null!=t.top&&(d.top=t.top-s.top+a),null!=t.left&&(d.left=t.left-s.left+o),"using"in t?t.using.call(e,d):("number"==typeof d.top&&(d.top+="px"),"number"==typeof d.left&&(d.left+="px"),u.css(d))}},x.fn.extend({offset:function(e){if(arguments.length)return void 0===e?this:this.each((function(t){x.offset.setOffset(this,e,t)}));var t,n,i=this[0];return i?i.getClientRects().length?(t=i.getBoundingClientRect(),n=i.ownerDocument.defaultView,{top:t.top+n.pageYOffset,left:t.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,i=this[0],o={top:0,left:0};if("fixed"===x.css(i,"position"))t=i.getBoundingClientRect();else{for(t=this.offset(),n=i.ownerDocument,e=i.offsetParent||n.documentElement;e&&(e===n.body||e===n.documentElement)&&"static"===x.css(e,"position");)e=e.parentNode;e&&e!==i&&1===e.nodeType&&((o=x(e).offset()).top+=x.css(e,"borderTopWidth",!0),o.left+=x.css(e,"borderLeftWidth",!0))}return{top:t.top-o.top-x.css(i,"marginTop",!0),left:t.left-o.left-x.css(i,"marginLeft",!0)}}},offsetParent:function(){return this.map((function(){for(var e=this.offsetParent;e&&"static"===x.css(e,"position");)e=e.offsetParent;return e||ie}))}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},(function(e,t){var n="pageYOffset"===t;x.fn[e]=function(i){return q(this,(function(e,i,o){var r;if(g(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===o)return r?r[t]:e[i];r?r.scrollTo(n?r.pageXOffset:o,n?o:r.pageYOffset):e[i]=o}),e,i,arguments.length)}})),x.each(["top","left"],(function(e,t){x.cssHooks[t]=qe(h.pixelPosition,(function(e,n){if(n)return n=He(e,t),Pe.test(n)?x(e).position()[t]+"px":n}))})),x.each({Height:"height",Width:"width"},(function(e,t){x.each({padding:"inner"+e,content:t,"":"outer"+e},(function(n,i){x.fn[i]=function(o,r){var a=arguments.length&&(n||"boolean"!=typeof o),s=n||(!0===o||!0===r?"margin":"border");return q(this,(function(t,n,o){var r;return g(t)?0===i.indexOf("outer")?t["inner"+e]:t.document.documentElement["client"+e]:9===t.nodeType?(r=t.documentElement,Math.max(t.body["scroll"+e],r["scroll"+e],t.body["offset"+e],r["offset"+e],r["client"+e])):void 0===o?x.css(t,n,s):x.style(t,n,o,s)}),t,a?o:void 0,a)}}))})),x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],(function(e,t){x.fn[t]=function(e){return this.on(t,e)}})),x.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,i){return this.on(t,e,n,i)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),x.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),(function(e,t){x.fn[t]=function(e,n){return 0{throw Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=()=>{throw Error("set is read-only")}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach((n=>{var i=e[n];"object"!=typeof i||Object.isFrozen(i)||t(i)})),e}e.exports=t,e.exports.default=t;var n=e.exports;class i{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}}function o(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function r(e,...t){const n=Object.create(null);for(const t in e)n[t]=e[t];return t.forEach((e=>{for(const t in e)n[t]=e[t]})),n}const a=e=>!!e.kind;class s{constructor(e,t){this.buffer="",this.classPrefix=t.classPrefix,e.walk(this)}addText(e){this.buffer+=o(e)}openNode(e){if(!a(e))return;let t=e.kind;t=e.sublanguage?"language-"+t:((e,{prefix:t})=>{if(e.includes(".")){const n=e.split(".");return[`${t}${n.shift()}`,...n.map(((e,t)=>`${e}${"_".repeat(t+1)}`))].join(" ")}return`${t}${e}`})(t,{prefix:this.classPrefix}),this.span(t)}closeNode(e){a(e)&&(this.buffer+="")}value(){return this.buffer}span(e){this.buffer+=``}}class l{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const t={kind:e,children:[]};this.add(t),this.stack.push(t)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,t){return"string"==typeof t?e.addText(t):t.children&&(e.openNode(t),t.children.forEach((t=>this._walk(e,t))),e.closeNode(t)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{l._collapse(e)})))}}class c extends l{constructor(e){super(),this.options=e}addKeyword(e,t){""!==e&&(this.openNode(t),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,t){const n=e.root;n.kind=t,n.sublanguage=!0,this.add(n)}toHTML(){return new s(this,this.options).value()}finalize(){return!0}}function u(e){return e?"string"==typeof e?e:e.source:null}function d(...e){return e.map((e=>u(e))).join("")}function f(...e){return"("+((e=>{const t=e[e.length-1];return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{}})(e).capture?"":"?:")+e.map((e=>u(e))).join("|")+")"}function h(e){return RegExp(e.toString()+"|").exec("").length-1}const p=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;function g(e,{joinWith:t}){let n=0;return e.map((e=>{n+=1;const t=n;let i=u(e),o="";for(;i.length>0;){const e=p.exec(i);if(!e){o+=i;break}o+=i.substring(0,e.index),i=i.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?o+="\\"+(Number(e[1])+t):(o+=e[0],"("===e[0]&&n++)}return o})).map((e=>`(${e})`)).join(t)}const m="[a-zA-Z]\\w*",v="[a-zA-Z_]\\w*",b="\\b\\d+(\\.\\d+)?",y="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",w="\\b(0b[01]+)",x={begin:"\\\\[\\s\\S]",relevance:0},_={scope:"string",begin:"'",end:"'",illegal:"\\n",contains:[x]},E={scope:"string",begin:'"',end:'"',illegal:"\\n",contains:[x]},C=(e,t,n={})=>{const i=r({scope:"comment",begin:e,end:t,contains:[]},n);i.contains.push({scope:"doctag",begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)",end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0});const o=f("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/);return i.contains.push({begin:d(/[ ]+/,"(",o,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i},T=C("//","$"),S=C("/\\*","\\*/"),k=C("#","$");var N=Object.freeze({__proto__:null,MATCH_NOTHING_RE:/\b\B/,IDENT_RE:m,UNDERSCORE_IDENT_RE:v,NUMBER_RE:b,C_NUMBER_RE:y,BINARY_NUMBER_RE:w,RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const t=/^#![ ]*\//;return e.binary&&(e.begin=d(t,/.*\b/,e.binary,/\b.*/)),r({scope:"meta",begin:t,end:/$/,relevance:0,"on:begin":(e,t)=>{0!==e.index&&t.ignoreMatch()}},e)},BACKSLASH_ESCAPE:x,APOS_STRING_MODE:_,QUOTE_STRING_MODE:E,PHRASAL_WORDS_MODE:{begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},COMMENT:C,C_LINE_COMMENT_MODE:T,C_BLOCK_COMMENT_MODE:S,HASH_COMMENT_MODE:k,NUMBER_MODE:{scope:"number",begin:b,relevance:0},C_NUMBER_MODE:{scope:"number",begin:y,relevance:0},BINARY_NUMBER_MODE:{scope:"number",begin:w,relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{scope:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[x,{begin:/\[/,end:/\]/,relevance:0,contains:[x]}]}]},TITLE_MODE:{scope:"title",begin:m,relevance:0},UNDERSCORE_TITLE_MODE:{scope:"title",begin:v,relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:e=>Object.assign(e,{"on:begin":(e,t)=>{t.data._beginMatch=e[1]},"on:end":(e,t)=>{t.data._beginMatch!==e[1]&&t.ignoreMatch()}})});function A(e,t){"."===e.input[e.index-1]&&t.ignoreMatch()}function O(e,t){void 0!==e.className&&(e.scope=e.className,delete e.className)}function M(e,t){t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",e.__beforeBegin=A,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,void 0===e.relevance&&(e.relevance=0))}function $(e,t){Array.isArray(e.illegal)&&(e.illegal=f(...e.illegal))}function D(e,t){if(e.match){if(e.begin||e.end)throw Error("begin & end are not supported with match");e.begin=e.match,delete e.match}}function L(e,t){void 0===e.relevance&&(e.relevance=1)}const j=(e,t)=>{if(!e.beforeMatch)return;if(e.starts)throw Error("beforeMatch cannot be used with starts");const n=Object.assign({},e);Object.keys(e).forEach((t=>{delete e[t]})),e.keywords=n.keywords,e.begin=d(n.beforeMatch,d("(?=",n.begin,")")),e.starts={relevance:0,contains:[Object.assign(n,{endsParent:!0})]},e.relevance=0,delete n.beforeMatch},I=["of","and","for","in","not","or","if","then","parent","list","value"];function P(e,t,n="keyword"){const i=Object.create(null);return"string"==typeof e?o(n,e.split(" ")):Array.isArray(e)?o(n,e):Object.keys(e).forEach((n=>{Object.assign(i,P(e[n],t,n))})),i;function o(e,n){t&&(n=n.map((e=>e.toLowerCase()))),n.forEach((t=>{const n=t.split("|");i[n[0]]=[e,R(n[0],n[1])]}))}}function R(e,t){return t?Number(t):(e=>I.includes(e.toLowerCase()))(e)?0:1}const F={},B=e=>{console.error(e)},H=(e,...t)=>{console.log("WARN: "+e,...t)},q=(e,t)=>{F[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),F[`${e}/${t}`]=!0)},z=Error();function W(e,t,{key:n}){let i=0;const o=e[n],r={},a={};for(let e=1;e<=t.length;e++)a[e+i]=o[e],r[e+i]=!0,i+=h(t[e-1]);e[n]=a,e[n]._emit=r,e[n]._multi=!0}function U(e){(e=>{e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope,delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={_wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope}),(e=>{if(Array.isArray(e.begin)){if(e.skip||e.excludeBegin||e.returnBegin)throw B("skip, excludeBegin, returnBegin not compatible with beginScope: {}"),z;if("object"!=typeof e.beginScope||null===e.beginScope)throw B("beginScope must be object"),z;W(e,e.begin,{key:"beginScope"}),e.begin=g(e.begin,{joinWith:""})}})(e),(e=>{if(Array.isArray(e.end)){if(e.skip||e.excludeEnd||e.returnEnd)throw B("skip, excludeEnd, returnEnd not compatible with endScope: {}"),z;if("object"!=typeof e.endScope||null===e.endScope)throw B("endScope must be object"),z;W(e,e.end,{key:"endScope"}),e.end=g(e.end,{joinWith:""})}})(e)}function X(e){function t(t,n){return RegExp(u(t),"m"+(e.case_insensitive?"i":"")+(n?"g":""))}class n{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,t){t.position=this.position++,this.matchIndexes[this.matchAt]=t,this.regexes.push([t,e]),this.matchAt+=h(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map((e=>e[1]));this.matcherRe=t(g(e,{joinWith:"|"}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const t=this.matcherRe.exec(e);if(!t)return null;const n=t.findIndex(((e,t)=>t>0&&void 0!==e)),i=this.matchIndexes[n];return t.splice(0,n),Object.assign(t,i)}}class i{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const t=new n;return this.rules.slice(e).forEach((([e,n])=>t.addRule(e,n))),t.compile(),this.multiRegexes[e]=t,t}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,t){this.rules.push([e,t]),"begin"===t.type&&this.count++}exec(e){const t=this.getMatcher(this.regexIndex);t.lastIndex=this.lastIndex;let n=t.exec(e);if(this.resumingScanAtSamePosition())if(n&&n.index===this.lastIndex);else{const t=this.getMatcher(0);t.lastIndex=this.lastIndex+1,n=t.exec(e)}return n&&(this.regexIndex+=n.position+1,this.regexIndex===this.count&&this.considerAll()),n}}if(e.compilerExtensions||(e.compilerExtensions=[]),e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return e.classNameAliases=r(e.classNameAliases||{}),function n(o,a){const s=o;if(o.isCompiled)return s;[O,D,U,j].forEach((e=>e(o,a))),e.compilerExtensions.forEach((e=>e(o,a))),o.__beforeBegin=null,[M,$,L].forEach((e=>e(o,a))),o.isCompiled=!0;let l=null;return"object"==typeof o.keywords&&o.keywords.$pattern&&(o.keywords=Object.assign({},o.keywords),l=o.keywords.$pattern,delete o.keywords.$pattern),l=l||/\w+/,o.keywords&&(o.keywords=P(o.keywords,e.case_insensitive)),s.keywordPatternRe=t(l,!0),a&&(o.begin||(o.begin=/\B|\b/),s.beginRe=t(o.begin),o.end||o.endsWithParent||(o.end=/\B|\b/),o.end&&(s.endRe=t(o.end)),s.terminatorEnd=u(o.end)||"",o.endsWithParent&&a.terminatorEnd&&(s.terminatorEnd+=(o.end?"|":"")+a.terminatorEnd)),o.illegal&&(s.illegalRe=t(o.illegal)),o.contains||(o.contains=[]),o.contains=[].concat(...o.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((t=>r(e,{variants:null},t)))),e.cachedVariants?e.cachedVariants:V(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e))("self"===e?o:e)))),o.contains.forEach((e=>{n(e,s)})),o.starts&&n(o.starts,a),s.matcher=(e=>{const t=new i;return e.contains.forEach((e=>t.addRule(e.begin,{rule:e,type:"begin"}))),e.terminatorEnd&&t.addRule(e.terminatorEnd,{type:"end"}),e.illegal&&t.addRule(e.illegal,{type:"illegal"}),t})(s),s}(e)}function V(e){return!!e&&(e.endsWithParent||V(e.starts))}const Y=o,K=r,Q=Symbol("nomatch");var G=(e=>{const t=Object.create(null),o=Object.create(null),r=[];let a=!0;const s="Could not find the language '{}', did you forget to load/include a language module?",l={disableAutodetect:!0,name:"Plain text",contains:[]};let u={ignoreUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",cssSelector:"pre code",languages:null,__emitter:c};function d(e){return u.noHighlightRe.test(e)}function f(e,t,n,i){let o="",r="";"object"==typeof t?(o=e,n=t.ignoreIllegals,r=t.language,i=void 0):(q("10.7.0","highlight(lang, code, ...args) has been deprecated."),q("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"),r=e,o=t),void 0===n&&(n=!0);const a={code:o,language:r};x("before:highlight",a);const s=a.result?a.result:h(a.language,a.code,n,i);return s.code=a.code,x("after:highlight",s),s}function h(e,n,o,r){const l=Object.create(null);function c(){if(!C.keywords)return void S.addText(k);let e=0;C.keywordPatternRe.lastIndex=0;let t=C.keywordPatternRe.exec(k),n="";for(;t;){n+=k.substring(e,t.index);const o=x.case_insensitive?t[0].toLowerCase():t[0],r=(i=o,C.keywords[i]);if(r){const[e,i]=r;if(S.addText(n),n="",l[o]=(l[o]||0)+1,l[o]<=7&&(N+=i),e.startsWith("_"))n+=t[0];else{const n=x.classNameAliases[e]||e;S.addKeyword(t[0],n)}}else n+=t[0];e=C.keywordPatternRe.lastIndex,t=C.keywordPatternRe.exec(k)}var i;n+=k.substr(e),S.addText(n)}function d(){null!=C.subLanguage?(()=>{if(""===k)return;let e=null;if("string"==typeof C.subLanguage){if(!t[C.subLanguage])return void S.addText(k);e=h(C.subLanguage,k,!0,T[C.subLanguage]),T[C.subLanguage]=e._top}else e=p(k,C.subLanguage.length?C.subLanguage:null);C.relevance>0&&(N+=e.relevance),S.addSublanguage(e._emitter,e.language)})():c(),k=""}function f(e,t){let n=1;for(;void 0!==t[n];){if(!e._emit[n]){n++;continue}const i=x.classNameAliases[e[n]]||e[n],o=t[n];i?S.addKeyword(o,i):(k=o,c(),k=""),n++}}function g(e,t){return e.scope&&"string"==typeof e.scope&&S.openNode(x.classNameAliases[e.scope]||e.scope),e.beginScope&&(e.beginScope._wrap?(S.addKeyword(k,x.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap),k=""):e.beginScope._multi&&(f(e.beginScope,t),k="")),C=Object.create(e,{parent:{value:C}}),C}function m(e,t,n){let o=((e,t)=>{const n=e&&e.exec(t);return n&&0===n.index})(e.endRe,n);if(o){if(e["on:end"]){const n=new i(e);e["on:end"](t,n),n.isMatchIgnored&&(o=!1)}if(o){for(;e.endsParent&&e.parent;)e=e.parent;return e}}if(e.endsWithParent)return m(e.parent,t,n)}function v(e){return 0===C.matcher.regexIndex?(k+=e[0],1):(M=!0,0)}let y={};function w(t,r){const s=r&&r[0];if(k+=t,null==s)return d(),0;if("begin"===y.type&&"end"===r.type&&y.index===r.index&&""===s){if(k+=n.slice(r.index,r.index+1),!a){const t=Error(`0 width match regex (${e})`);throw t.languageName=e,t.badRule=y.rule,t}return 1}if(y=r,"begin"===r.type)return(e=>{const t=e[0],n=e.rule,o=new i(n),r=[n.__beforeBegin,n["on:begin"]];for(const n of r)if(n&&(n(e,o),o.isMatchIgnored))return v(t);return n.skip?k+=t:(n.excludeBegin&&(k+=t),d(),n.returnBegin||n.excludeBegin||(k=t)),g(n,e),n.returnBegin?0:t.length})(r);if("illegal"===r.type&&!o){const e=Error('Illegal lexeme "'+s+'" for mode "'+(C.scope||"")+'"');throw e.mode=C,e}if("end"===r.type){const e=function(e){const t=e[0],i=n.substr(e.index),o=m(C,e,i);if(!o)return Q;const r=C;C.endScope&&C.endScope._wrap?(d(),S.addKeyword(t,C.endScope._wrap)):C.endScope&&C.endScope._multi?(d(),f(C.endScope,e)):r.skip?k+=t:(r.returnEnd||r.excludeEnd||(k+=t),d(),r.excludeEnd&&(k=t));do{C.scope&&!C.isMultiClass&&S.closeNode(),C.skip||C.subLanguage||(N+=C.relevance),C=C.parent}while(C!==o.parent);return o.starts&&g(o.starts,e),r.returnEnd?0:t.length}(r);if(e!==Q)return e}if("illegal"===r.type&&""===s)return 1;if(O>1e5&&O>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return k+=s,s.length}const x=b(e);if(!x)throw B(s.replace("{}",e)),Error('Unknown language: "'+e+'"');const _=X(x);let E="",C=r||_;const T={},S=new u.__emitter(u);(()=>{const e=[];for(let t=C;t!==x;t=t.parent)t.scope&&e.unshift(t.scope);e.forEach((e=>S.openNode(e)))})();let k="",N=0,A=0,O=0,M=!1;try{for(C.matcher.considerAll();;){O++,M?M=!1:C.matcher.considerAll(),C.matcher.lastIndex=A;const e=C.matcher.exec(n);if(!e)break;const t=w(n.substring(A,e.index),e);A=e.index+t}return w(n.substr(A)),S.closeAllNodes(),S.finalize(),E=S.toHTML(),{language:e,value:E,relevance:N,illegal:!1,_emitter:S,_top:C}}catch(t){if(t.message&&t.message.includes("Illegal"))return{language:e,value:Y(n),illegal:!0,relevance:0,_illegalBy:{message:t.message,index:A,context:n.slice(A-100,A+100),mode:t.mode,resultSoFar:E},_emitter:S};if(a)return{language:e,value:Y(n),illegal:!1,relevance:0,errorRaised:t,_emitter:S,_top:C};throw t}}function p(e,n){n=n||u.languages||Object.keys(t);const i=(e=>{const t={value:Y(e),illegal:!1,relevance:0,_top:l,_emitter:new u.__emitter(u)};return t._emitter.addText(e),t})(e),o=n.filter(b).filter(w).map((t=>h(t,e,!1)));o.unshift(i);const r=o.sort(((e,t)=>{if(e.relevance!==t.relevance)return t.relevance-e.relevance;if(e.language&&t.language){if(b(e.language).supersetOf===t.language)return 1;if(b(t.language).supersetOf===e.language)return-1}return 0})),[a,s]=r,c=a;return c.secondBest=s,c}function g(e){let t=null;const n=(e=>{let t=e.className+" ";t+=e.parentNode?e.parentNode.className:"";const n=u.languageDetectRe.exec(t);if(n){const t=b(n[1]);return t||(H(s.replace("{}",n[1])),H("Falling back to no-highlight mode for this block.",e)),t?n[1]:"no-highlight"}return t.split(/\s+/).find((e=>d(e)||b(e)))})(e);if(d(n))return;x("before:highlightElement",{el:e,language:n}),!u.ignoreUnescapedHTML&&e.children.length>0&&(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),console.warn("https://github.com/highlightjs/highlight.js/issues/2886"),console.warn(e)),t=e;const i=t.textContent,r=n?f(i,{language:n,ignoreIllegals:!0}):p(i);e.innerHTML=r.value,((e,t,n)=>{const i=t&&o[t]||n;e.classList.add("hljs"),e.classList.add("language-"+i)})(e,n,r.language),e.result={language:r.language,re:r.relevance,relevance:r.relevance},r.secondBest&&(e.secondBest={language:r.secondBest.language,relevance:r.secondBest.relevance}),x("after:highlightElement",{el:e,result:r,text:i})}let m=!1;function v(){"loading"!==document.readyState?document.querySelectorAll(u.cssSelector).forEach(g):m=!0}function b(e){return e=(e||"").toLowerCase(),t[e]||t[o[e]]}function y(e,{languageName:t}){"string"==typeof e&&(e=[e]),e.forEach((e=>{o[e.toLowerCase()]=t}))}function w(e){const t=b(e);return t&&!t.disableAutodetect}function x(e,t){const n=e;r.forEach((e=>{e[n]&&e[n](t)}))}"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{m&&v()}),!1),Object.assign(e,{highlight:f,highlightAuto:p,highlightAll:v,highlightElement:g,highlightBlock:e=>(q("10.7.0","highlightBlock will be removed entirely in v12.0"),q("10.7.0","Please use highlightElement now."),g(e)),configure:e=>{u=K(u,e)},initHighlighting:()=>{v(),q("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")},initHighlightingOnLoad:()=>{v(),q("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.")},registerLanguage:(n,i)=>{let o=null;try{o=i(e)}catch(e){if(B("Language definition for '{}' could not be registered.".replace("{}",n)),!a)throw e;B(e),o=l}o.name||(o.name=n),t[n]=o,o.rawDefinition=i.bind(null,e),o.aliases&&y(o.aliases,{languageName:n})},unregisterLanguage:e=>{delete t[e];for(const t of Object.keys(o))o[t]===e&&delete o[t]},listLanguages:()=>Object.keys(t),getLanguage:b,registerAliases:y,autoDetection:w,inherit:K,addPlugin:e=>{(e=>{e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=t=>{e["before:highlightBlock"](Object.assign({block:t.el},t))}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=t=>{e["after:highlightBlock"](Object.assign({block:t.el},t))})})(e),r.push(e)}}),e.debugMode=()=>{a=!1},e.safeMode=()=>{a=!0},e.versionString="11.0.1";for(const e in N)"object"==typeof N[e]&&n(N[e]);return Object.assign(e,N),e})({}),Z=Object.freeze({__proto__:null});const J=G;for(const e of Object.keys(Z)){const t=e.replace("grmr_","");J.registerLanguage(t,Z[e])}return J}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs),hljs.registerLanguage("csharp",(()=>{"use strict";return e=>{const t={keyword:["abstract","as","base","break","case","class","const","continue","do","else","event","explicit","extern","finally","fixed","for","foreach","goto","if","implicit","in","interface","internal","is","lock","namespace","new","operator","out","override","params","private","protected","public","readonly","record","ref","return","sealed","sizeof","stackalloc","static","struct","switch","this","throw","try","typeof","unchecked","unsafe","using","virtual","void","volatile","while"].concat(["add","alias","and","ascending","async","await","by","descending","equals","from","get","global","group","init","into","join","let","nameof","not","notnull","on","or","orderby","partial","remove","select","set","unmanaged","value|0","var","when","where","with","yield"]),built_in:["bool","byte","char","decimal","delegate","double","dynamic","enum","float","int","long","nint","nuint","object","sbyte","short","string","ulong","uint","ushort"],literal:["default","false","null","true"]},n=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},o={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},r=e.inherit(o,{illegal:/\n/}),a={className:"subst",begin:/\{/,end:/\}/,keywords:t},s=e.inherit(a,{illegal:/\n/}),l={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/},e.BACKSLASH_ESCAPE,s]},c={className:"string",begin:/\$@"/,end:'"',contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:'""'},a]},u=e.inherit(c,{illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:'""'},s]});a.contains=[c,l,o,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,i,e.C_BLOCK_COMMENT_MODE],s.contains=[u,l,r,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,i,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];const d={variants:[c,l,o,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},f={begin:"<",end:">",contains:[{beginKeywords:"in out"},n]},h=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",p={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:t,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:"?",end:">"}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{keyword:"if else elif endif define undef warning error line region endregion pragma checksum"}},d,i,{beginKeywords:"class interface",relevance:0,end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},n,f,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",relevance:0,end:/[{;=]/,illegal:/[^\s:]/,contains:[n,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"record",relevance:0,end:/[{;=]/,illegal:/[^\s:]/,contains:[n,f,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[(?=[\\w])",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+h+"\\s+)+"+e.IDENT_RE+"\\s*(<.+>\\s*)?\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:t,contains:[{beginKeywords:"public private protected static internal protected abstract async extern override unsafe virtual new sealed partial",relevance:0},{begin:e.IDENT_RE+"\\s*(<.+>\\s*)?\\(",returnBegin:!0,contains:[e.TITLE_MODE,f],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,relevance:0,contains:[d,i,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},p]}}})()),hljs.registerLanguage("bash",(()=>{"use strict";function e(...e){return e.map((e=>{return(t=e)?"string"==typeof t?t:t.source:null;var t})).join("")}return t=>{const n={},i={begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[n]}]};Object.assign(n,{className:"variable",variants:[{begin:e(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},i]});const o={className:"subst",begin:/\$\(/,end:/\)/,contains:[t.BACKSLASH_ESCAPE]},r={begin:/<<-?\s*(?=\w+)/,starts:{contains:[t.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,className:"string"})]}},a={className:"string",begin:/"/,end:/"/,contains:[t.BACKSLASH_ESCAPE,n,o]};o.contains.push(a);const s={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},t.NUMBER_MODE,n]},l=t.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[t.inherit(t.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z._-]+\b/,keyword:["if","then","else","elif","fi","for","while","in","do","done","case","esac","function"],literal:["true","false"],built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp"},contains:[l,t.SHEBANG(),c,s,t.HASH_COMMENT_MODE,r,a,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},n]}}})()),hljs.registerLanguage("dos",(()=>{"use strict";return e=>{const t=e.COMMENT(/^\s*@?rem\b/,/$/,{relevance:10});return{name:"Batch file (DOS)",aliases:["bat","cmd"],case_insensitive:!0,illegal:/\/\*/,keywords:{keyword:["if","else","goto","for","in","do","call","exit","not","exist","errorlevel","defined","equ","neq","lss","leq","gtr","geq"],built_in:["prn","nul","lpt3","lpt2","lpt1","con","com4","com3","com2","com1","aux","shift","cd","dir","echo","setlocal","endlocal","set","pause","copy","append","assoc","at","attrib","break","cacls","cd","chcp","chdir","chkdsk","chkntfs","cls","cmd","color","comp","compact","convert","date","dir","diskcomp","diskcopy","doskey","erase","fs","find","findstr","format","ftype","graftabl","help","keyb","label","md","mkdir","mode","more","move","path","pause","print","popd","pushd","promt","rd","recover","rem","rename","replace","restore","rmdir","shift","sort","start","subst","time","title","tree","type","ver","verify","vol","ping","net","ipconfig","taskkill","xcopy","ren","del"]},contains:[{className:"variable",begin:/%%[^ ]|%[^ ]+?%|![^ ]+?!/},{className:"function",begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)",end:"goto:eof",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),t]},{className:"number",begin:"\\b\\d+",relevance:0},t]}}})()),hljs.registerLanguage("fsharp",(()=>{"use strict";return e=>{const t={begin:"<",end:">",contains:[e.inherit(e.TITLE_MODE,{begin:/'[a-zA-Z0-9_]+/})]};return{name:"F#",aliases:["fs"],keywords:["abstract","and","as","assert","base","begin","class","default","delegate","do","done","downcast","downto","elif","else","end","exception","extern","false","finally","for","fun","function","global","if","in","inherit","inline","interface","internal","lazy","let","match","member","module","mutable","namespace","new","null","of","open","or","override","private","public","rec","return","sig","static","struct","then","to","true","try","type","upcast","use","val","void","when","while","with","yield"],illegal:/\/\*/,contains:[{className:"keyword",begin:/\b(yield|return|let|do)!/},{className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:'"""',end:'"""'},e.COMMENT("\\(\\*(\\s)","\\*\\)",{contains:["self"]}),{className:"class",beginKeywords:"type",end:"\\(|=|$",excludeEnd:!0,contains:[e.UNDERSCORE_TITLE_MODE,t]},{className:"meta",begin:"\\[<",end:">\\]",relevance:10},{className:"symbol",begin:"\\B('[A-Za-z])\\b",contains:[e.BACKSLASH_ESCAPE]},e.C_LINE_COMMENT_MODE,e.inherit(e.QUOTE_STRING_MODE,{illegal:null}),e.C_NUMBER_MODE]}}})()),hljs.registerLanguage("xml",(()=>{"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function t(e){return n("(?=",e,")")}function n(...t){return t.map((t=>e(t))).join("")}function i(...t){return"("+((e=>{const t=e[e.length-1];return"object"==typeof t&&t.constructor===Object?(e.splice(e.length-1,1),t):{}})(t).capture?"":"?:")+t.map((t=>e(t))).join("|")+")"}return e=>{const o=n(/[A-Z_]/,n("(?:",/[A-Z0-9_.-]*:/,")?"),/[A-Z0-9_.-]*/),r={className:"symbol",begin:/&[a-z]+;|[0-9]+;|[a-f0-9]+;/},a={begin:/\s/,contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},s=e.inherit(a,{begin:/\(/,end:/\)/}),l=e.inherit(e.APOS_STRING_MODE,{className:"string"}),c=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),u={endsWithParent:!0,illegal:/,relevance:0,contains:[{className:"attr",begin:/[A-Za-z0-9._:-]+/,relevance:0},{begin:/=\s*/,relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/,end:/"/,contains:[r]},{begin:/'/,end:/'/,contains:[r]},{begin:/[^\s"'=<>`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[a,c,l,s,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[a,s,c,l]}]}]},e.COMMENT(//,{relevance:10}),{begin://,relevance:10},r,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/"),n("body").addClass("compensate-for-scrollbar")),o="",n.each(a.buttons,(function(e,t){o+=a.btnTpl[t]||""})),i=n(r.translate(r,a.baseTpl.replace("{{buttons}}",o).replace("{{arrows}}",a.btnTpl.arrowLeft+a.btnTpl.arrowRight))).attr("id","fancybox-container-"+r.id).addClass(a.baseClass).data("FancyBox",r).appendTo(a.parentEl),r.$refs={container:i},["bg","inner","infobar","toolbar","stage","caption","navigation"].forEach((function(e){r.$refs[e]=i.find(".fancybox-"+e)})),r.trigger("onInit"),r.activate(),r.jumpTo(r.currIndex)},translate:function(e,t){var n=e.opts.i18n[e.opts.lang]||e.opts.i18n.en;return t.replace(/\{\{(\w+)\}\}/g,(function(e,t){return void 0===n[t]?e:n[t]}))},addContent:function(e){var t,i=this,o=n.makeArray(e);n.each(o,(function(e,t){var o,r,a,s,l,c={},u={};n.isPlainObject(t)?(c=t,u=t.opts||t):"object"===n.type(t)&&n(t).length?(u=(o=n(t)).data()||{},(u=n.extend(!0,{},u,u.options)).$orig=o,c.src=i.opts.src||u.src||o.attr("href"),c.type||c.src||(c.type="inline",c.src=t)):c={type:"html",src:t+""},c.opts=n.extend(!0,{},i.opts,u),n.isArray(u.buttons)&&(c.opts.buttons=u.buttons),n.fancybox.isMobile&&c.opts.mobile&&(c.opts=h(c.opts,c.opts.mobile)),r=c.type||c.opts.type,s=c.src||"",!r&&s&&((a=s.match(/\.(mp4|mov|ogv|webm)((\?|#).*)?$/i))?(r="video",c.opts.video.format||(c.opts.video.format="video/"+("ogv"===a[1]?"ogg":a[1]))):s.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i)?r="image":s.match(/\.(pdf)((\?|#).*)?$/i)?(r="iframe",c=n.extend(!0,c,{contentType:"pdf",opts:{iframe:{preload:!1}}})):"#"===s.charAt(0)&&(r="inline")),r?c.type=r:i.trigger("objectNeedsType",c),c.contentType||(c.contentType=n.inArray(c.type,["html","inline","ajax"])>-1?"html":c.type),c.index=i.group.length,"auto"==c.opts.smallBtn&&(c.opts.smallBtn=n.inArray(c.type,["html","inline","ajax"])>-1),"auto"===c.opts.toolbar&&(c.opts.toolbar=!c.opts.smallBtn),c.$thumb=c.opts.$thumb||null,c.opts.$trigger&&c.index===i.opts.index&&(c.$thumb=c.opts.$trigger.find("img:first"),c.$thumb.length&&(c.opts.$orig=c.opts.$trigger)),c.$thumb&&c.$thumb.length||!c.opts.$orig||(c.$thumb=c.opts.$orig.find("img:first")),c.$thumb&&!c.$thumb.length&&(c.$thumb=null),c.thumb=c.opts.thumb||(c.$thumb?c.$thumb[0].src:null),"function"===n.type(c.opts.caption)&&(c.opts.caption=c.opts.caption.apply(t,[i,c])),"function"===n.type(i.opts.caption)&&(c.opts.caption=i.opts.caption.apply(t,[i,c])),c.opts.caption instanceof n||(c.opts.caption=void 0===c.opts.caption?"":c.opts.caption+""),"ajax"===c.type&&((l=s.split(/\s+/,2)).length>1&&(c.src=l.shift(),c.opts.filter=l.shift())),c.opts.modal&&(c.opts=n.extend(!0,c.opts,{trapFocus:!0,infobar:0,toolbar:0,smallBtn:0,keyboard:0,slideShow:0,fullScreen:0,thumbs:0,touch:0,clickContent:!1,clickSlide:!1,clickOutside:!1,dblclickContent:!1,dblclickSlide:!1,dblclickOutside:!1})),i.group.push(c)})),Object.keys(i.slides).length&&(i.updateControls(),(t=i.Thumbs)&&t.isActive&&(t.create(),t.focus()))},addEvents:function(){var t=this;t.removeEvents(),t.$refs.container.on("click.fb-close","[data-fancybox-close]",(function(e){e.stopPropagation(),e.preventDefault(),t.close(e)})).on("touchstart.fb-prev click.fb-prev","[data-fancybox-prev]",(function(e){e.stopPropagation(),e.preventDefault(),t.previous()})).on("touchstart.fb-next click.fb-next","[data-fancybox-next]",(function(e){e.stopPropagation(),e.preventDefault(),t.next()})).on("click.fb","[data-fancybox-zoom]",(function(e){t[t.isScaledDown()?"scaleToActual":"scaleToFit"]()})),a.on("orientationchange.fb resize.fb",(function(e){e&&e.originalEvent&&"resize"===e.originalEvent.type?(t.requestId&&u(t.requestId),t.requestId=c((function(){t.update(e)}))):(t.current&&"iframe"===t.current.type&&t.$refs.stage.hide(),setTimeout((function(){t.$refs.stage.show(),t.update(e)}),n.fancybox.isMobile?600:250))})),s.on("keydown.fb",(function(e){var i=(n.fancybox?n.fancybox.getInstance():null).current,o=e.keyCode||e.which;if(9!=o)return!i.opts.keyboard||e.ctrlKey||e.altKey||e.shiftKey||n(e.target).is("input,textarea,video,audio,select")?void 0:8===o||27===o?(e.preventDefault(),void t.close(e)):37===o||38===o?(e.preventDefault(),void t.previous()):39===o||40===o?(e.preventDefault(),void t.next()):void t.trigger("afterKeydown",e,o);i.opts.trapFocus&&t.focus(e)})),t.group[t.currIndex].opts.idleTime&&(t.idleSecondsCounter=0,s.on("mousemove.fb-idle mouseleave.fb-idle mousedown.fb-idle touchstart.fb-idle touchmove.fb-idle scroll.fb-idle keydown.fb-idle",(function(e){t.idleSecondsCounter=0,t.isIdle&&t.showControls(),t.isIdle=!1})),t.idleInterval=e.setInterval((function(){++t.idleSecondsCounter>=t.group[t.currIndex].opts.idleTime&&!t.isDragging&&(t.isIdle=!0,t.idleSecondsCounter=0,t.hideControls())}),1e3))},removeEvents:function(){var t=this;a.off("orientationchange.fb resize.fb"),s.off("keydown.fb .fb-idle"),this.$refs.container.off(".fb-close .fb-prev .fb-next"),t.idleInterval&&(e.clearInterval(t.idleInterval),t.idleInterval=null)},previous:function(e){return this.jumpTo(this.currPos-1,e)},next:function(e){return this.jumpTo(this.currPos+1,e)},jumpTo:function(e,t){var i,o,r,a,s,l,c,u,d,h=this,p=h.group.length;if(!(h.isDragging||h.isClosing||h.isAnimating&&h.firstRun)){if(e=parseInt(e,10),!(r=h.current?h.current.opts.loop:h.opts.loop)&&(e<0||e>=p))return!1;if(i=h.firstRun=!Object.keys(h.slides).length,s=h.current,h.prevIndex=h.currIndex,h.prevPos=h.currPos,a=h.createSlide(e),p>1&&((r||a.index0)&&h.createSlide(e-1)),h.current=a,h.currIndex=a.index,h.currPos=a.pos,h.trigger("beforeShow",i),h.updateControls(),a.forcedDuration=void 0,n.isNumeric(t)?a.forcedDuration=t:t=a.opts[i?"animationDuration":"transitionDuration"],t=parseInt(t,10),o=h.isMoved(a),a.$slide.addClass("fancybox-slide--current"),i)return a.opts.animationEffect&&t&&h.$refs.container.css("transition-duration",t+"ms"),h.$refs.container.addClass("fancybox-is-open").trigger("focus"),h.loadSlide(a),void h.preload("image");l=n.fancybox.getTranslate(s.$slide),c=n.fancybox.getTranslate(h.$refs.stage),n.each(h.slides,(function(e,t){n.fancybox.stop(t.$slide,!0)})),s.pos!==a.pos&&(s.isComplete=!1),s.$slide.removeClass("fancybox-slide--complete fancybox-slide--current"),o?(d=l.left-(s.pos*l.width+s.pos*s.opts.gutter),n.each(h.slides,(function(e,i){i.$slide.removeClass("fancybox-animated").removeClass((function(e,t){return(t.match(/(^|\s)fancybox-fx-\S+/g)||[]).join(" ")}));var o=i.pos*l.width+i.pos*i.opts.gutter;n.fancybox.setTranslate(i.$slide,{top:0,left:o-c.left+d}),i.pos!==a.pos&&i.$slide.addClass("fancybox-slide--"+(i.pos>a.pos?"next":"previous")),f(i.$slide),n.fancybox.animate(i.$slide,{top:0,left:(i.pos-a.pos)*l.width+(i.pos-a.pos)*i.opts.gutter},t,(function(){i.$slide.css({transform:"",opacity:""}).removeClass("fancybox-slide--next fancybox-slide--previous"),i.pos===h.currPos&&h.complete()}))}))):t&&a.opts.transitionEffect&&(u="fancybox-animated fancybox-fx-"+a.opts.transitionEffect,s.$slide.addClass("fancybox-slide--"+(s.pos>a.pos?"next":"previous")),n.fancybox.animate(s.$slide,u,t,(function(){s.$slide.removeClass(u).removeClass("fancybox-slide--next fancybox-slide--previous")}),!1)),a.isLoaded?h.revealContent(a):h.loadSlide(a),h.preload("image")}},createSlide:function(e){var t,i,o=this;return i=(i=e%o.group.length)<0?o.group.length+i:i,!o.slides[e]&&o.group[i]&&(t=n('
').appendTo(o.$refs.stage),o.slides[e]=n.extend(!0,{},o.group[i],{pos:e,$slide:t,isLoaded:!1}),o.updateSlide(o.slides[e])),o.slides[e]},scaleToActual:function(e,t,i){var o,r,a,s,l,c=this,u=c.current,d=u.$content,f=n.fancybox.getTranslate(u.$slide).width,h=n.fancybox.getTranslate(u.$slide).height,p=u.width,g=u.height;c.isAnimating||c.isMoved()||!d||"image"!=u.type||!u.isLoaded||u.hasError||(c.isAnimating=!0,n.fancybox.stop(d),e=void 0===e?.5*f:e,t=void 0===t?.5*h:t,(o=n.fancybox.getTranslate(d)).top-=n.fancybox.getTranslate(u.$slide).top,o.left-=n.fancybox.getTranslate(u.$slide).left,s=p/o.width,l=g/o.height,r=.5*f-.5*p,a=.5*h-.5*g,p>f&&((r=o.left*s-(e*s-e))>0&&(r=0),rh&&((a=o.top*l-(t*l-t))>0&&(a=0),at-.5&&(l=t),(c*=o)>i-.5&&(c=i),"image"===e.type?(u.top=Math.floor(.5*(i-c))+parseFloat(s.css("paddingTop")),u.left=Math.floor(.5*(t-l))+parseFloat(s.css("paddingLeft"))):"video"===e.contentType&&(c>l/(r=e.opts.width&&e.opts.height?l/c:e.opts.ratio||16/9)?c=l/r:l>c*r&&(l=c*r)),u.width=l,u.height=c,u)},update:function(e){var t=this;n.each(t.slides,(function(n,i){t.updateSlide(i,e)}))},updateSlide:function(e,t){var i=this,o=e&&e.$content,r=e.width||e.opts.width,a=e.height||e.opts.height,s=e.$slide;i.adjustCaption(e),o&&(r||a||"video"===e.contentType)&&!e.hasError&&(n.fancybox.stop(o),n.fancybox.setTranslate(o,i.getFitPos(e)),e.pos===i.currPos&&(i.isAnimating=!1,i.updateCursor())),i.adjustLayout(e),s.length&&(s.trigger("refresh"),e.pos===i.currPos&&i.$refs.toolbar.add(i.$refs.navigation.find(".fancybox-button--arrow_right")).toggleClass("compensate-for-scrollbar",s.get(0).scrollHeight>s.get(0).clientHeight)),i.trigger("onUpdate",e,t)},centerSlide:function(e){var t=this,i=t.current,o=i.$slide;!t.isClosing&&i&&(o.siblings().css({transform:"",opacity:""}),o.parent().children().removeClass("fancybox-slide--previous fancybox-slide--next"),n.fancybox.animate(o,{top:0,left:0,opacity:1},void 0===e?0:e,(function(){o.css({transform:"",opacity:""}),i.isComplete||t.complete()}),!1))},isMoved:function(e){var t,i,o=e||this.current;return!!o&&(i=n.fancybox.getTranslate(this.$refs.stage),t=n.fancybox.getTranslate(o.$slide),!o.$slide.hasClass("fancybox-animated")&&(Math.abs(t.top-i.top)>.5||Math.abs(t.left-i.left)>.5))},updateCursor:function(e,t){var i,o,r=this,a=r.current,s=r.$refs.container;a&&!r.isClosing&&r.Guestures&&(s.removeClass("fancybox-is-zoomable fancybox-can-zoomIn fancybox-can-zoomOut fancybox-can-swipe fancybox-can-pan"),o=!!(i=r.canPan(e,t))||r.isZoomable(),s.toggleClass("fancybox-is-zoomable",o),n("[data-fancybox-zoom]").prop("disabled",!o),i?s.addClass("fancybox-can-pan"):o&&("zoom"===a.opts.clickContent||n.isFunction(a.opts.clickContent)&&"zoom"==a.opts.clickContent(a))?s.addClass("fancybox-can-zoomIn"):a.opts.touch&&(a.opts.touch.vertical||r.group.length>1)&&"video"!==a.contentType&&s.addClass("fancybox-can-swipe"))},isZoomable:function(){var e,t=this,n=t.current;if(n&&!t.isClosing&&"image"===n.type&&!n.hasError){if(!n.isLoaded)return!0;if((e=t.getFitPos(n))&&(n.width>e.width||n.height>e.height))return!0}return!1},isScaledDown:function(e,t){var i=!1,o=this.current,r=o.$content;return void 0!==e&&void 0!==t?i=e1.5||Math.abs(o.height-r.height)>1.5)),r},loadSlide:function(e){var t,i,o,r=this;if(!e.isLoading&&!e.isLoaded){if(e.isLoading=!0,!1===r.trigger("beforeLoad",e))return e.isLoading=!1,!1;switch(t=e.type,(i=e.$slide).off("refresh").trigger("onReset").addClass(e.opts.slideClass),t){case"image":r.setImage(e);break;case"iframe":r.setIframe(e);break;case"html":r.setContent(e,e.src||e.content);break;case"video":r.setContent(e,e.opts.video.tpl.replace(/\{\{src\}\}/gi,e.src).replace("{{format}}",e.opts.videoFormat||e.opts.video.format||"").replace("{{poster}}",e.thumb||""));break;case"inline":n(e.src).length?r.setContent(e,n(e.src)):r.setError(e);break;case"ajax":r.showLoading(e),o=n.ajax(n.extend({},e.opts.ajax.settings,{url:e.src,success:function(t,n){"success"===n&&r.setContent(e,t)},error:function(t,n){t&&"abort"!==n&&r.setError(e)}})),i.one("onReset",(function(){o.abort()}));break;default:r.setError(e)}return!0}},setImage:function(e){var i,o=this;setTimeout((function(){var t=e.$image;o.isClosing||!e.isLoading||t&&t.length&&t[0].complete||e.hasError||o.showLoading(e)}),50),o.checkSrcset(e),e.$content=n('
').addClass("fancybox-is-hidden").appendTo(e.$slide.addClass("fancybox-slide--image")),!1!==e.opts.preload&&e.opts.width&&e.opts.height&&e.thumb&&(e.width=e.opts.width,e.height=e.opts.height,(i=t.createElement("img")).onerror=function(){n(this).remove(),e.$ghost=null},i.onload=function(){o.afterLoad(e)},e.$ghost=n(i).addClass("fancybox-image").appendTo(e.$content).attr("src",e.thumb)),o.setBigImage(e)},checkSrcset:function(t){var n,i,o,r,a=t.opts.srcset||t.opts.image.srcset;if(a){o=e.devicePixelRatio||1,r=e.innerWidth*o,i=a.split(",").map((function(e){var t={};return e.trim().split(/\s+/).forEach((function(e,n){var i=parseInt(e.substring(0,e.length-1),10);if(0===n)return t.url=e;i&&(t.value=i,t.postfix=e[e.length-1])})),t})),i.sort((function(e,t){return e.value-t.value}));for(var s=0;s=r||"x"===l.postfix&&l.value>=o){n=l;break}}!n&&i.length&&(n=i[i.length-1]),n&&(t.src=n.url,t.width&&t.height&&"w"==n.postfix&&(t.height=t.width/t.height*n.value,t.width=n.value),t.opts.srcset=a)}},setBigImage:function(e){var i=this,o=t.createElement("img"),r=n(o);e.$image=r.one("error",(function(){i.setError(e)})).one("load",(function(){var t;e.$ghost||(i.resolveImageSlideSize(e,this.naturalWidth,this.naturalHeight),i.afterLoad(e)),i.isClosing||(e.opts.srcset&&((t=e.opts.sizes)&&"auto"!==t||(t=(e.width/e.height>1&&a.width()/a.height()>1?"100":Math.round(e.width/e.height*100))+"vw"),r.attr("sizes",t).attr("srcset",e.opts.srcset)),e.$ghost&&setTimeout((function(){e.$ghost&&!i.isClosing&&e.$ghost.hide()}),Math.min(300,Math.max(1e3,e.height/1600))),i.hideLoading(e))})).addClass("fancybox-image").attr("src",e.src).appendTo(e.$content),(o.complete||"complete"==o.readyState)&&r.naturalWidth&&r.naturalHeight?r.trigger("load"):o.error&&r.trigger("error")},resolveImageSlideSize:function(e,t,n){var i=parseInt(e.opts.width,10),o=parseInt(e.opts.height,10);e.width=t,e.height=n,i>0&&(e.width=i,e.height=Math.floor(i*n/t)),o>0&&(e.width=Math.floor(o*t/n),e.height=o)},setIframe:function(e){var t,i=this,o=e.opts.iframe,r=e.$slide;e.$content=n('
').css(o.css).appendTo(r),r.addClass("fancybox-slide--"+e.contentType),e.$iframe=t=n(o.tpl.replace(/\{rnd\}/g,(new Date).getTime())).attr(o.attr).appendTo(e.$content),o.preload?(i.showLoading(e),t.on("load.fb error.fb",(function(t){this.isReady=1,e.$slide.trigger("refresh"),i.afterLoad(e)})),r.on("refresh.fb",(function(){var n,i=e.$content,a=o.css.width,s=o.css.height;if(1===t[0].isReady){try{n=t.contents().find("body")}catch(e){}n&&n.length&&n.children().length&&(r.css("overflow","visible"),i.css({width:"100%","max-width":"100%",height:"9999px"}),void 0===a&&(a=Math.ceil(Math.max(n[0].clientWidth,n.outerWidth(!0)))),i.css("width",a||"").css("max-width",""),void 0===s&&(s=Math.ceil(Math.max(n[0].clientHeight,n.outerHeight(!0)))),i.css("height",s||""),r.css("overflow","auto")),i.removeClass("fancybox-is-hidden")}}))):i.afterLoad(e),t.attr("src",e.src),r.one("onReset",(function(){try{n(this).find("iframe").hide().unbind().attr("src","//about:blank")}catch(e){}n(this).off("refresh.fb").empty(),e.isLoaded=!1,e.isRevealed=!1}))},setContent:function(e,t){var i=this;i.isClosing||(i.hideLoading(e),e.$content&&n.fancybox.stop(e.$content),e.$slide.empty(),function(e){return e&&e.hasOwnProperty&&e instanceof n}(t)&&t.parent().length?((t.hasClass("fancybox-content")||t.parent().hasClass("fancybox-content"))&&t.parents(".fancybox-slide").trigger("onReset"),e.$placeholder=n("").hide().insertAfter(t),t.css("display","inline-block")):e.hasError||("string"===n.type(t)&&(t=n("
").append(n.trim(t)).contents()),e.opts.filter&&(t=n("
").html(t).find(e.opts.filter))),e.$slide.one("onReset",(function(){n(this).find("video,audio").trigger("pause"),e.$placeholder&&(e.$placeholder.after(t.removeClass("fancybox-content").hide()).remove(),e.$placeholder=null),e.$smallBtn&&(e.$smallBtn.remove(),e.$smallBtn=null),e.hasError||(n(this).empty(),e.isLoaded=!1,e.isRevealed=!1)})),n(t).appendTo(e.$slide),n(t).is("video,audio")&&(n(t).addClass("fancybox-video"),n(t).wrap("
"),e.contentType="video",e.opts.width=e.opts.width||n(t).attr("width"),e.opts.height=e.opts.height||n(t).attr("height")),e.$content=e.$slide.children().filter("div,form,main,video,audio,article,.fancybox-content").first(),e.$content.siblings().hide(),e.$content.length||(e.$content=e.$slide.wrapInner("
").children().first()),e.$content.addClass("fancybox-content"),e.$slide.addClass("fancybox-slide--"+e.contentType),i.afterLoad(e))},setError:function(e){e.hasError=!0,e.$slide.trigger("onReset").removeClass("fancybox-slide--"+e.contentType).addClass("fancybox-slide--error"),e.contentType="html",this.setContent(e,this.translate(e,e.opts.errorTpl)),e.pos===this.currPos&&(this.isAnimating=!1)},showLoading:function(e){var t=this;(e=e||t.current)&&!e.$spinner&&(e.$spinner=n(t.translate(t,t.opts.spinnerTpl)).appendTo(e.$slide).hide().fadeIn("fast"))},hideLoading:function(e){(e=e||this.current)&&e.$spinner&&(e.$spinner.stop().remove(),delete e.$spinner)},afterLoad:function(e){var t=this;t.isClosing||(e.isLoading=!1,e.isLoaded=!0,t.trigger("afterLoad",e),t.hideLoading(e),!e.opts.smallBtn||e.$smallBtn&&e.$smallBtn.length||(e.$smallBtn=n(t.translate(e,e.opts.btnTpl.smallBtn)).appendTo(e.$content)),e.opts.protect&&e.$content&&!e.hasError&&(e.$content.on("contextmenu.fb",(function(e){return 2==e.button&&e.preventDefault(),!0})),"image"===e.type&&n('
').appendTo(e.$content)),t.adjustCaption(e),t.adjustLayout(e),e.pos===t.currPos&&t.updateCursor(),t.revealContent(e))},adjustCaption:function(e){var t,n=this,i=e||n.current,o=i.opts.caption,r=i.opts.preventCaptionOverlap,a=n.$refs.caption,s=!1;a.toggleClass("fancybox-caption--separate",r),r&&o&&o.length&&(i.pos!==n.currPos?((t=a.clone().appendTo(a.parent())).children().eq(0).empty().html(o),s=t.outerHeight(!0),t.empty().remove()):n.$caption&&(s=n.$caption.outerHeight(!0)),i.$slide.css("padding-bottom",s||""))},adjustLayout:function(e){var t,n,i,o,r=e||this.current;r.isLoaded&&!0!==r.opts.disableLayoutFix&&(r.$content.css("margin-bottom",""),r.$content.outerHeight()>r.$slide.height()+.5&&(i=r.$slide[0].style["padding-bottom"],o=r.$slide.css("padding-bottom"),parseFloat(o)>0&&(t=r.$slide[0].scrollHeight,r.$slide.css("padding-bottom",0),Math.abs(t-r.$slide[0].scrollHeight)<1&&(n=o),r.$slide.css("padding-bottom",i))),r.$content.css("margin-bottom",n))},revealContent:function(e){var t,i,o,r,a=this,s=e.$slide,l=!1,c=!1,u=a.isMoved(e),d=e.isRevealed;return e.isRevealed=!0,t=e.opts[a.firstRun?"animationEffect":"transitionEffect"],o=e.opts[a.firstRun?"animationDuration":"transitionDuration"],o=parseInt(void 0===e.forcedDuration?o:e.forcedDuration,10),!u&&e.pos===a.currPos&&o||(t=!1),"zoom"===t&&(e.pos===a.currPos&&o&&"image"===e.type&&!e.hasError&&(c=a.getThumbPos(e))?l=a.getFitPos(e):t="fade"),"zoom"===t?(a.isAnimating=!0,l.scaleX=l.width/c.width,l.scaleY=l.height/c.height,"auto"==(r=e.opts.zoomOpacity)&&(r=Math.abs(e.width/e.height-c.width/c.height)>.1),r&&(c.opacity=.1,l.opacity=1),n.fancybox.setTranslate(e.$content.removeClass("fancybox-is-hidden"),c),f(e.$content),void n.fancybox.animate(e.$content,l,o,(function(){a.isAnimating=!1,a.complete()}))):(a.updateSlide(e),t?(n.fancybox.stop(s),i="fancybox-slide--"+(e.pos>=a.prevPos?"next":"previous")+" fancybox-animated fancybox-fx-"+t,s.addClass(i).removeClass("fancybox-slide--current"),e.$content.removeClass("fancybox-is-hidden"),f(s),"image"!==e.type&&e.$content.hide().show(0),void n.fancybox.animate(s,"fancybox-slide--current",o,(function(){s.removeClass(i).css({transform:"",opacity:""}),e.pos===a.currPos&&a.complete()}),!0)):(e.$content.removeClass("fancybox-is-hidden"),d||!u||"image"!==e.type||e.hasError||e.$content.hide().fadeIn("fast"),void(e.pos===a.currPos&&a.complete())))},getThumbPos:function(e){var t,i,o,r,a,s=!1,l=e.$thumb;return!(!l||!p(l[0]))&&(t=n.fancybox.getTranslate(l),i=parseFloat(l.css("border-top-width")||0),o=parseFloat(l.css("border-right-width")||0),r=parseFloat(l.css("border-bottom-width")||0),a=parseFloat(l.css("border-left-width")||0),s={top:t.top+i,left:t.left+a,width:t.width-o-a,height:t.height-i-r,scaleX:1,scaleY:1},t.width>0&&t.height>0&&s)},complete:function(){var e,t=this,i=t.current,o={};!t.isMoved()&&i.isLoaded&&(i.isComplete||(i.isComplete=!0,i.$slide.siblings().trigger("onReset"),t.preload("inline"),f(i.$slide),i.$slide.addClass("fancybox-slide--complete"),n.each(t.slides,(function(e,i){i.pos>=t.currPos-1&&i.pos<=t.currPos+1?o[i.pos]=i:i&&(n.fancybox.stop(i.$slide),i.$slide.off().remove())})),t.slides=o),t.isAnimating=!1,t.updateCursor(),t.trigger("afterShow"),i.opts.video.autoStart&&i.$slide.find("video,audio").filter(":visible:first").trigger("play").one("ended",(function(){Document.exitFullscreen?Document.exitFullscreen():this.webkitExitFullscreen&&this.webkitExitFullscreen(),t.next()})),i.opts.autoFocus&&"html"===i.contentType&&((e=i.$content.find("input[autofocus]:enabled:visible:first")).length?e.trigger("focus"):t.focus(null,!0)),i.$slide.scrollTop(0).scrollLeft(0))},preload:function(e){var t,n,i=this;i.group.length<2||(n=i.slides[i.currPos+1],(t=i.slides[i.currPos-1])&&t.type===e&&i.loadSlide(t),n&&n.type===e&&i.loadSlide(n))},focus:function(e,i){var o,r,a=this,s=["a[href]","area[href]",'input:not([disabled]):not([type="hidden"]):not([aria-hidden])',"select:not([disabled]):not([aria-hidden])","textarea:not([disabled]):not([aria-hidden])","button:not([disabled]):not([aria-hidden])","iframe","object","embed","video","audio","[contenteditable]",'[tabindex]:not([tabindex^="-"])'].join(",");a.isClosing||((o=(o=!e&&a.current&&a.current.isComplete?a.current.$slide.find("*:visible"+(i?":not(.fancybox-close-small)":"")):a.$refs.container.find("*:visible")).filter(s).filter((function(){return"hidden"!==n(this).css("visibility")&&!n(this).hasClass("disabled")}))).length?(r=o.index(t.activeElement),e&&e.shiftKey?(r<0||0==r)&&(e.preventDefault(),o.eq(o.length-1).trigger("focus")):(r<0||r==o.length-1)&&(e&&e.preventDefault(),o.eq(0).trigger("focus"))):a.$refs.container.trigger("focus"))},activate:function(){var e=this;n(".fancybox-container").each((function(){var t=n(this).data("FancyBox");t&&t.id!==e.id&&!t.isClosing&&(t.trigger("onDeactivate"),t.removeEvents(),t.isVisible=!1)})),e.isVisible=!0,(e.current||e.isIdle)&&(e.update(),e.updateControls()),e.trigger("onActivate"),e.addEvents()},close:function(e,t){var i,o,r,a,s,l,u,d=this,h=d.current,p=function(){d.cleanUp(e)};return!(d.isClosing||(d.isClosing=!0,!1===d.trigger("beforeClose",e)?(d.isClosing=!1,c((function(){d.update()})),1):(d.removeEvents(),r=h.$content,i=h.opts.animationEffect,o=n.isNumeric(t)?t:i?h.opts.animationDuration:0,h.$slide.removeClass("fancybox-slide--complete fancybox-slide--next fancybox-slide--previous fancybox-animated"),!0!==e?n.fancybox.stop(h.$slide):i=!1,h.$slide.siblings().trigger("onReset").remove(),o&&d.$refs.container.removeClass("fancybox-is-open").addClass("fancybox-is-closing").css("transition-duration",o+"ms"),d.hideLoading(h),d.hideControls(!0),d.updateCursor(),"zoom"!==i||r&&o&&"image"===h.type&&!d.isMoved()&&!h.hasError&&(u=d.getThumbPos(h))||(i="fade"),"zoom"===i?(n.fancybox.stop(r),a=n.fancybox.getTranslate(r),l={top:a.top,left:a.left,scaleX:a.width/u.width,scaleY:a.height/u.height,width:u.width,height:u.height},s=h.opts.zoomOpacity,"auto"==s&&(s=Math.abs(h.width/h.height-u.width/u.height)>.1),s&&(u.opacity=0),n.fancybox.setTranslate(r,l),f(r),n.fancybox.animate(r,u,o,p),0):(i&&o?n.fancybox.animate(h.$slide.addClass("fancybox-slide--previous").removeClass("fancybox-slide--current"),"fancybox-animated fancybox-fx-"+i,o,p):!0===e?setTimeout(p,o):p(),0))))},cleanUp:function(t){var i,o,r,a=this,s=a.current.opts.$orig;a.current.$slide.trigger("onReset"),a.$refs.container.empty().remove(),a.trigger("afterClose",t),a.current.opts.backFocus&&(s&&s.length&&s.is(":visible")||(s=a.$trigger),s&&s.length&&(o=e.scrollX,r=e.scrollY,s.trigger("focus"),n("html, body").scrollTop(r).scrollLeft(o))),a.current=null,(i=n.fancybox.getInstance())?i.activate():(n("body").removeClass("fancybox-active compensate-for-scrollbar"),n("#fancybox-style-noscroll").remove())},trigger:function(e,t){var i,o=Array.prototype.slice.call(arguments,1),r=this,a=t&&t.opts?t:r.current;if(a?o.unshift(a):a=r,o.unshift(r),n.isFunction(a.opts[e])&&(i=a.opts[e].apply(a,o)),!1===i)return i;"afterClose"!==e&&r.$refs?r.$refs.container.trigger(e+".fb",o):s.trigger(e+".fb",o)},updateControls:function(){var e=this,i=e.current,o=i.index,r=e.$refs.container,a=e.$refs.caption,s=i.opts.caption;i.$slide.trigger("refresh"),s&&s.length?(e.$caption=a,a.children().eq(0).html(s)):e.$caption=null,e.hasHiddenControls||e.isIdle||e.showControls(),r.find("[data-fancybox-count]").html(e.group.length),r.find("[data-fancybox-index]").html(o+1),r.find("[data-fancybox-prev]").prop("disabled",!i.opts.loop&&o<=0),r.find("[data-fancybox-next]").prop("disabled",!i.opts.loop&&o>=e.group.length-1),"image"===i.type?r.find("[data-fancybox-zoom]").show().end().find("[data-fancybox-download]").attr("href",i.opts.image.src||i.src).show():i.opts.toolbar&&r.find("[data-fancybox-download],[data-fancybox-zoom]").hide(),n(t.activeElement).is(":hidden,[disabled]")&&e.$refs.container.trigger("focus")},hideControls:function(e){var t=["infobar","toolbar","nav"];!e&&this.current.opts.preventCaptionOverlap||t.push("caption"),this.$refs.container.removeClass(t.map((function(e){return"fancybox-show-"+e})).join(" ")),this.hasHiddenControls=!0},showControls:function(){var e=this,t=e.current?e.current.opts:e.opts,n=e.$refs.container;e.hasHiddenControls=!1,e.idleSecondsCounter=0,n.toggleClass("fancybox-show-toolbar",!(!t.toolbar||!t.buttons)).toggleClass("fancybox-show-infobar",!!(t.infobar&&e.group.length>1)).toggleClass("fancybox-show-caption",!!e.$caption).toggleClass("fancybox-show-nav",!!(t.arrows&&e.group.length>1)).toggleClass("fancybox-is-modal",!!t.modal)},toggleControls:function(){this.hasHiddenControls?this.showControls():this.hideControls()}}),n.fancybox={version:"3.5.7",defaults:r,getInstance:function(e){var t=n('.fancybox-container:not(".fancybox-is-closing"):last').data("FancyBox"),i=Array.prototype.slice.call(arguments,1);return t instanceof g&&("string"===n.type(e)?t[e].apply(t,i):"function"===n.type(e)&&e.apply(t,i),t)},open:function(e,t,n){return new g(e,t,n)},close:function(e){var t=this.getInstance();t&&(t.close(),!0===e&&this.close(e))},destroy:function(){this.close(!0),s.add("body").off("click.fb-start","**")},isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),use3d:function(){var n=t.createElement("div");return e.getComputedStyle&&e.getComputedStyle(n)&&e.getComputedStyle(n).getPropertyValue("transform")&&!(t.documentMode&&t.documentMode<11)}(),getTranslate:function(e){var t;return!(!e||!e.length)&&{top:(t=e[0].getBoundingClientRect()).top||0,left:t.left||0,width:t.width,height:t.height,opacity:parseFloat(e.css("opacity"))}},setTranslate:function(e,t){var n="",i={};if(e&&t)return void 0===t.left&&void 0===t.top||(n=(void 0===t.left?e.position().left:t.left)+"px, "+(void 0===t.top?e.position().top:t.top)+"px",n=this.use3d?"translate3d("+n+", 0px)":"translate("+n+")"),void 0!==t.scaleX&&void 0!==t.scaleY?n+=" scale("+t.scaleX+", "+t.scaleY+")":void 0!==t.scaleX&&(n+=" scaleX("+t.scaleX+")"),n.length&&(i.transform=n),void 0!==t.opacity&&(i.opacity=t.opacity),void 0!==t.width&&(i.width=t.width),void 0!==t.height&&(i.height=t.height),e.css(i)},animate:function(e,t,i,o,r){var a,s=this;n.isFunction(i)&&(o=i,i=null),s.stop(e),a=s.getTranslate(e),e.on(d,(function(l){(!l||!l.originalEvent||e.is(l.originalEvent.target)&&"z-index"!=l.originalEvent.propertyName)&&(s.stop(e),n.isNumeric(i)&&e.css("transition-duration",""),n.isPlainObject(t)?void 0!==t.scaleX&&void 0!==t.scaleY&&s.setTranslate(e,{top:t.top,left:t.left,width:a.width*t.scaleX,height:a.height*t.scaleY,scaleX:1,scaleY:1}):!0!==r&&e.removeClass(t),n.isFunction(o)&&o(l))})),n.isNumeric(i)&&e.css("transition-duration",i+"ms"),n.isPlainObject(t)?(void 0!==t.scaleX&&void 0!==t.scaleY&&(delete t.width,delete t.height,e.parent().hasClass("fancybox-slide--image")&&e.parent().addClass("fancybox-is-scaling")),n.fancybox.setTranslate(e,t)):e.addClass(t),e.data("timer",setTimeout((function(){e.trigger(d)}),i+33))},stop:function(e,t){e&&e.length&&(clearTimeout(e.data("timer")),t&&e.trigger(d),e.off(d).css("transition-duration",""),e.parent().removeClass("fancybox-is-scaling"))}},n.fn.fancybox=function(e){var t;return(t=(e=e||{}).selector||!1)?n("body").off("click.fb-start",t).on("click.fb-start",t,{options:e},o):this.off("click.fb-start").on("click.fb-start",{items:this,options:e},o),this},s.on("click.fb-start","[data-fancybox]",o),s.on("click.fb-start","[data-fancybox-trigger]",(function(e){n('[data-fancybox="'+n(this).attr("data-fancybox-trigger")+'"]').eq(n(this).attr("data-fancybox-index")||0).trigger("click.fb-start",{$trigger:n(this)})})),function(){var e=null;s.on("mousedown mouseup focus blur",".fancybox-button",(function(t){switch(t.type){case"mousedown":e=n(this);break;case"mouseup":e=null;break;case"focusin":n(".fancybox-button").removeClass("fancybox-focus"),n(this).is(e)||n(this).is("[disabled]")||n(this).addClass("fancybox-focus");break;case"focusout":n(".fancybox-button").removeClass("fancybox-focus")}}))}()}}(window,document,jQuery),function(e){"use strict";var t={youtube:{matcher:/(youtube\.com|youtu\.be|youtube\-nocookie\.com)\/(watch\?(.*&)?v=|v\/|u\/|embed\/?)?(videoseries\?list=(.*)|[\w-]{11}|\?listType=(.*)&list=(.*))(.*)/i,params:{autoplay:1,autohide:1,fs:1,rel:0,hd:1,wmode:"transparent",enablejsapi:1,html5:1},paramPlace:8,type:"iframe",url:"https://www.youtube-nocookie.com/embed/$4",thumb:"https://img.youtube.com/vi/$4/hqdefault.jpg"},vimeo:{matcher:/^.+vimeo.com\/(.*\/)?([\d]+)(.*)?/,params:{autoplay:1,hd:1,show_title:1,show_byline:1,show_portrait:0,fullscreen:1},paramPlace:3,type:"iframe",url:"//player.vimeo.com/video/$2"},instagram:{matcher:/(instagr\.am|instagram\.com)\/p\/([a-zA-Z0-9_\-]+)\/?/i,type:"image",url:"//$1/p/$2/media/?size=l"},gmap_place:{matcher:/(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(((maps\/(place\/(.*)\/)?\@(.*),(\d+.?\d+?)z))|(\?ll=))(.*)?/i,type:"iframe",url:function(e){return"//maps.google."+e[2]+"/?ll="+(e[9]?e[9]+"&z="+Math.floor(e[10])+(e[12]?e[12].replace(/^\//,"&"):""):e[12]+"").replace(/\?/,"&")+"&output="+(e[12]&&e[12].indexOf("layer=c")>0?"svembed":"embed")}},gmap_search:{matcher:/(maps\.)?google\.([a-z]{2,3}(\.[a-z]{2})?)\/(maps\/search\/)(.*)/i,type:"iframe",url:function(e){return"//maps.google."+e[2]+"/maps?q="+e[5].replace("query=","q=").replace("api=1","")+"&output=embed"}}},n=function(t,n,i){if(t)return i=i||"","object"===e.type(i)&&(i=e.param(i,!0)),e.each(n,(function(e,n){t=t.replace("$"+e,n||"")})),i.length&&(t+=(t.indexOf("?")>0?"&":"?")+i),t};e(document).on("objectNeedsType.fb",(function(i,o,r){var a,s,l,c,u,d,f,h=r.src||"",p=!1;a=e.extend(!0,{},t,r.opts.media),e.each(a,(function(t,i){if(l=h.match(i.matcher)){if(p=i.type,f=t,d={},i.paramPlace&&l[i.paramPlace]){"?"==(u=l[i.paramPlace])[0]&&(u=u.substring(1)),u=u.split("&");for(var o=0;o
1&&("youtube"===n.contentSource||"vimeo"===n.contentSource)&&i.load(n.contentSource)}})}(jQuery),function(e,t,n){"use strict";var i=e.requestAnimationFrame||e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||function(t){return e.setTimeout(t,1e3/60)},o=e.cancelAnimationFrame||e.webkitCancelAnimationFrame||e.mozCancelAnimationFrame||e.oCancelAnimationFrame||function(t){e.clearTimeout(t)},r=function(t){var n=[];for(var i in t=(t=t.originalEvent||t||e.e).touches&&t.touches.length?t.touches:t.changedTouches&&t.changedTouches.length?t.changedTouches:[t])t[i].pageX?n.push({x:t[i].pageX,y:t[i].pageY}):t[i].clientX&&n.push({x:t[i].clientX,y:t[i].clientY});return n},a=function(e,t,n){return t&&e?"x"===n?e.x-t.x:"y"===n?e.y-t.y:Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2)):0},s=function(e){if(e.is('a,area,button,[role="button"],input,label,select,summary,textarea,video,audio,iframe')||n.isFunction(e.get(0).onclick)||e.data("selectable"))return!0;for(var t=0,i=e[0].attributes,o=i.length;tt.clientHeight,r=("scroll"===i||"auto"===i)&&t.scrollWidth>t.clientWidth;return o||r},c=function(e){for(var t=!1;!(t=l(e.get(0)))&&((e=e.parent()).length&&!e.hasClass("fancybox-stage")&&!e.is("body")););return t},u=function(e){var t=this;t.instance=e,t.$bg=e.$refs.bg,t.$stage=e.$refs.stage,t.$container=e.$refs.container,t.destroy(),t.$container.on("touchstart.fb.touch mousedown.fb.touch",n.proxy(t,"ontouchstart"))};u.prototype.destroy=function(){var e=this;e.$container.off(".fb.touch"),n(t).off(".fb.touch"),e.requestId&&(o(e.requestId),e.requestId=null),e.tapped&&(clearTimeout(e.tapped),e.tapped=null)},u.prototype.ontouchstart=function(i){var o=this,l=n(i.target),u=o.instance,d=u.current,f=d.$slide,h=d.$content,p="touchstart"==i.type;if(p&&o.$container.off("mousedown.fb.touch"),(!i.originalEvent||2!=i.originalEvent.button)&&f.length&&l.length&&!s(l)&&!s(l.parent())&&(l.is("img")||!(i.originalEvent.clientX>l[0].clientWidth+l.offset().left))){if(!d||u.isAnimating||d.$slide.hasClass("fancybox-animated"))return i.stopPropagation(),void i.preventDefault();o.realPoints=o.startPoints=r(i),o.startPoints.length&&(d.touch&&i.stopPropagation(),o.startEvent=i,o.canTap=!0,o.$target=l,o.$content=h,o.opts=d.opts.touch,o.isPanning=!1,o.isSwiping=!1,o.isZooming=!1,o.isScrolling=!1,o.canPan=u.canPan(),o.startTime=(new Date).getTime(),o.distanceX=o.distanceY=o.distance=0,o.canvasWidth=Math.round(f[0].clientWidth),o.canvasHeight=Math.round(f[0].clientHeight),o.contentLastPos=null,o.contentStartPos=n.fancybox.getTranslate(o.$content)||{top:0,left:0},o.sliderStartPos=n.fancybox.getTranslate(f),o.stagePos=n.fancybox.getTranslate(u.$refs.stage),o.sliderStartPos.top-=o.stagePos.top,o.sliderStartPos.left-=o.stagePos.left,o.contentStartPos.top-=o.stagePos.top,o.contentStartPos.left-=o.stagePos.left,n(t).off(".fb.touch").on(p?"touchend.fb.touch touchcancel.fb.touch":"mouseup.fb.touch mouseleave.fb.touch",n.proxy(o,"ontouchend")).on(p?"touchmove.fb.touch":"mousemove.fb.touch",n.proxy(o,"ontouchmove")),n.fancybox.isMobile&&t.addEventListener("scroll",o.onscroll,!0),((o.opts||o.canPan)&&(l.is(o.$stage)||o.$stage.find(l).length)||(l.is(".fancybox-image")&&i.preventDefault(),n.fancybox.isMobile&&l.parents(".fancybox-caption").length))&&(o.isScrollable=c(l)||c(l.parent()),n.fancybox.isMobile&&o.isScrollable||i.preventDefault(),(1===o.startPoints.length||d.hasError)&&(o.canPan?(n.fancybox.stop(o.$content),o.isPanning=!0):o.isSwiping=!0,o.$container.addClass("fancybox-is-grabbing")),2===o.startPoints.length&&"image"===d.type&&(d.isLoaded||d.$ghost)&&(o.canTap=!1,o.isSwiping=!1,o.isPanning=!1,o.isZooming=!0,n.fancybox.stop(o.$content),o.centerPointStartX=.5*(o.startPoints[0].x+o.startPoints[1].x)-n(e).scrollLeft(),o.centerPointStartY=.5*(o.startPoints[0].y+o.startPoints[1].y)-n(e).scrollTop(),o.percentageOfImageAtPinchPointX=(o.centerPointStartX-o.contentStartPos.left)/o.contentStartPos.width,o.percentageOfImageAtPinchPointY=(o.centerPointStartY-o.contentStartPos.top)/o.contentStartPos.height,o.startDistanceBetweenFingers=a(o.startPoints[0],o.startPoints[1]))))}},u.prototype.onscroll=function(e){this.isScrolling=!0,t.removeEventListener("scroll",this.onscroll,!0)},u.prototype.ontouchmove=function(e){var t=this;return void 0!==e.originalEvent.buttons&&0===e.originalEvent.buttons?void t.ontouchend(e):t.isScrolling?void(t.canTap=!1):(t.newPoints=r(e),void((t.opts||t.canPan)&&t.newPoints.length&&t.newPoints.length&&(t.isSwiping&&!0===t.isSwiping||e.preventDefault(),t.distanceX=a(t.newPoints[0],t.startPoints[0],"x"),t.distanceY=a(t.newPoints[0],t.startPoints[0],"y"),t.distance=a(t.newPoints[0],t.startPoints[0]),t.distance>0&&(t.isSwiping?t.onSwipe(e):t.isPanning?t.onPan():t.isZooming&&t.onZoom()))))},u.prototype.onSwipe=function(t){var r,a=this,s=a.instance,l=a.isSwiping,c=a.sliderStartPos.left||0;if(!0!==l)"x"==l&&(a.distanceX>0&&(a.instance.group.length<2||0===a.instance.current.index&&!a.instance.current.opts.loop)?c+=Math.pow(a.distanceX,.8):a.distanceX<0&&(a.instance.group.length<2||a.instance.current.index===a.instance.group.length-1&&!a.instance.current.opts.loop)?c-=Math.pow(-a.distanceX,.8):c+=a.distanceX),a.sliderLastPos={top:"x"==l?0:a.sliderStartPos.top+a.distanceY,left:c},a.requestId&&(o(a.requestId),a.requestId=null),a.requestId=i((function(){a.sliderLastPos&&(n.each(a.instance.slides,(function(e,t){var i=t.pos-a.instance.currPos;n.fancybox.setTranslate(t.$slide,{top:a.sliderLastPos.top,left:a.sliderLastPos.left+i*a.canvasWidth+i*t.opts.gutter})})),a.$container.addClass("fancybox-is-sliding"))}));else if(Math.abs(a.distance)>10){if(a.canTap=!1,s.group.length<2&&a.opts.vertical?a.isSwiping="y":s.isDragging||!1===a.opts.vertical||"auto"===a.opts.vertical&&n(e).width()>800?a.isSwiping="x":(r=Math.abs(180*Math.atan2(a.distanceY,a.distanceX)/Math.PI),a.isSwiping=r>45&&r<135?"y":"x"),"y"===a.isSwiping&&n.fancybox.isMobile&&a.isScrollable)return void(a.isScrolling=!0);s.isDragging=a.isSwiping,a.startPoints=a.newPoints,n.each(s.slides,(function(e,t){var i,o;n.fancybox.stop(t.$slide),i=n.fancybox.getTranslate(t.$slide),o=n.fancybox.getTranslate(s.$refs.stage),t.$slide.css({transform:"",opacity:"","transition-duration":""}).removeClass("fancybox-animated").removeClass((function(e,t){return(t.match(/(^|\s)fancybox-fx-\S+/g)||[]).join(" ")})),t.pos===s.current.pos&&(a.sliderStartPos.top=i.top-o.top,a.sliderStartPos.left=i.left-o.left),n.fancybox.setTranslate(t.$slide,{top:i.top-o.top,left:i.left-o.left})})),s.SlideShow&&s.SlideShow.isActive&&s.SlideShow.stop()}},u.prototype.onPan=function(){var e=this;a(e.newPoints[0],e.realPoints[0])<(n.fancybox.isMobile?10:5)?e.startPoints=e.newPoints:(e.canTap=!1,e.contentLastPos=e.limitMovement(),e.requestId&&o(e.requestId),e.requestId=i((function(){n.fancybox.setTranslate(e.$content,e.contentLastPos)})))},u.prototype.limitMovement=function(){var e,t,n,i,o,r,a=this,s=a.canvasWidth,l=a.canvasHeight,c=a.distanceX,u=a.distanceY,d=a.contentStartPos,f=d.left,h=d.top,p=d.width,g=d.height;return o=p>s?f+c:f,r=h+u,e=Math.max(0,.5*s-.5*p),t=Math.max(0,.5*l-.5*g),n=Math.min(s-p,.5*s-.5*p),i=Math.min(l-g,.5*l-.5*g),c>0&&o>e&&(o=e-1+Math.pow(-e+f+c,.8)||0),c<0&&o0&&r>t&&(r=t-1+Math.pow(-t+h+u,.8)||0),u<0&&ro?e=(e=e>0?0:e)r?t=(t=t>0?0:t)1&&(i.dMs>130&&a>10||a>50);i.sliderLastPos=null,"y"==e&&!t&&Math.abs(i.distanceY)>50?(n.fancybox.animate(i.instance.current.$slide,{top:i.sliderStartPos.top+i.distanceY+150*i.velocityY,opacity:0},200),o=i.instance.close(!0,250)):s&&i.distanceX>0?o=i.instance.previous(300):s&&i.distanceX<0&&(o=i.instance.next(300)),!1!==o||"x"!=e&&"y"!=e||i.instance.centerSlide(200),i.$container.removeClass("fancybox-is-sliding")},u.prototype.endPanning=function(){var e,t,i,o=this;o.contentLastPos&&(!1===o.opts.momentum||o.dMs>350?(e=o.contentLastPos.left,t=o.contentLastPos.top):(e=o.contentLastPos.left+500*o.velocityX,t=o.contentLastPos.top+500*o.velocityY),(i=o.limitPosition(e,t,o.contentStartPos.width,o.contentStartPos.height)).width=o.contentStartPos.width,i.height=o.contentStartPos.height,n.fancybox.animate(o.$content,i,366))},u.prototype.endZooming=function(){var e,t,i,o,r=this,a=r.instance.current,s=r.newWidth,l=r.newHeight;r.contentLastPos&&(e=r.contentLastPos.left,o={top:t=r.contentLastPos.top,left:e,width:s,height:l,scaleX:1,scaleY:1},n.fancybox.setTranslate(r.$content,o),sa.width||l>a.height?r.instance.scaleToActual(r.centerPointStartX,r.centerPointStartY,150):(i=r.limitPosition(e,t,s,l),n.fancybox.animate(r.$content,i,150)))},u.prototype.onTap=function(t){var i,o=this,a=n(t.target),s=o.instance,l=s.current,c=t&&r(t)||o.startPoints,u=c[0]?c[0].x-n(e).scrollLeft()-o.stagePos.left:0,d=c[0]?c[0].y-n(e).scrollTop()-o.stagePos.top:0,f=function(e){var i=l.opts[e];if(n.isFunction(i)&&(i=i.apply(s,[l,t])),i)switch(i){case"close":s.close(o.startEvent);break;case"toggleControls":s.toggleControls();break;case"next":s.next();break;case"nextOrClose":s.group.length>1?s.next():s.close(o.startEvent);break;case"zoom":"image"==l.type&&(l.isLoaded||l.$ghost)&&(s.canPan()?s.scaleToFit():s.isScaledDown()?s.scaleToActual(u,d):s.group.length<2&&s.close(o.startEvent))}};if((!t.originalEvent||2!=t.originalEvent.button)&&(a.is("img")||!(u>a[0].clientWidth+a.offset().left))){if(a.is(".fancybox-bg,.fancybox-inner,.fancybox-outer,.fancybox-container"))i="Outside";else if(a.is(".fancybox-slide"))i="Slide";else{if(!s.current.$content||!s.current.$content.find(a).addBack().filter(a).length)return;i="Content"}if(o.tapped){if(clearTimeout(o.tapped),o.tapped=null,Math.abs(u-o.tapX)>50||Math.abs(d-o.tapY)>50)return this;f("dblclick"+i)}else o.tapX=u,o.tapY=d,l.opts["dblclick"+i]&&l.opts["dblclick"+i]!==l.opts["click"+i]?o.tapped=setTimeout((function(){o.tapped=null,s.isAnimating||f("click"+i)}),500):f("click"+i);return this}},n(t).on("onActivate.fb",(function(e,t){t&&!t.Guestures&&(t.Guestures=new u(t))})).on("beforeClose.fb",(function(e,t){t&&t.Guestures&&t.Guestures.destroy()}))}(window,document,jQuery),function(e,t){"use strict";t.extend(!0,t.fancybox.defaults,{btnTpl:{slideShow:' '},slideShow:{autoStart:!1,speed:3e3,progress:!0}});var n=function(e){this.instance=e,this.init()};t.extend(n.prototype,{timer:null,isActive:!1,$button:null,init:function(){var e=this,n=e.instance,i=n.group[n.currIndex].opts.slideShow;e.$button=n.$refs.toolbar.find("[data-fancybox-play]").on("click",(function(){e.toggle()})),n.group.length<2||!i?e.$button.hide():i.progress&&(e.$progress=t('
').appendTo(n.$refs.inner))},set:function(e){var n=this,i=n.instance,o=i.current;o&&(!0===e||o.opts.loop||i.currIndex '},fullScreen:{autoStart:!1}}),t(e).on(n.fullscreenchange,(function(){var e=i.isFullscreen(),n=t.fancybox.getInstance();n&&(n.current&&"image"===n.current.type&&n.isAnimating&&(n.isAnimating=!1,n.update(!0,!0,0),n.isComplete||n.complete()),n.trigger("onFullscreenChange",e),n.$refs.container.toggleClass("fancybox-is-fullscreen",e),n.$refs.toolbar.find("[data-fancybox-fullscreen]").toggleClass("fancybox-button--fsenter",!e).toggleClass("fancybox-button--fsexit",e))}))}t(e).on({"onInit.fb":function(e,t){n?t&&t.group[t.currIndex].opts.fullScreen?(t.$refs.container.on("click.fb-fullscreen","[data-fancybox-fullscreen]",(function(e){e.stopPropagation(),e.preventDefault(),i.toggle()})),t.opts.fullScreen&&!0===t.opts.fullScreen.autoStart&&i.request(),t.FullScreen=i):t&&t.$refs.toolbar.find("[data-fancybox-fullscreen]").hide():t.$refs.toolbar.find("[data-fancybox-fullscreen]").remove()},"afterKeydown.fb":function(e,t,n,i,o){t&&t.FullScreen&&70===o&&(i.preventDefault(),t.FullScreen.toggle())},"beforeClose.fb":function(e,t){t&&t.FullScreen&&t.$refs.container.hasClass("fancybox-is-fullscreen")&&i.exit()}})}(document,jQuery),function(e,t){"use strict";var n="fancybox-thumbs";t.fancybox.defaults=t.extend(!0,{btnTpl:{thumbs:' '},thumbs:{autoStart:!1,hideOnClose:!0,parentEl:".fancybox-container",axis:"y"}},t.fancybox.defaults);var i=function(e){this.init(e)};t.extend(i.prototype,{$button:null,$grid:null,$list:null,isVisible:!1,isActive:!1,init:function(e){var t=this,n=e.group,i=0;t.instance=e,t.opts=n[e.currIndex].opts.thumbs,e.Thumbs=t,t.$button=e.$refs.toolbar.find("[data-fancybox-thumbs]");for(var o=0,r=n.length;o1));o++);i>1&&t.opts?(t.$button.removeAttr("style").on("click",(function(){t.toggle()})),t.isActive=!0):t.$button.hide()},create:function(){var e,i=this,o=i.instance,r=i.opts.parentEl,a=[];i.$grid||(i.$grid=t('
').appendTo(o.$refs.container.find(r).addBack().filter(r)),i.$grid.on("click","a",(function(){o.jumpTo(t(this).attr("data-index"))}))),i.$list||(i.$list=t('').appendTo(i.$grid)),t.each(o.group,(function(t,n){(e=n.thumb)||"image"!==n.type||(e=n.src),a.push('
")})),i.$list[0].innerHTML=a.join(""),"x"===i.opts.axis&&i.$list.width(parseInt(i.$grid.css("padding-right"),10)+o.group.length*i.$list.children().eq(0).outerWidth(!0))},focus:function(e){var t,n,i=this,o=i.$list,r=i.$grid;i.instance.current&&(n=(t=o.children().removeClass("fancybox-thumbs-active").filter('[data-index="'+i.instance.current.index+'"]').addClass("fancybox-thumbs-active")).position(),"y"===i.opts.axis&&(n.top<0||n.top>o.height()-t.outerHeight())?o.stop().animate({scrollTop:o.scrollTop()+n.top},e):"x"===i.opts.axis&&(n.left
r.scrollLeft()+(r.width()-t.outerWidth()))&&o.parent().stop().animate({scrollLeft:n.left},e))},update:function(){var e=this;e.instance.$refs.container.toggleClass("fancybox-show-thumbs",this.isVisible),e.isVisible?(e.$grid||e.create(),e.instance.trigger("onThumbsShow"),e.focus(0)):e.$grid&&e.instance.trigger("onThumbsHide"),e.instance.update()},hide:function(){this.isVisible=!1,this.update()},show:function(){this.isVisible=!0,this.update()},toggle:function(){this.isVisible=!this.isVisible,this.update()}}),t(e).on({"onInit.fb":function(e,t){var n;t&&!t.Thumbs&&((n=new i(t)).isActive&&!0===n.opts.autoStart&&n.show())},"beforeShow.fb":function(e,t,n,i){var o=t&&t.Thumbs;o&&o.isVisible&&o.focus(i?0:250)},"afterKeydown.fb":function(e,t,n,i,o){var r=t&&t.Thumbs;r&&r.isActive&&71===o&&(i.preventDefault(),r.toggle())},"beforeClose.fb":function(e,t){var n=t&&t.Thumbs;n&&n.isVisible&&!1!==n.opts.hideOnClose&&n.$grid.hide()}})}(document,jQuery),function(e,t){"use strict";t.extend(!0,t.fancybox.defaults,{btnTpl:{share:' '},share:{url:function(e,t){return!e.currentHash&&"inline"!==t.type&&"html"!==t.type&&(t.origSrc||t.src)||window.location},tpl:''}}),t(e).on("click","[data-fancybox-share]",(function(){var e,n,i=t.fancybox.getInstance(),o=i.current||null;o&&("function"===t.type(o.opts.share.url)&&(e=o.opts.share.url.apply(o,[i,o])),n=o.opts.share.tpl.replace(/\{\{media\}\}/g,"image"===o.type?encodeURIComponent(o.src):"").replace(/\{\{url\}\}/g,encodeURIComponent(e)).replace(/\{\{url_raw\}\}/g,function(e){var t={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};return String(e).replace(/[&<>"'`=\/]/g,(function(e){return t[e]}))}(e)).replace(/\{\{descr\}\}/g,i.$caption?encodeURIComponent(i.$caption.text()):""),t.fancybox.open({src:i.translate(i,n),type:"html",opts:{touch:!1,animationEffect:!1,afterLoad:function(e,t){i.$refs.container.one("beforeClose.fb",(function(){e.close(null,0)})),t.$content.find(".fancybox-share__button").click((function(){return window.open(this.href,"Share","width=550, height=450"),!1}))},mobile:{autoFocus:!1}}}))}))}(document,jQuery),function(e,t,n){"use strict";function i(){var t=e.location.hash.substr(1),n=t.split("-"),i=n.length>1&&/^\+?\d+$/.test(n[n.length-1])&&parseInt(n.pop(-1),10)||1;return{hash:t,index:i<1?1:i,gallery:n.join("-")}}function o(e){""!==e.gallery&&n("[data-fancybox='"+n.escapeSelector(e.gallery)+"']").eq(e.index-1).focus().trigger("click.fb-start")}function r(e){var t,n;return!!e&&(""!==(n=(t=e.current?e.current.opts:e.opts).hash||(t.$orig?t.$orig.data("fancybox")||t.$orig.data("fancybox-trigger"):""))&&n)}n.escapeSelector||(n.escapeSelector=function(e){return(e+"").replace(/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g,(function(e,t){return t?"\0"===e?"�":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e}))}),n((function(){!1!==n.fancybox.defaults.hash&&(n(t).on({"onInit.fb":function(e,t){var n,o;!1!==t.group[t.currIndex].opts.hash&&(n=i(),(o=r(t))&&n.gallery&&o==n.gallery&&(t.currIndex=n.index-1))},"beforeShow.fb":function(n,i,o,a){var s;o&&!1!==o.opts.hash&&(s=r(i))&&(i.currentHash=s+(i.group.length>1?"-"+(o.index+1):""),e.location.hash!=="#"+i.currentHash&&(a&&!i.origHash&&(i.origHash=e.location.hash),i.hashTimer&&clearTimeout(i.hashTimer),i.hashTimer=setTimeout((function(){"replaceState"in e.history?(e.history[a?"pushState":"replaceState"]({},t.title,e.location.pathname+e.location.search+"#"+i.currentHash),a&&(i.hasCreatedHistory=!0)):e.location.hash=i.currentHash,i.hashTimer=null}),300)))},"beforeClose.fb":function(n,i,o){o&&!1!==o.opts.hash&&(clearTimeout(i.hashTimer),i.currentHash&&i.hasCreatedHistory?e.history.back():i.currentHash&&("replaceState"in e.history?e.history.replaceState({},t.title,e.location.pathname+e.location.search+(i.origHash||"")):e.location.hash=i.origHash),i.currentHash=null)}}),n(e).on("hashchange.fb",(function(){var e=i(),t=null;n.each(n(".fancybox-container").get().reverse(),(function(e,i){var o=n(i).data("FancyBox");if(o&&o.currentHash)return t=o,!1})),t?t.currentHash===e.gallery+"-"+e.index||1===e.index&&t.currentHash==e.gallery||(t.currentHash=null,t.close()):""!==e.gallery&&o(e)})),setTimeout((function(){n.fancybox.getInstance()||o(i())}),50))}))}(window,document,jQuery),function(e,t){"use strict";var n=(new Date).getTime();t(e).on({"onInit.fb":function(e,t,i){t.$refs.stage.on("mousewheel DOMMouseScroll wheel MozMousePixelScroll",(function(e){var i=t.current,o=(new Date).getTime();t.group.length<2||!1===i.opts.wheel||"auto"===i.opts.wheel&&"image"!==i.type||(e.preventDefault(),e.stopPropagation(),i.$slide.hasClass("fancybox-animated")||(e=e.originalEvent||e,o-n<250||(n=o,t[(-e.deltaY||-e.deltaX||e.wheelDelta||-e.detail)<0?"next":"previous"]())))}))}})}(document,jQuery),function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.ClipboardJS=t():e.ClipboardJS=t()}(this,(function(){return n={},e.m=t=[function(e,t){e.exports=function(e){var t;if("SELECT"===e.nodeName)e.focus(),t=e.value;else if("INPUT"===e.nodeName||"TEXTAREA"===e.nodeName){var n=e.hasAttribute("readonly");n||e.setAttribute("readonly",""),e.select(),e.setSelectionRange(0,e.value.length),n||e.removeAttribute("readonly"),t=e.value}else{e.hasAttribute("contenteditable")&&e.focus();var i=window.getSelection(),o=document.createRange();o.selectNodeContents(e),i.removeAllRanges(),i.addRange(o),t=i.toString()}return t}},function(e,t){function n(){}n.prototype={on:function(e,t,n){var i=this.e||(this.e={});return(i[e]||(i[e]=[])).push({fn:t,ctx:n}),this},once:function(e,t,n){var i=this;function o(){i.off(e,o),t.apply(n,arguments)}return o._=t,this.on(e,o,n)},emit:function(e){for(var t=[].slice.call(arguments,1),n=((this.e||(this.e={}))[e]||[]).slice(),i=0,o=n.length;i{e.target===this.circle||e.target===this.circleText?this.showGif():this.isPlaying&&(e.target===this.circle||e.target===this.circleText||e.target===this.container||e.target===this.image)&&this.hideGif()}))):this.element.onload=()=>{this.gif=this.element.getAttribute("data-gif"),this.alt=this.element.getAttribute("alt"),this.src=this.element.src,this.classN=this.element.className,this.imageId=this.element.id,this.circle=this.generateCircle(),this.circleText=this.circle.children[0],this.image=this.generateImage(),this.container=this.generateContainer(),this.parentElement=this.element.parentElement,this.container.appendChild(this.image),this.container.appendChild(this.circle),this.loaded=!1,this.blobURL,this.isPlaying=!1,this.parentElement.replaceChild(this.container,this.element),this.parentElement.addEventListener("click",(e=>{e.target===this.circle||e.target===this.circleText?this.showGif():this.isPlaying&&(e.target===this.circle||e.target===this.circleText||e.target===this.container||e.target===this.image)&&this.hideGif()}))}};gifsee.prototype.generateCircle=function(){var e=document.createElement("div");e.className="gifsee-circle";var t=document.createElement("span");return t.className="text",e.appendChild(t),e},gifsee.prototype.generateContainer=function(){var e=document.createElement("div");return e.className="gifsee-wrapper",e},gifsee.prototype.generateImage=function(){var e=document.createElement("img");return e.className=this.classN,this.imageId&&(e.id=this.imageId),e.src=this.src,e.setAttribute("data-gif-location",this.gif),e.setAttribute("alt",this.alt),e},gifsee.prototype.showGif=function(){this.loaded?(this.image.src=this.blobURL,this.circle.style.display="none",this.isPlaying=!0):this.fetchGif().then((e=>{this.blobURL=URL.createObjectURL(e),this.loaded=!0,this.circle.classList.remove("loading"),this.image.src=this.blobURL,this.circle.style.display="none",this.isPlaying=!0}))},gifsee.prototype.fetchGif=function(){return this.circle.classList.add("loading"),new Promise(((e,t)=>{fetch(this.gif).then((function(n){n.blob().then((function(t){e(t)})).catch((function(e){t(e)}))})).catch((function(e){t(e)}))}))},gifsee.prototype.hideGif=function(){this.image.src=this.src,this.circle.style.display="block",this.isPlaying=!1},$(document).ready((function(){function e(){var e=new ClipboardJS(".btn-clipboard",{target:function(e){return e.parentNode.previousElementSibling}});e.on("success",(function(e){$(e.trigger).text("Copied!"),setTimeout((function(){$(e.trigger).html(' ')}),1e3),e.clearSelection()})),e.on("error",(function(e){var t="Press "+(/Mac/i.test(navigator.userAgent)?"⌘":"Ctrl-")+"C to copy";$(e.trigger).text(t)}))}var t,n;if(function(t){hljs.highlightAll(),t(e),$("body").append('
')}((function(e){$(".hljs").parent().wrap('
'),$(".hljs-wrapper").append('
'),$(".hljs-wrapper .hljs-actions-panel").prepend(' '),$(".hljs-wrapper .hljs-actions-panel").prepend(' '),e()})),t=!1,n=$(".js-fullscreen-code")[0],$("body").on("click",".btn-fullscreen-mode",(function(){if(t)$("body").css("overflow",""),$(n).removeClass("is-open").empty(),t=!1;else{var e=this.parentNode.parentNode.cloneNode(!0);$("body").css("overflow","hidden"),$(n).append(e),$(n).find(".btn-fullscreen-mode").attr("title","Leave fullscreen mode"),$(n).find(".btn-fullscreen-mode i").removeClass("fa-expand").addClass("fa-compress"),$(n).addClass("is-open"),t=!0}})),$(document).keyup((function(e){$(n).hasClass("is-open")&&"Escape"===e.key&&($("body").css("overflow",""),$(n).removeClass("is-open").empty(),t=!1)})),$(".gif").each((function(e){new gifsee(this)})),$("table").addClass("table"),$("table thead").addClass("thead-light"),$("#show-sidebar").on("click",(function(){$("body").addClass("sidebar-open"),$(".sidebar").addClass("sidebar-active")})),$(".main-nav .li_dropdown .li_parent").on("click",(function(){$(this).parent().toggleClass("active")})),$(document).on("click",".sidebar-open",(function(e){$(e.target).hasClass("sidebar-open")&&($("body").removeClass("sidebar-open"),$(".sidebar").removeClass("sidebar-active"))})),$("#back-to-top").on("click",(function(){$("html, body").animate({scrollTop:0},800)})),$(".main-content-body h2, .main-content-body h3, .main-content-body h4, .main-content-body h5, .main-content-body h6").each((function(){window.location.hash.replace("#","");var e=$(this).attr("id"),t=$(this).text();$(this).empty(),"undefined"!==e&&$(this).append(''+t+' ')})),$(".main-content-body h2 > a, .main-content-body h3 > a, .main-content-body h4 > a, .main-content-body h5 > a, .main-content-body h6 > a").on("click",(function(){$("html, body").animate({scrollTop:$(this).offset().top-80},500)})),$(window).on("load",(function(){var e=window.location.hash.replace("#","");e&&$(".main-content-body h2, .main-content-body h3, .main-content-body h4, .main-content-body h5, .main-content-body h6").each((function(){var t=$(this).attr("id");if("undefined"!==t&&e===t)return $("html, body").animate({scrollTop:$(this).offset().top-80},100),!1}))})),$(".toc").length){var i=$(' Contents ');$(".toc").prepend(i),$(".toc a").on("click",(function(){var e=$(this.hash);if($(".nav-tabs").length&&$(".tab-pane "+this.hash).length){var t=$(this.hash)[0].closest(".tab-pane"),n=$(t).attr("id");$('a[href="#'+n+'"]').tab("show")}$("html, body").animate({scrollTop:e.offset().top-80},500)}))}if(0!=$("#search").length){$.getJSON("/search/search_index.json").done((function(e){var t=$("#searchList"),n=new Fuse(e.docs,{shouldSort:!0,tokenize:!0,threshold:0,location:0,distance:100,maxPatternLength:32,minMatchCharLength:1,ignoreLocation:!0,keys:["title","text"]});$("#search").on("keyup",(function(){if(this.value){var e=n.search(this.value).filter((function(e){return null===e.item.location.match(/(\/#)/g)}));0===e.length?t.html(""):(t.html(""),t.append("")),e.forEach((function(e){$("#searchList ul").append(""+e.item.title+" ")}))}else $("#searchList").empty()}))}))}function o(){$(window).scrollTop()<10?$(".navbar-dark").removeClass("dark-mode"):$(".navbar-dark").addClass("dark-mode")}$(".main-content-body img").each((function(){$(this).hasClass("no-lightbox")||$(this).hasClass("gif")||$(this).hasClass("emojione")||$(this).parent("a").length||$(this).wrap((function(){return" "}))})),$(window).scroll((function(){o()})),o(),$("#currentYear").text((new Date).getFullYear()),document.querySelector(".bug-head").addEventListener("click",(function(){throw new Error("Headshot")})),$('a.nav-link[data-toggle="tab"]').on("click",(function(e){var t=e.currentTarget.closest("ul.nav-tabs"),n=e.currentTarget.dataset.tab,i=$('a.nav-link[data-toggle="tab"][data-tab="'+n+'"]').filter((function(e,n){return $(n).closest("ul.nav-tabs").not(t)[0]}));if(i.length>0){var o=$(e.currentTarget).offset().top-$(document).scrollTop();$(i).tab("show"),$(document).scrollTop($(e.currentTarget).offset().top-o)}}))})),window.intercomSettings={app_id:"i2hhgdvj",system:"elmah.io",background_color:"#0da58e"},function(e){var t="undefined"!==e.app_id?e.app_id:"",n=void 0!==e.background_color?e.background_color:"#333333";if(t){var i=function(e,t=null,n=null){var i=document.createElement("div");return Object.keys(e).forEach((function(t){i.style[t]=e[t]})),t&&i.setAttribute("id",t),i.innerHTML=n,i},o=function(e){if(!window.Intercom){var n=window,i=n.Intercom;if("function"==typeof i)i("reattach_activator"),i("update",n.intercomSettings);else{var o=document,s=function(){s.c(arguments)};s.q=[],s.c=function(e){s.q.push(e)},n.Intercom=s;var l=function(){var e=o.createElement("script");e.type="text/javascript",e.async=!0,e.src="https://widget.intercom.io/widget/"+t+"/";var n=o.getElementsByTagName("script")[0];n.parentNode.insertBefore(e,n)}}l()}e&&(r.style.opacity="0",a.style.opacity="1",a.style.transform="rotate(0deg)",window.Intercom("show"));var c=0,u=setInterval((function(){c++,window.Intercom.booted?(null!==document.querySelector("#intercom-facade-btn")&&document.querySelector("#intercom-facade-btn").remove(),clearInterval(u)):c>10&&clearInterval(u)}),1e3);return!0},r=i({display:"flex",WebkitBoxAlign:"center",alignItems:"center",WebkitBoxPack:"center",justifyContent:"center",position:"absolute",top:"0px",bottom:"0px",width:"100%",transform:"rotate(0deg) scale(1)",transition:"transform 0.16s linear 0s, opacity 0.08s linear 0s"},null,'\n\n \n \n'),a=i({display:"flex",WebkitBoxAlign:"center",alignItems:"center",WebkitBoxPack:"center",justifyContent:"center",position:"absolute",top:"0px",bottom:"0px",width:"100%",transition:"transform 0.16s linear 0s, opacity 0.08s linear 0s",opacity:"0",transform:"rotate(-30deg)"},null,'\n\n \n \n \n'),s=i({position:"absolute",top:"0px",left:"0px",width:"48px",height:"48px",borderRadius:"50%",cursor:"pointer",transformOrigin:"center",overflowX:"hidden",overflowY:"hidden",WebkitBackfaceVisibility:"hidden",WebkitFontSmoothing:"antialiased"}),l=i({fontFamily:"intercom-font, 'Helvetica Neue', 'Apple Color Emoji', Helvetica, Arial, sans-serif",fontSize:"100%",fontStyle:"normal",letterSpacing:"normal",fontStretch:"normal",fontVariantLigatures:"normal",fontVariantCaps:"normal",fontVariantEastAsian:"normal",fontVariantPosition:"normal",fontWeight:"normal",textAlign:"left",textDecorationLine:"none",textDecorationStyle:"initial",textDecorationColor:"initial",textDecoration:"none",textIndent:"0px",textShadow:"none",textTransform:"none",boxSizing:"content-box",WebkitTextEmphasisStyle:"none",WebkitTextEmphasisColor:"initial",WebkitFontSmoothing:"antialiased",lineHeight:1}),c=i({zIndex:2147483004,position:"fixed",bottom:"20px",display:"block",right:"20px",width:"48px",height:"48px",borderRadius:"50%",boxShadow:"rgba(0, 0, 0, 0.0588235) 0px 1px 6px 0px, rgba(0, 0, 0, 0.156863) 0px 2px 32px 0px",backgroundColor:n},"intercom-facade-btn");s.append(r),s.append(a),l.append(s),l.addEventListener("click",(function(){o(!0)})),l.addEventListener("mouseenter",(function(){o(!1)})),c.append(l),document.querySelector("body").append(c),void 0!==e.custom_launcher_selector&&document.querySelectorAll(e.custom_launcher_selector).forEach((function(e){e.addEventListener("click",(function(e){e.preventDefault(),o(!0)}))}))}}(window.intercomSettings);
\ No newline at end of file
diff --git a/assets/js/popper.min.js b/assets/js/popper.min.js
new file mode 100644
index 0000000000..6040f6c006
--- /dev/null
+++ b/assets/js/popper.min.js
@@ -0,0 +1,4 @@
+/*
+ Copyright (C) Federico Zivolo 2020
+ Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
+ */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function i(e){return e&&e.referenceNode?e.referenceNode:e}function r(e){return 11===e?re:10===e?pe:re||pe}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),ae({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=le({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f]),E=parseFloat(w['border'+f+'Width']),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},ae(n,m,$(v)),ae(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ce.FLIP:p=[n,i];break;case ce.CLOCKWISE:p=G(n);break;case ce.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=le({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!fe),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=le({},E,e.attributes),e.styles=le({},m,e.styles),e.arrowStyles=le({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ge});
diff --git a/authentication/index.html b/authentication/index.html
new file mode 100644
index 0000000000..c296162352
--- /dev/null
+++ b/authentication/index.html
@@ -0,0 +1,658 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Authentication on elmah.io
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Authentication
+
All of our integrations communicates with the elmah.io API. In order to request endpoints on the API, each client will need to provide a valid API key. API keys are available on the Organization Settings view, as well as on the Install tab on the Log Settings screen. We wrote a guide to help you find your API key here: Where is my API key? . A default API key is created when you create your organization, but new keys can be added, keys revoked, and more.
+
Sending the API key to the elmah.io API, is typically handled by the Elmah.Io.Client
NuGet package. All integrations have a dependency to this package, which means that it will be automatically installed through NuGet. How you provide your API key depends on the integration you are installing. Some integrations expect the API key in a config file, while others, accept the key in C#. For details about how to provide the API key for each integration, click the various installation guides in the left menu.
+
Besides a unique string representing an API key, each key can have a set of permissions. As default, API keys only have the Write Messages permission, which means that the key cannot be used to read data from your logs. In 99% of all scenarios, you will browse through errors using the elmah.io UI, which will require you to sign in using username/password or one of the supported social providers. In the case you want to enable one of the native UIs provided by different integrations (like the /elmah.axd
endpoint part of the ELMAH
package) or you are building a third-party integration to elmah.io, you will need to assign additional permissions to your API key. For details about API key permissions, check out How to configure API key permissions .
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bot-detection/index.html b/bot-detection/index.html
new file mode 100644
index 0000000000..03878e056e
--- /dev/null
+++ b/bot-detection/index.html
@@ -0,0 +1,672 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Bot detection - Machine learning will help you identify bot errors
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Bot detection
+
elmah.io can help you with classifying log messages generated by bots and crawlers. When storing a log message, we run a range of checks to try and identify if a log message is generated by an automated script or a real human visitor of your website/application. In this case, a flag named isBot
is set to true
on the message. In case we couldn't identify a log message as generated by a bot, the flag is set to false
.
+
Besides an automated check, you can also mark a log message as generated by a bot manually. This is done from within the elmah.io UI:
+
+
The benefit of marking log messages with the isBot
flag manually is that elmah.io will then automatically mark new instances of this log message with isBot=true
(this feature is available for automatically bot-marked log messages as well). By doing so you get the possibilities listed later in this article.
+
Log messages marked as generated by bots include a small robot icon on the search result:
+
+
Search by or not by bots
+
If you want to show all log messages generated by bots you can create a search query like this:
+
isBot:true
+
+
Or include a search filter for the Is Bot field:
+
+
By reversing the filter, you see a list of log messages NOT generated by a bot, which can make it easier to get an overview of "real" errors.
+
Hide or ignore log messages generated by bots
+
By using the isBot
field in Hide and Ignore filters, you can let elmah.io automatically hide or ignore future log messages generated by bots. Be aware that creating ignore filters based on the isBot
field is only available for users on the Enterprise plan.
+
To create a new filter, navigate to the Rules tab on log settings, and click the Add a new rule button. Input a query including the isBot
field and select either the Hide or Ignore action:
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cli-clear/index.html b/cli-clear/index.html
new file mode 100644
index 0000000000..9704c7376b
--- /dev/null
+++ b/cli-clear/index.html
@@ -0,0 +1,683 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Clearing log messages from the CLI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Clearing log messages from the CLI
+
The clear
command is used to delete one or more messages from a log.
+
+Be aware that clearing a log does not reset the monthly counter towards log messages included in your current plan. The clear
command is intended for cleanup in non-expired log messages you no longer need.
+
+
Usage
+
> elmahio clear --help
+
+Description:
+ Delete one or more messages from a log
+
+Usage:
+ elmahio clear [options]
+
+Options:
+ --apiKey <apiKey> (REQUIRED) An API key with permission to execute the command
+ --logId <logId> (REQUIRED) The log ID of the log to clear messages
+ --query <query> (REQUIRED) Clear messages matching this query (use * for all messages)
+ --from <from> Optional date and time to clear messages from
+ --to <to> Optional date and time to clear messages to
+ -?, -h, --help Show help and usage information
+
+
Examples
+
Simple:
+
elmahio clear --apiKey API_KEY --logId LOG_ID --query "statusCode:404"
+
+
Full:
+
elmahio clear --apiKey API_KEY --logId LOG_ID --query "statusCode:404" --from 2022-05-17 --to 2022-05-18
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cli-dataloader/index.html b/cli-dataloader/index.html
new file mode 100644
index 0000000000..711cb77828
--- /dev/null
+++ b/cli-dataloader/index.html
@@ -0,0 +1,673 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dataloader loads data from the CLI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Dataloader loads data from the CLI
+
The dataloader
command loads 50 log messages into a specified log.
+
Usage
+
> elmahio dataloader --help
+
+Description:
+ Load 50 log messages into the specified log
+
+Usage:
+ elmahio dataloader [options]
+
+Options:
+ --apiKey <apiKey> (REQUIRED) An API key with permission to execute the command
+ --logId <logId> (REQUIRED) The log ID of the log to import messages into
+ -?, -h, --help Show help and usage information
+
+
Example
+
elmahio dataloader --apiKey API_KEY --logId LOG_ID
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cli-deployment/index.html b/cli-deployment/index.html
new file mode 100644
index 0000000000..1b10f95279
--- /dev/null
+++ b/cli-deployment/index.html
@@ -0,0 +1,682 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create a deployment from the CLI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create a deployment from the CLI
+
The deployment
command is used to create new deployments on elmah.io.
+
Usage
+
> elmahio deployment --help
+
+Description:
+ Create a new deployment
+
+Usage:
+ elmahio deployment [options]
+
+Options:
+ --apiKey <apiKey> (REQUIRED) An API key with permission to execute the command
+ --version <version> (REQUIRED) The version number of this deployment
+ --created <created> When was this deployment created in UTC
+ --description <description> Description of this deployment
+ --userName <userName> The name of the person responsible for creating this deployment
+ --userEmail <userEmail> The email of the person responsible for creating this deployment
+ --logId <logId> The ID of a log if this deployment is specific to a single log
+ -?, -h, --help Show help and usage information
+
+
Examples
+
Simple:
+
elmahio deployment --apiKey API_KEY --version 1.0.0
+
+
Full:
+
elmahio deployment --apiKey API_KEY --version 1.0.0 --created 2022-02-08 --description "My new cool release" --userName "Thomas Ardal" --userEmail "thomas@elmah.io" --logId LOG_ID
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cli-diagnose/index.html b/cli-diagnose/index.html
new file mode 100644
index 0000000000..3f42d4d9aa
--- /dev/null
+++ b/cli-diagnose/index.html
@@ -0,0 +1,677 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Diagnose potential problems with an elmah.io installation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Diagnose potential problems with an elmah.io installation
+
The diagnose
command can be run in the root folder of an elmah.io installation to find potential problems with the configuration.
+
Usage
+
> elmahio diagnose --help
+
+Description:
+ Diagnose potential problems with an elmah.io installation
+
+Usage:
+ elmahio diagnose [options]
+
+Options:
+ --directory <directory> The root directory to check [default: C:\test]
+ --verbose Output verbose diagnostics to help debug problems [default: False]
+ -?, -h, --help Show help and usage information
+
+
Examples
+
Simple:
+
elmahio diagnose --directory c:\projects\my-project
+
+
Full:
+
elmahio diagnose --directory c:\projects\my-project --verbose
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cli-export/index.html b/cli-export/index.html
new file mode 100644
index 0000000000..92f197b1ad
--- /dev/null
+++ b/cli-export/index.html
@@ -0,0 +1,684 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Exporting log messages from the CLI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Exporting log messages from the CLI
+
The export
command is used to export one or more log messages from a log to JSON.
+
Usage
+
> elmahio export --help
+
+Description:
+ Export log messages from a specified log
+
+Usage:
+ elmahio export [options]
+
+Options:
+ --apiKey <apiKey> (REQUIRED) An API key with permission to execute the command
+ --logId <logId> (REQUIRED) The ID of the log to export messages from
+ --dateFrom <dateFrom> (REQUIRED) Defines the Date from which the logs start. Ex. " --dateFrom 2023-03-09"
+ --dateTo <dateTo> (REQUIRED) Defines the Date from which the logs end. Ex. " --dateTo 2023-03-16"
+ --filename <filename> Defines the path and filename of the file to export to. Ex. " --filename
+ C:\myDirectory\myFile.json" [default:
+ C:\Users\thoma\Export-638145521994987555.json]
+ --query <query> Defines the query that is passed to the API [default: *]
+ --includeHeaders Include headers, cookies, etc. in output (will take longer to export)
+ -?, -h, --help Show help and usage information
+
+
Examples
+
Simple:
+
elmahio export --apiKey API_KEY --logId LOG_ID --dateFrom 2020-08-21 --dateTo 2020-08-28
+
+
Full:
+
elmahio export --apiKey API_KEY --logId LOG_ID --dateFrom 2020-08-21 --dateTo 2020-08-28 --filename c:\temp\elmahio.json --query "statusCode: 404" --includeHeaders
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cli-import/index.html b/cli-import/index.html
new file mode 100644
index 0000000000..3ecf261e13
--- /dev/null
+++ b/cli-import/index.html
@@ -0,0 +1,683 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Importing log messages to elmah.io from the CLI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Importing log messages from the CLI
+
The import
command is used to import log messages from IIS log files and W3C Extended log files to an elmah.io log.
+
Usage
+
> elmahio import --help
+
+Description:
+ Import log messages to a specified log
+
+Usage:
+ elmahio import [options]
+
+Options:
+ --apiKey <apiKey> (REQUIRED) An API key with permission to execute the command
+ --logId <logId> (REQUIRED) The ID of the log to import messages to
+ --type <iis|w3c> (REQUIRED) The type of log file to import. Use 'w3c' for W3C Extended Log File Format and
+ 'iis' for IIS Log File Format
+ --filename <filename> (REQUIRED) Defines the path and filename of the file to import from. Ex. " --filename
+ C:\myDirectory\log.txt"
+ --dateFrom <dateFrom> Defines the Date from which the logs start. Ex. " --dateFrom 2023-03-06"
+ --dateTo <dateTo> Defines the Date from which the logs end. Ex. " --dateTo 2023-03-13"
+ -?, -h, --help Show help and usage information
+
+
Examples
+
IIS:
+
elmahio import --apiKey API_KEY --logId LOG_ID --type iis --filename u_inetsv1.log --dateFrom 2023-03-13T10:14 --dateTo 2023-03-13T10:16
+
+
w3c:
+
elmahio import --apiKey API_KEY --logId LOG_ID --type w3c --filename u_extend1.log --dateFrom 2023-03-13T10:14 --dateTo 2023-03-13T10:16
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cli-log/index.html b/cli-log/index.html
new file mode 100644
index 0000000000..e6a4b432d9
--- /dev/null
+++ b/cli-log/index.html
@@ -0,0 +1,714 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Log a message from the CLI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Log a message from the CLI
+
The log
command is used to store a log message in a specified log.
+
Usage
+
> elmahio log --help
+
+Description:
+ Log a message to the specified log
+
+Usage:
+ elmahio log [options]
+
+Options:
+ --apiKey <apiKey> (REQUIRED) An API key with permission to execute the command
+ --logId <logId> (REQUIRED) The ID of the log to send the log message to
+ --application <application> Used to identify which application logged this message. You can use this if you have
+ multiple applications and services logging to the same log
+ --detail <detail> A longer description of the message. For errors this could be a stacktrace, but it's
+ really up to you what to log in there.
+ --hostname <hostname> The hostname of the server logging the message.
+ --title <title> (REQUIRED) The textual title or headline of the message to log.
+ --titleTemplate <titleTemplate> The title template of the message to log. This property can be used from logging
+ frameworks that supports structured logging like: "{user} says {quote}". In the
+ example, titleTemplate will be this string and title will be "Gilfoyle says It's not
+ magic. It's talent and sweat".
+ --source <source> The source of the code logging the message. This could be the assembly name.
+ --statusCode <statusCode> If the message logged relates to a HTTP status code, you can put the code in this
+ property. This would probably only be relevant for errors, but could be used for
+ logging successful status codes as well.
+ --dateTime <dateTime> The date and time in UTC of the message. If you don't provide us with a value in
+ dateTime, we will set the current date and time in UTC.
+ --type <type> The type of message. If logging an error, the type of the exception would go into
+ type but you can put anything in there, that makes sense for your domain.
+ --user <user> An identification of the user triggering this message. You can put the users email
+ address or your user key into this property.
+ --severity <severity> An enum value representing the severity of this message. The following values are
+ allowed: Verbose, Debug, Information, Warning, Error, Fatal.
+ --url <url> If message relates to a HTTP request, you may send the URL of that request. If you
+ don't provide us with an URL, we will try to find a key named URL in
+ serverVariables.
+ --method <method> If message relates to a HTTP request, you may send the HTTP method of that request.
+ If you don't provide us with a method, we will try to find a key named
+ REQUEST_METHOD in serverVariables.
+ --version <version> Versions can be used to distinguish messages from different versions of your
+ software. The value of version can be a SemVer compliant string or any other syntax
+ that you are using as your version numbering scheme.
+ --correlationId <correlationId> CorrelationId can be used to group similar log messages together into a single
+ discoverable batch. A correlation ID could be a session ID from ASP.NET Core, a
+ unique string spanning multiple microsservices handling the same request, or
+ similar.
+ --category <category> The category to set on the message. Category can be used to emulate a logger name
+ when created from a logging framework.
+ -?, -h, --help Show help and usage information
+
+
Examples
+
Simple:
+
elmahio log --apiKey API_KEY --logId LOG_ID --title "An error happened"
+
+
Full:
+
elmahio log --apiKey API_KEY --logId LOG_ID --title "An error happened" --application "My app" --details "Some details" --hostname "localhost" --titleTemplate "An {severity} happened" --source "A source" --statusCode 500 --dateTime 2022-01-13 --type "The type" --user "A user" --severity "Error" --url "https://elmah.io" --method "GET" --version "1.0.0"
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cli-overview/index.html b/cli-overview/index.html
new file mode 100644
index 0000000000..aad8f20f72
--- /dev/null
+++ b/cli-overview/index.html
@@ -0,0 +1,795 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ CLI overview - Automate repeating tasks with the elmah.io CLI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
CLI overview
+
The elmah.io CLI lets you execute common tasks against elmah.io.
+
Installing the CLI
+
+The elmah.io CLI requires .NET 6 or newer installed.
+
+
The elmah.io CLI can be installed in several ways. To set up everything automatically, execute the following script from the command line:
+
dotnet tool install --global Elmah.Io.Cli
+
+
or make sure to run on the latest version if you already have the CLI installed:
+
dotnet tool update --global Elmah.Io.Cli
+
+
If you prefer downloading the CLI as a zip you can download the latest version from GitHub . To clone and build the CLI manually, check out the instructions below.
+
Run the CLI
+
+
+
Run the CLI to get help:
+
elmahio --help
+
+
Help similar to this is outputted to the console:
+
elmahio:
+ CLI for executing various actions against elmah.io
+
+Usage:
+ elmahio [options] [command]
+
+Options:
+ --nologo Doesn't display the startup banner or the copyright message
+ --version Show version information
+ -?, -h, --help Show help and usage information
+
+Commands:
+ clear Delete one or more messages from a log
+ dataloader Load 50 log messages into the specified log
+ deployment Create a new deployment
+ diagnose Diagnose potential problems with an elmah.io installation
+ export Export log messages from a specified log
+ import Import log messages to a specified log
+ log Log a message to the specified log
+ sourcemap Upload a source map and minified JavaScript
+ tail Tail log messages from a specified log
+
+
Cloning the CLI
+
Create a new folder and git clone
the repository:
+
git clone https://github.com/elmahio/Elmah.Io.Cli.git
+
+
Building the CLI
+
Navigate to the root repository of the code and execute the following command:
+
dotnet build
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cli-sourcemap/index.html b/cli-sourcemap/index.html
new file mode 100644
index 0000000000..3396032d3d
--- /dev/null
+++ b/cli-sourcemap/index.html
@@ -0,0 +1,679 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Upload source maps from the CLI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Upload a source map from the CLI
+
The sourcemap
command is used to upload source maps and minified JavaScript files to elmah.io.
+
Usage
+
> elmahio sourcemap --help
+
+Description:
+ Upload a source map and minified JavaScript
+
+Usage:
+ elmahio sourcemap [options]
+
+Options:
+ --apiKey <apiKey> (REQUIRED) An API key with permission to execute the command
+ --logId <logId> (REQUIRED) The ID of the log which should contain the minified JavaScript
+ and source map
+ --path <path> (REQUIRED) An URL to the online minified JavaScript file
+ --sourceMap <sourceMap> (REQUIRED) The source map file. Only files with an extension of .map and
+ content type of application/json will be accepted
+ --minifiedJavaScript <minifiedJavaScript> (REQUIRED) The minified JavaScript file. Only files with an extension of
+ .js and content type of text/javascript will be accepted
+ -?, -h, --help Show help and usage information
+
+
Examples
+
sourcemap --apiKey API_KEY --logId LOG_ID --path "/bundles/sharedbundle.min.js" --sourceMap "c:\path\to\sharedbundle.map" --minifiedJavaScript "c:\path\to\sharedbundle.min.js"
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cli-tail/index.html b/cli-tail/index.html
new file mode 100644
index 0000000000..07100fdce1
--- /dev/null
+++ b/cli-tail/index.html
@@ -0,0 +1,673 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tail log messages from the CLI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Tail log messages from the CLI
+
The tail
command is used to tail log messages in a specified log.
+
Usage
+
> elmahio tail --help
+
+Description:
+ Tail log messages from a specified log
+
+Usage:
+ elmahio tail [options]
+
+Options:
+ --apiKey <apiKey> (REQUIRED) An API key with permission to execute the command
+ --logId <logId> (REQUIRED) The ID of the log to send the log message to
+ -?, -h, --help Show help and usage information
+
+
Example
+
elmahio tail --apiKey API_KEY --logId LOG_ID
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/configure-elmah-io-from-code/index.html b/configure-elmah-io-from-code/index.html
new file mode 100644
index 0000000000..c6a79eaa67
--- /dev/null
+++ b/configure-elmah-io-from-code/index.html
@@ -0,0 +1,694 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Configure elmah.io from code
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
You typically configure elmah.io in your web.config
file. With a little help from some custom code, you will be able to configure everything in code as well:
+
using Elmah;
+using System.Collections.Generic;
+using System.ComponentModel.Design;
+
+[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(ElmahFromCodeExample.ElmahConfig), "Start")]
+
+namespace ElmahFromCodeExample
+{
+ public static class ElmahConfig
+ {
+ public static void Start()
+ {
+ ServiceCenter.Current = CreateServiceProviderQueryHandler(ServiceCenter.Current);
+ HttpApplication.RegisterModule(typeof(ErrorLogModule));
+ }
+
+ private static ServiceProviderQueryHandler CreateServiceProviderQueryHandler(ServiceProviderQueryHandler sp)
+ {
+ return context =>
+ {
+ var container = new ServiceContainer(sp(context));
+
+ var config = new Dictionary<string, string>();
+ config["apiKey"] = "API_KEY";
+ config["logId"] = "LOG_ID";
+ var log = new Elmah.Io.ErrorLog(config);
+
+ container.AddService(typeof(Elmah.ErrorLog), log);
+ return container;
+ };
+ }
+ }
+}
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with a log ID (Where is my log ID? ).
+
Let's look at the code. Our class ElmahConfig
is configured as a PreApplicationStartMethod
which means, that ASP.NET (MVC) will execute the Start method when the web application starts up. Inside this method, we set the ServiceCenter.Current
property to the return type of the CreateServiceProviderQueryHandler
method. This method is where the magic happens. Besides creating the new ServiceContainer
, we created the Elmah.Io.ErrorLog
class normally configured through XML. The Dictionary should contain the API key and log ID as explained earlier.
+
In the second line of the Start
-method, we call the RegisterModule
-method with ErrorLogModule
as parameter. This replaces the need for registering the module in web.config
as part of the system.webServer
element.
+
That's it! You no longer need the <elmah>
element, config sections, or anything else related to ELMAH and elmah.io in your web.config
file.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/configure-elmah-io-manually/index.html b/configure-elmah-io-manually/index.html
new file mode 100644
index 0000000000..c87a9d4992
--- /dev/null
+++ b/configure-elmah-io-manually/index.html
@@ -0,0 +1,738 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Configure elmah.io manually
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
The Elmah.Io NuGet package normally adds all of the necessary configuration, to get up and running with elmah.io. This is one of our killer features and our customers tell us, that we have the simplest installer on the market. In some cases, you may experience problems with the automatic configuration, though. Different reasons can cause the configuration not to be added automatically. The most common reason is restrictions to executing PowerShell inside Visual Studio.
+
Start by installing the Elmah.Io
package:
+
Install-Package Elmah.Io
+
dotnet add package Elmah.Io
+
<PackageReference Include="Elmah.Io" Version="5.*" />
+
+
If a dialog is shown during the installation, input your API key (Where is my API key? ) and log ID (Where is my log ID? ). Don't worry if the configuration isn't added, since we will verify this later.
+
Add the following to the <configSections>
element in your web.config
:
+
<sectionGroup name="elmah">
+ <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
+ <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
+ <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
+ <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
+</sectionGroup>
+
+
Add the following to the <httpModules>
element (inside <system.web>
) in your web.config
:
+
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
+<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
+<add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah"/>
+
+
Add the following to the <modules>
element (inside <system.webServer>
) in your web.config
:
+
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
+<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
+<add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
+
+
Add the following to the system.webServer
element in your web.config
:
+
<validation validateIntegratedModeConfiguration="false" />
+
+
Add the following as a root element beneath the <configuration>
element in your web.config
:
+
<elmah>
+ <security allowRemoteAccess="false" />
+ <errorLog type="Elmah.Io.ErrorLog, Elmah.Io" apiKey="API_KEY" logId="LOG_ID" />
+</elmah>
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with your log ID (Where is my log ID? ).
+
That's it. You managed to install elmah.io manually and you should go to your LinkedIn profile and update with a new certification called "Certified elmah.io installer" :)
+
Here's a full example of ELMAH configuration in a web.config
file:
+
<configuration>
+ <configSections>
+ <sectionGroup name="elmah">
+ <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
+ <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
+ <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
+ <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
+ </sectionGroup>
+ </configSections>
+ <system.web>
+ <httpModules>
+ <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
+ <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
+ <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah"/>
+ </httpModules>
+ </system.web>
+ <system.webServer>
+ <validation validateIntegratedModeConfiguration="false" />
+ <modules>
+ <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
+ <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
+ <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
+ </modules>
+ </system.webServer>
+ <elmah>
+ <security allowRemoteAccess="false" />
+ <errorLog type="Elmah.Io.ErrorLog, Elmah.Io" apiKey="API_KEY" logId="LOG_ID" />
+ </elmah>
+</configuration>
+
+
In case you need to access your error log on /elmah.axd
, you need to add the following to the <configuration>
element in your web.config
:
+
<location path="elmah.axd" inheritInChildApplications="false">
+ <system.web>
+ <httpHandlers>
+ <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
+ </httpHandlers>
+ </system.web>
+ <system.webServer>
+ <handlers>
+ <add name="ELMAH" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" preCondition="integratedMode" />
+ </handlers>
+ </system.webServer>
+</location>
+
+
We don't recommend browsing your error logs through the /elmah.axd
endpoint. The elmah.io UI will let you control different levels of access and more.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/create-deployments-from-atlassian-bamboo/index.html b/create-deployments-from-atlassian-bamboo/index.html
new file mode 100644
index 0000000000..c46ddfe4ba
--- /dev/null
+++ b/create-deployments-from-atlassian-bamboo/index.html
@@ -0,0 +1,681 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create deployments from Atlassian Bamboo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create deployments from Atlassian Bamboo
+
Setting up elmah.io Deployment Tracking on Bamboo is easy using a bit of PowerShell.
+
+
+Add a new Script Task and select Windows PowerShell in Interpreter .
+
+
+Select Inline in Script location and add the following PowerShell to Script body :
+
+
+
$ProgressPreference = "SilentlyContinue"
+
+Write-Host $bamboo_buildNumber
+
+$url = "https://api.elmah.io/v3/deployments?api_key=API_KEY"
+$body = @{
+ version = $Env:bamboo_buildNumber
+ logId = "LOG_ID"
+}
+[Net.ServicePointManager]::SecurityProtocol = `
+ [Net.SecurityProtocolType]::Tls12,
+ [Net.SecurityProtocolType]::Tls11,
+ [Net.SecurityProtocolType]::Tls
+Invoke-RestMethod -Method Post -Uri $url -Body $body
+
+
+
Replace API_KEY
and LOG_ID
and everything is configured. The script uses the build number of the current build as version number ($Env:bamboo_buildNumber
). If you prefer another scheme, Bamboo offers a range of variables .
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/create-deployments-from-azure-devops-pipelines/index.html b/create-deployments-from-azure-devops-pipelines/index.html
new file mode 100644
index 0000000000..e33cf8b474
--- /dev/null
+++ b/create-deployments-from-azure-devops-pipelines/index.html
@@ -0,0 +1,733 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create deployments from Azure DevOps Pipelines
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create deployments from Azure DevOps Pipelines
+
+
Notifying elmah.io about new deployments is possible as a build step in Azure DevOps, by adding a bit of PowerShell.
+
Using YAML
+
+
+Edit your build definition YAML file.
+
+
+If not already shown, open the assistant by clicking the Show assistant button.
+
+
+Search for 'powershell'.
+
+
+Click the PowerShell task.
+
+
+Select the Inline radio button and input the following script:
+
+
+
$ProgressPreference = "SilentlyContinue"
+
+$url = "https://api.elmah.io/v3/deployments?api_key=API_KEY"
+$body = @{
+ version = "$env:BUILD_BUILDNUMBER"
+ description = "$env:BUILD_SOURCEVERSIONMESSAGE"
+ userName = "$env:BUILD_REQUESTEDFOR"
+ userEmail = "$env:BUILD_REQUESTEDFOREMAIL"
+ logId = "LOG_ID"
+}
+[Net.ServicePointManager]::SecurityProtocol = `
+ [Net.SecurityProtocolType]::Tls12,
+ [Net.SecurityProtocolType]::Tls11,
+ [Net.SecurityProtocolType]::Tls
+Invoke-RestMethod -Method Post -Uri $url -Body $body
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
(Where is my log ID? ) with the id of the log representing the application deployed by this build configuration.
+
Click the Add button and the new task will be added to your YAML definition. You typically want to move the deployment task to the last placement in tasks .
+
Using Classic editor
+
+
+Edit the build definition currently building your project(s).
+
+
+Click the Add task button and locate the PowerShell task. Click Add .
+
+
+
+Fill in the details as shown in the screenshot.
+
+
+
+
... and here's the code from the screenshot above:
+
$ProgressPreference = "SilentlyContinue"
+
+$url = "https://api.elmah.io/v3/deployments?api_key=API_KEY"
+$body = @{
+ version = "$env:BUILD_BUILDNUMBER"
+ description = "$env:BUILD_SOURCEVERSIONMESSAGE"
+ userName = "$env:BUILD_REQUESTEDFOR"
+ userEmail = "$env:BUILD_REQUESTEDFOREMAIL"
+ logId = "LOG_ID"
+}
+[Net.ServicePointManager]::SecurityProtocol = `
+ [Net.SecurityProtocolType]::Tls12,
+ [Net.SecurityProtocolType]::Tls11,
+ [Net.SecurityProtocolType]::Tls
+Invoke-RestMethod -Method Post -Uri $url -Body $body
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
(Where is my log ID? ) with the id of the log representing the application deployed by this build configuration.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/create-deployments-from-azure-devops-releases/index.html b/create-deployments-from-azure-devops-releases/index.html
new file mode 100644
index 0000000000..7df61d8b3b
--- /dev/null
+++ b/create-deployments-from-azure-devops-releases/index.html
@@ -0,0 +1,670 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create deployments from Azure DevOps Releases
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create deployments from Azure DevOps Releases
+
If you are using Releases in Azure DevOps, you should use our extension to notify elmah.io about new deployments. To install and configure the extension, follow the simple steps below:
+
+Go to the elmah.io Deployment Tasks extension on the Azure DevOps Marketplace and click the Get it free button:
+
+
+
+Select your organization and click the Install button:
+
+
+
+Go to your Azure DevOps project and add the elmah.io Deployment Notification task. Fill in all fields as shown here:
+
+
+
You will need to replace API_KEY
with an API key (Where is my API key? ) with permission (How to configure API key permissions ) to create deployments. If the deployment is specific to a single log, insert a log ID (Where is my log ID? ) with the ID of the log instead of LOG_ID
. Deployments without a log ID will show on all logs in the organization.
+
That's it! Azure DevOps will now notify elmah.io every time the release pipeline is executed.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/create-deployments-from-bitbucket-pipelines/index.html b/create-deployments-from-bitbucket-pipelines/index.html
new file mode 100644
index 0000000000..18d4808874
--- /dev/null
+++ b/create-deployments-from-bitbucket-pipelines/index.html
@@ -0,0 +1,667 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create deployments from Bitbucket Pipelines
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create deployments from Bitbucket Pipelines
+
Pipelines use scripts, embedded in YAML files, to configure the different steps required to build and deploy software. To notify elmah.io as part of a build/deployment, the first you will need to do is to add your API key as a secure environment variable. To do so, go to Settings | Workspace Settings | Workspace variables and add a new variable:
+
+
Where is my API key?
+
Then add a new script to your build YAML-file after building and deploying your software:
+
pipelines:
+ default:
+ - step:
+ script:
+ # ...
+ - curl -X POST -d "{\"version\":\"$BITBUCKET_BUILD_NUMBER\"}" -H "Content-Type:application/json" https://api.elmah.io/v3/deployments?api_key=$ELMAHIO_APIKEY
+
+
The script uses curl
to invoke the elmah.io Deployments endpoint with the API key ($ELMAHIO_APIKEY
) and a version number ($BITBUCKET_BUILD_NUMBER
). The posted JSON can be extended to support additional properties like a changelog and the name of the person triggering the deployment. Check out the API documentation for details.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/create-deployments-from-cli/index.html b/create-deployments-from-cli/index.html
new file mode 100644
index 0000000000..4365fa002d
--- /dev/null
+++ b/create-deployments-from-cli/index.html
@@ -0,0 +1,665 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create deployments from the elmah.io CLI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create deployments from the elmah.io CLI
+
Deployments can be easily created from either the command-line or a build server using the elmah.io CLI. There's a help page dedicated to the deployment command but here's a quick recap.
+
If not already installed, start by installing the elmah.io CLI:
+
dotnet tool install --global Elmah.Io.Cli
+
+
Then, create a new deployment using the deployment
command:
+
elmahio deployment --apiKey API_KEY --version 1.0.0
+
+
In case you are calling the CLI from a build server, you may want to exclude the elmah.io logo and copyright message using the --nologo
parameter to reduce log output and to avoid cluttering the build output:
+
elmahio deployment --nologo --apiKey API_KEY --version 1.0.0
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/create-deployments-from-github-actions/index.html b/create-deployments-from-github-actions/index.html
new file mode 100644
index 0000000000..c29bf94ccf
--- /dev/null
+++ b/create-deployments-from-github-actions/index.html
@@ -0,0 +1,735 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create deployments from GitHub Actions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create deployments from GitHub Actions
+
GitHub Actions is a great platform for building and releasing software. To notify elmah.io when you deploy a new version of your project, you will need an additional step in your build definition. Before you do that, start by creating new secrets:
+
+
+Go to your project on GitHub.
+
+
+Click the Settings tab.
+
+
+Click the Secrets navigation item.
+
+
+Click New repository secret .
+
+
+Name the secret ELMAH_IO_API_KEY
.
+
+
+Insert your elmah.io API key in Value (Where is my API key? ). Make sure to use an API key that includes the Deployments | Write permission (How to configure API key permissions ).
+
+
+Click Add secret
+
+
+Do the same for your elmah.io log ID but name it ELMAH_IO_LOG_ID
(Where is my log ID? ).
+
+
+Insert the following step as the last one in your YAML build specification:
+
+
+
- name: Create Deployment on elmah.io
+ uses: elmahio/github-create-deployment-action@v1
+ with:
+ apiKey: ${{ secrets.ELMAH_IO_API_KEY }}
+ version: ${{ github.run_number }}
+ logId: ${{ secrets.ELMAH_IO_LOG_ID }}
+
+
The configuration will automatically notify elmah.io every time the build script is running. The build number (github.run_number
) is used as the version for this sample, but you can modify this if you prefer another scheme.
+
Here's a full overview of properties:
+
+
+
+Name
+Required
+Description
+
+
+
+
+apiKey
+✔️
+An API key with permission to create deployments.
+
+
+version
+✔️
+The version number of this deployment. The value of version can be a SemVer compliant string or any other syntax that you are using as your version numbering scheme. You can use ${{ github.run_number }}
to use the build number as the version or you can pick another scheme or combine the two.
+
+
+description
+
+Optional description of this deployment. Can be markdown or cleartext. The latest commit message can be used as the description by using ${{ github.event.head_commit.message }}
.
+
+
+userName
+
+The name of the person responsible for creating this deployment. This can be set manually or dynamically using the ${{ github.actor }}
variable.
+
+
+userEmail
+
+The email of the person responsible for creating this deployment. There doesn't seem to be a way to pull the email responsible for triggering the build through variables, why this will need to be set manually.
+
+
+logId
+
+As default, deployments are attached to all logs of the organization. If you want a deployment to attach to a single log only, set this to the ID of that log.
+
+
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/create-deployments-from-kudu/index.html b/create-deployments-from-kudu/index.html
new file mode 100644
index 0000000000..32960f46c5
--- /dev/null
+++ b/create-deployments-from-kudu/index.html
@@ -0,0 +1,693 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create deployments from Kudu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create deployments from Kudu
+
Kudu is the engine behind Git deployments on Microsoft Azure. To create a new elmah.io deployment every time you deploy a new app service to Azure, add a new post-deployment script by navigating your browser to https://yoursite.scm.azurewebsites.net
where yoursite
is the name of your Azure website. Click the Debug console and navigate to site\deployments\tools\PostDeploymentActions
(create it if it doesn't exist).
+
To create the new PowerShell file, write the following in the prompt:
+
touch CreateDeployment.ps1
+
+
With a post-deployment script running inside Kudu, we can to extract some more information about the current deployment. A full deployment PowerShell script for Kudu would look like this:
+
$version = Get-Date -format u
+
+(Get-Content ..\wwwroot\web.config).replace('$version', $version) | Set-Content ..\wwwroot\web.config
+
+$ProgressPreference = "SilentlyContinue"
+
+$commit = [System.Environment]::GetEnvironmentVariable("SCM_COMMIT_MESSAGE");
+$commitId = [System.Environment]::GetEnvironmentVariable("SCM_COMMIT_ID");
+$httpHost = [System.Environment]::GetEnvironmentVariable("HTTP_HOST");
+$deployUrl = "https://$httpHost/api/deployments/$commitId"
+
+$username = "MY_USERNAME"
+$password = "MY_PASSWORD"
+$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
+
+$deployInfo = Invoke-RestMethod -Method Get -Uri $deployUrl -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)}
+
+$url = 'https://api.elmah.io/v3/deployments?api_key=API_KEY'
+$body = @{
+ version = $version
+ description = $commit
+ userName = $deployInfo.author
+ userEmail = $deployInfo.author_email
+}
+
+[Net.ServicePointManager]::SecurityProtocol = `
+ [Net.SecurityProtocolType]::Tls12,
+ [Net.SecurityProtocolType]::Tls11,
+ [Net.SecurityProtocolType]::Tls
+Invoke-RestMethod -Method Post -Uri $url -Body $body
+
+
(replace MY_USERNAME
and MY_PASSWORD
with your Azure deployment credentials and API_KEY
with your elmah.io API key located on your organization settings page)
+
The script generates a new version string from the current date and time. How you want your version string looking, is really up to you. To fetch additional information about the deployment, the Kudu deployments
endpoint is requested with the current commit id. Finally, the script creates the deployment using the elmah.io REST API.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/create-deployments-from-octopus-deploy/index.html b/create-deployments-from-octopus-deploy/index.html
new file mode 100644
index 0000000000..6a35085d0c
--- /dev/null
+++ b/create-deployments-from-octopus-deploy/index.html
@@ -0,0 +1,673 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create deployments from Octopus Deploy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create deployments from Octopus Deploy
+
Notifying elmah.io of a new deployment from Octopus Deploy is supported through a custom step template. The step template can be installed in multiple ways as explained on Community step templates . In this document, the step template will be installed directly from the Process Editor :
+
+
+Go to the Process Editor and click the ADD STEP button. In the Choose Step Template section search for 'elmah.io':
+
+
+
+Hover over the 'elmah.io - Register Deployment' community template and click the INSTALL AND ADD button.
+
+
+In the Install and add modal click the SAVE button.
+
+
+The step template is now added to the process. Fill in your API key (Where is my API key? ) and log ID (Where is my log ID? ) in the step template fields and click the SAVE button:
+
+
+
+
And we're done. On every new deployment, Octopus Deploy will notify elmah.io. In case you want an alternative version naming scheme, the Version field in the step template can be used to change the format.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/create-deployments-from-powershell/index.html b/create-deployments-from-powershell/index.html
new file mode 100644
index 0000000000..b96379fc06
--- /dev/null
+++ b/create-deployments-from-powershell/index.html
@@ -0,0 +1,681 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create deployments from PowerShell
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create deployments from PowerShell
+
If you release your software using a build or deployment server, creating the new release is easy using a bit of PowerShell. To request the deployments
endpoint, write the following PowerShell script:
+
$version = "1.42.7"
+$ProgressPreference = "SilentlyContinue"
+$url = 'https://api.elmah.io/v3/deployments?api_key=API_KEY'
+$body = @{
+ version = $version
+}
+[Net.ServicePointManager]::SecurityProtocol = `
+ [Net.SecurityProtocolType]::Tls12,
+ [Net.SecurityProtocolType]::Tls11,
+ [Net.SecurityProtocolType]::Tls
+Invoke-RestMethod -Method Post -Uri $url -Body $body
+
+
(replace API_KEY
with your API key found on your organization settings page)
+
In the example, a simple version string is sent to the API and elmah.io will automatically put a timestamp on that. Overriding user information and description make the experience within the elmah.io UI better. Pulling release notes and the name and email of the deployer, is usually available through environment variables or similar, depending on the technology used for creating the deployment.
+
Here's an example of a full payload for the create deployment endpoint:
+
$body = @{
+ version = "1.0.0"
+ created = [datetime]::UtcNow.ToString("o")
+ description = "my deployment"
+ userName = "Thomas"
+ userEmail = "thomas@elmah.io"
+ logId = "39e60b0b-21b4-4d12-8f09-81f3642c64be"
+}
+
+
In this example, the deployment belongs to a single log why the logId
property is set. The description
property can be used to include a changelog or similar. Markdown is supported.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/create-deployments-from-umbraco-cloud/index.html b/create-deployments-from-umbraco-cloud/index.html
new file mode 100644
index 0000000000..a2c871b4c1
--- /dev/null
+++ b/create-deployments-from-umbraco-cloud/index.html
@@ -0,0 +1,703 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create deployments from Umbraco Cloud
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Create deployments from Umbraco Cloud
+
Umbraco Cloud uses Azure to host Umbraco websites, so supporting deployment tracking pretty much corresponds to the steps specified in Using Kudu . Navigate to https://your-umbraco-site.scm.s1.umbraco.io
where your-umbraco-site
is the name of your Umbraco site. Click the Debug console link and navigate to site\deployments\tools\PostDeploymentActions\deploymenthooks
(create it if it doesn't exist). Notice the folder deploymenthooks
, which is required for your scripts to run on Umbraco Cloud.
+
Unlike Kudu, Umbraco Cloud only executes cmd
and bat
files. Create a new cmd
file:
+
touch create-deployment.cmd
+
+
with the following content:
+
echo "Creating elmah.io deployment"
+
+cd %POST_DEPLOYMENT_ACTIONS_DIR%
+
+cd deploymenthooks
+
+powershell -command ". .\create-deployment.ps1"
+
+
The script executes a PowerShell script, which we will create next:
+
touch create-deployment.ps1
+
+
The content of the PowerShell script looks a lot like in Using Kudu , but with some minor tweaks to support Umbraco Cloud:
+
$version = Get-Date -format u
+
+$ProgressPreference = "SilentlyContinue"
+
+$commitId = [System.Environment]::GetEnvironmentVariable("SCM_COMMIT_ID");
+$deployUrl = "https://your-umbraco-site.scm.s1.umbraco.io/api/deployments/$commitId"
+
+$username = "MY_USERNAME"
+$password = "MY_PASSWORD"
+$logId = "LOG_ID"
+$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
+
+$deployInfo = Invoke-RestMethod -Method Get -Uri $deployUrl -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)}
+
+$url = 'https://api.elmah.io/v3/deployments?api_key=API_KEY'
+$body = @{
+ version = $version
+ description = $deployInfo.message
+ userName = $deployInfo.author
+ userEmail = $deployInfo.author_email
+ logId = $logId
+}
+
+[Net.ServicePointManager]::SecurityProtocol = `
+ [Net.SecurityProtocolType]::Tls12,
+ [Net.SecurityProtocolType]::Tls11,
+ [Net.SecurityProtocolType]::Tls
+Invoke-RestMethod -Method Post -Uri $url -Body $body
+
+
Replace your-umbraco-site
with the name of your site, MY_USERNAME
with your Umbraco Cloud username, MY_PASSWORD
with your Umbraco Cloud password, LOG_ID
with the id if the elmah.io log that should contain the deployments (Where is my log ID? ), and finally API_KEY
with your elmah.io API key, found and your organization settings page.
+
There you go. When deploying changes to your Umbraco Cloud site, a new deployment is automatically created on elmah.io.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/creating-rules-to-perform-actions-on-messages/index.html b/creating-rules-to-perform-actions-on-messages/index.html
new file mode 100644
index 0000000000..5deb2fcb8d
--- /dev/null
+++ b/creating-rules-to-perform-actions-on-messages/index.html
@@ -0,0 +1,750 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Creating Rules to Perform Actions on Messages
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
elmah.io comes with a great rule engine for performing various actions when messages are logged in your log.
+
This guide is also available as a short video tutorial here:
+
+
+
+
+
The rule engine is located beneath each log on the log settings page:
+
+
A rule consists of three parts: a title, a query, and an action.
+
The title should be a short text explaining what this rule does. We don't use the title for anything, so please write something that helps you identify rules and to keep them apart.
+
The query should contain either a full-text search string or a Lucene query. When new messages are logged, the message is matched up against all queries registered on that log. If and only if a message matches a query, the action registered on the rule is performed.
+
As mentioned above, the action part of a rule is executed when a message matches the query specified in the same rule. An action can be one of four types: Ignore, Hide, Mail, and HTTP Request. To illustrate how to use each action type, here are four examples of useful rules.
+
Ignore errors with an HTTP status code of 400
+
+Be aware that Ignore rules are only meant as a temporary way of ignoring messages. In case you want to permanently ignore one or more log messages, use client-side filtering as explained in the documentation for each client integration. In addition, there's a client-side filtering help dialog available on the log message details toolbar. Ignoring a large number of messages with Ignore rules will slow down your application logging, use unnecessary network bandwidth, and risk hitting the elmah.io API request limit.
+
+
To ignore all messages with an HTTP status code of 400, you would need to set up the following:
+
+
+
+Title
+Query
+Then
+
+
+
+
+Ignore 400s
+statusCode:400
+Ignore
+
+
+
+
The rule would look like this in the UI:
+
+
Hide warnings
+
To hide all messages with a severity of Warning
, you would need to set up the following:
+
+
+
+Title
+Query
+Then
+
+
+
+
+Hide Warnings
+severity:Warning
+Hide
+
+
+
+
The rule would look like this in the UI:
+
+
Send an email on all messages containing a word
+
To send an email on all messages containing the word billing somewhere, you would need to set up the following:
+
+
+
+Title
+Query
+Then
+
+
+
+
+Mail on billing
+billing
+Email
+
+
+
+
The rule would look like this in the UI:
+
+
Make an HTTP request on all new and fatal messages
+
To make an HTTP request on every new message with a severity of Fatal
, you would need to set up the following:
+
+
+
+Title
+Query
+Then
+
+
+
+
+Request on new fatal
+isNew:true AND severity:Fatal
+HTTP
+
+
+
+
The rule would look like this in the UI:
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-and-custom-errors/index.html b/elmah-and-custom-errors/index.html
new file mode 100644
index 0000000000..176aec9522
--- /dev/null
+++ b/elmah-and-custom-errors/index.html
@@ -0,0 +1,677 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ELMAH and custom errors
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ELMAH and custom errors
+
ELMAH and ASP.NET (MVC) custom errors aren't exactly known to be best friends. Question after question has been posted on forums like Stack Overflow, from people having problems with ELMAH, when custom errors are configured. These problems make perfect sense since both ELMAH and custom errors are designed to catch errors and do something about them.
+
Before looking at some code, we recommend you to read Web.config customErrors element with ASP.NET explained and Demystifying ASP.NET MVC 5 Error Pages and Error Logging . Together, the posts are a great introduction to different ways of implementing custom error pages in ASP.NET MVC.
+
Back to ELMAH. In most implementations of custom error pages, ASP.NET swallows any uncaught exceptions, putting ELMAH out of play. To overcome this issue, you can utilize MVC's IExceptionFilter
to log all exceptions, whether or not it is handled by a custom error page:
+
public class ElmahExceptionLogger : IExceptionFilter
+{
+ public void OnException (ExceptionContext context)
+ {
+ if (context.ExceptionHandled)
+ {
+ ErrorSignal.FromCurrentContext().Raise(context.Exception);
+ }
+ }
+ }
+
+
The OnException
method on ElmahExceptionLogger
is executed every time an error is happening, by registering it in Application_Start
:
+
protected void Application_Start()
+{
+ // ...
+ GlobalConfiguration.Configuration.Filters.Add(new ElmahExceptionLogger());
+ // ...
+}
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-and-elmah-io-differences/index.html b/elmah-and-elmah-io-differences/index.html
new file mode 100644
index 0000000000..6e8e7b36e0
--- /dev/null
+++ b/elmah-and-elmah-io-differences/index.html
@@ -0,0 +1,759 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ELMAH and elmah.io differences
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ELMAH and elmah.io differences
+
We receive a lot of questions like these:
+
+What is the difference between ELMAH and elmah.io?
+I thought ELMAH was free. Why do you suddenly charge?
+My ELMAH SQL Server configuration doesn't work. Why not?
+
+
We understand the confusion. The purpose of this article is to give a bit of background of the differences between ELMAH and elmah.io and why they share similar names.
+
What is ELMAH?
+
ELMAH is an error logging framework originally developed by Atif Aziz able to log all unhandled exceptions from .NET web applications. Errors can be logged to a variety of destinations through ELMAH’s plugin model called error logs. Plugins for XML, SQL Server, MySQL, Elasticsearch, and many more exists. ELMAH automatically collects a lot of information from the HTTP context when logging the error, giving you the possibility to inspect request parameters, cookies, and much more for the failed request. Custom errors can be logged to ELMAH, by manually calling the error log.
+
What is elmah.io?
+
elmah.io is a cloud-based error management system originally developed on top of ELMAH (see history for details). Besides supporting ELMAH, elmah.io also integrates with popular logging frameworks like log4net , NLog , Serilog , and web frameworks like ASP.NET Core . elmah.io offers a superior notification model to ELMAH, with integrations to mail, Slack, Microsoft Teams, and many others. elmah.io also built a lot of features outside the scope of ELMAH, like a complete uptime monitoring system.
+
Comparison
+
+
History
+
So, why name a service elmah.io, when only a minor part of a client integration uses ELMAH? When elmah.io was introduced back in 2013, the intention was to create a cloud-based error logger for ELMAH. We had some simple search and graphing possibilities, but the platform was meant as an alternative to host your own errors logs in SQL Server or similar.
+
In time, elmah.io grew from being a hobby project to an actual company. During those years, we realized that the potential of the platform exceeded the possibilities with ELMAH in many ways. New features not available in ELMAH have been added constantly. A process that would have been nearly impossible with ELMAH's many storage integrations.
+
Today, elmah.io is a full error management system for everything from console applications to web apps and serverless code hosted on Azure or AWS. We've built an entire uptime monitoring system, able to monitor not only if your website fails but also if it even responds to requests.
+
Why not change the name to something else, you may be thinking? That is our wish as well. But changing your SaaS (software-as-a-service) company name isn't exactly easy. We have tried a couple of times, the first time back in 2016. We tried to name the different major features of elmah.io to sea creatures (like Stingray). We failed with the rename and people got confused. In 2017, we started looking at renaming the product again. This time to Unbug. We had learned from our previous mistake and this time silently started changing the name. We quickly realized that the domain change would cause a major risk in regards to SEO (search engine optimization) and confusion.
+
For now, we are elmah.io. The name is not ideal, but it's a lesson learned for another time :)
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-azure-boards/index.html b/elmah-io-apps-azure-boards/index.html
new file mode 100644
index 0000000000..fa72e20c47
--- /dev/null
+++ b/elmah-io-apps-azure-boards/index.html
@@ -0,0 +1,667 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with Azure DevOps Boards
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install Azure Boards App for elmah.io
+
Get your personal access token
+
To create bugs on Azure Boards, you will need to generate a personal access token. Go to Azure DevOps and click the User settings icon in the top right corner. Select the Personal access tokens menu item in the dropdown. Finally, click the New Token button and fill in the details as shown below:
+
+
For this example, we have picked 90 days expiration period, but you can decide on a shorter or longer period if you'd like. Remember to enable the Read & write scope under Work Items . Next, click the Create button and copy the generated token.
+
+Bugs created by elmah.io will have the CreatedBy set to the user generating the personal access token. If you want to identify bugs created by elmah.io, you should create the token from a new user (like elmahio@yourdomain.com).
+
+
Install the Azure Boards App on elmah.io
+
Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Azure Boards app and click the Install button:
+
+
Paste the token copied in the previous step into the Token textbox. In the Organization textbox, input the name of your organization. For https://dev.azure.com/myorg/myproject, the organization name would be myorg . In the Project textbox, input the name of the project containing your board. For https://dev.azure.com/myorg/myproject, the project name would be myproject . If you want to embed all bugs created by the app beneath an overall work item, epic, or similar, fill in the optional ID in the Parent field.
+
Click Save and the app is added to your log. When new errors are logged, bugs are automatically created in the configured Azure Board.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-bitbucket/index.html b/elmah-io-apps-bitbucket/index.html
new file mode 100644
index 0000000000..f032114998
--- /dev/null
+++ b/elmah-io-apps-bitbucket/index.html
@@ -0,0 +1,665 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with Atlassian Bitbucket
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install Bitbucket App for elmah.io
+
Get your App password
+
To allow elmah.io to create issues on Bitbucket, you will need an App password. App passwords can be generated by clicking your user in the top right corner and selecting Personal settings . In the left menu, click the App passwords page (https://bitbucket.org/account/settings/app-passwords/
). To create a new password, click the Create app password button and input the following information:
+
+
elmah.io only need the Issues - Write permission to create issues. To test the inputted values on elmah.io (later step) also check the Repositories - Read permission.
+
After clicking the Create button, copy the generated app password.
+
Install the Bitbucket App on elmah.io
+
Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Bitbucket app and click the Install button:
+
+
Paste the App password copied in the previous step into the APP PASSWORD textbox. In the TEAM textbox, input the name of the team/workspace owning the repository you want to create issues in. In the REPOSITORY textbox input the name of the repository. In the USERNAME textbox, input the name of the user generating the App password. In older installations, this can also contain the team/workspace name.
+
Click Save and the app is added to your log. When new errors are logged, issues are automatically created in the configured Bitbucket repository.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-botbuster/index.html b/elmah-io-apps-botbuster/index.html
new file mode 100644
index 0000000000..a540cf271d
--- /dev/null
+++ b/elmah-io-apps-botbuster/index.html
@@ -0,0 +1,660 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Identify and ignore log messages from bots
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install BotBuster App for elmah.io
+
+The BotBuster app is deprecated. Enable the Filter Crawlers toggle on the Filters tab to ignore errors generated by crawlers.
+
+
The BotBuster app for elmah.io identifies and ignores messages generated by white hat bots like spiders, search engine bots, and similar. Under normal circumstances, you want to allow access for white hat bots, but you don't want to get a notification every time one of them tries to request a resource not found on the server.
+
Installing BotBuster couldn't be simpler. Log into elmah.io and go to the log settings. Click the Apps tab. Locate the BotBuster app and click the Install button.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-chatgpt/index.html b/elmah-io-apps-chatgpt/index.html
new file mode 100644
index 0000000000..cabc2db49f
--- /dev/null
+++ b/elmah-io-apps-chatgpt/index.html
@@ -0,0 +1,660 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with ChatGPT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install ChatGPT for elmah.io
+
Log into elmah.io and go to the log settings. Click the Apps tab. Locate the ChatGPT app and click the Install button:
+
+
Input your OpenAI API key (Where do I find my OpenAI API Key? ). Next, select which language model to use. We currently support GPT-3.5-Turbo and GPT-4.
+
As a default, elmah.io will only share the stack trace of an error with ChatGPT when you click the Get suggestion button in the elmah.io UI. If you want to include the source code and/or any SQL attached to the error, you can enable one or both toggles. Sharing the source will require you to bundle your source code alongside errors as documented here: How to include source code in log messages .
+
Click Save and the app is added to your log. When you open errors valid for ChatGPT help, you will see a tab named AI next to Detail , Inspector , etc.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-clickup/index.html b/elmah-io-apps-clickup/index.html
new file mode 100644
index 0000000000..40af21b783
--- /dev/null
+++ b/elmah-io-apps-clickup/index.html
@@ -0,0 +1,680 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with ClickUp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install ClickUp for elmah.io
+
Log into elmah.io and go to the log settings page. Click the Apps tab. Locate the ClickUp app and click the Install button:
+
+
You will need to input a ClickUp API token and the ID of the list to create tasks. The API token can be generated by navigating to ClickUp, clicking the profile photo in the bottom left corner, and clicking Apps . It is important to click the Apps link beneath your profile and not the ClickApps link beneath the team. On the Apps page, you can generate and copy a new token beneath the API Token section.
+
The list ID can be found by going to the list on the ClickUp app and clicking the list name:
+
+
When copying the link you will get a link similar to this:
+
https://app.clickup.com/.../v/li/901200300647
+
+
The list ID is the last part of the URL (901200300647
in the example above).
+
When both the API token and list ID are inputted on elmah.io, click the Test button to test the values. When the Test button turns green, click the Save button, and the app is added to your log. When new errors are logged, tasks are automatically created in the configured ClickUp list.
+
Troubleshooting
+
If errors aren't showing up in ClickUp, please check that the following are all true:
+
+When clicking the Test button on the ClickUp app settings screen, the button turns green.
+There's a message logged in the log where you set up the ClickUp integration.
+The message is marked as new (yellow star next to the title on the search result).
+The message is either of severity Error
or Fatal
.
+
+
To trigger an error manually, go to https://api.elmah.io/swagger/index.html and input an elmah.io API key with the Messages - Write permission enabled. Expand Messages and the POST node with the URL /v3/messages/{logId}
. Input your log ID and the following JSON:
+
{
+ "title": "This is a test",
+ "severity": "Error"
+}
+
+
Finally, click the Try it out! button and verify that the API returns a status code of 201 . The new error should show up in ClickUp. If testing this multiple times, you will need to make small adjustments to the title field inside the JSON, for additional errors to be marked as new.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-github/index.html b/elmah-io-apps-github/index.html
new file mode 100644
index 0000000000..f2c64fc850
--- /dev/null
+++ b/elmah-io-apps-github/index.html
@@ -0,0 +1,665 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with GitHub
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install GitHub App for elmah.io
+
Generate Personal Access Token
+
To allow elmah.io to create issues on GitHub, you need a Personal Access Token. Sign in to GitHub, click your profile photo in the top right corner, and click Settings . On the Settings page click Developer settings followed by Personal access token . Here you can create a new token by clicking the Generate new token (classic) button:
+
+
Input a token note and select an expiration date. If the repository you want issues created in is public, make sure to check the public_repo checkbox. If the repository is private, check the repo checkbox. Finally, click the Generate token button, and copy the generated token (colored with a green background)
+
GitHub also supports fine-grained personal access tokens. This token can also be used on elmah.io. Make sure to select Read and write in the Issues permission.
+
Install the GitHub App on elmah.io
+
Log into elmah.io and go to the log settings. Click the Apps tab. Locate the GitHub app and click the Install button:
+
+
Paste the token copied in the previous step into the Token textbox. In the Owner textbox, input the name of the user or organization owning the repository you want to create issues in. In the Repository textbox input the name of the repository.
+
Click Save and the app is added to your log. When new errors are logged, issues are automatically created in the configured GitHub repository.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-gitlab/index.html b/elmah-io-apps-gitlab/index.html
new file mode 100644
index 0000000000..8866bddbf9
--- /dev/null
+++ b/elmah-io-apps-gitlab/index.html
@@ -0,0 +1,664 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with GitLab
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install GitLab App for elmah.io
+
Generate Personal Access Token
+
To allow elmah.io to create issues on GitLab, you will need to generate a Personal Access Token. To do so, log into GitLab, click your profile photo in the top right corner, and select Preferences . On the Preferences page click the Access Tokens menu item:
+
+
Input a token name, expiration date, and check the api checkbox. Click the Create personal access token button and copy the generated token.
+
Install the GitLab App on elmah.io
+
Log into elmah.io and go to the log settings. Click the Apps tab. Locate the GitLab app and click the Install button:
+
+
Paste the token copied in the previous step into the Token textbox. In the Project textbox, input the ID or name of the project you want issues created on. If you are self-hosting GitLab, input your custom URL in the URL textbox (for example https://gitlab.hooli.com).
+
Click the Test button and observe it turn green. When clicking Save , the app is added to your log. When new errors are logged, issues are automatically created in the configured GitLab project.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-hipchat/index.html b/elmah-io-apps-hipchat/index.html
new file mode 100644
index 0000000000..0353913101
--- /dev/null
+++ b/elmah-io-apps-hipchat/index.html
@@ -0,0 +1,670 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Install HipChat App for elmah.io
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install HipChat App for elmah.io
+
Generate OAuth 2 Token
+
To allow elmah.io to log messages to HipChat, you will need to generate an OAuth 2 token. To do so, log into HipChat and go to the API Access page (replace elmahio with your subdomain).
+
+
Input a label, click the Create button and copy the generated token.
+
+If you want to test your configuration using the Test button on the elmah.io UI, you will need to select both Send Notification and View Room in Scopes .
+
+
Install the HipChat App on elmah.io
+
Log into elmah.io and go to the log settings. Click the Apps tab. Locate the HipChat app and click the Install button:
+
+
Paste the token copied in the previous step into the Token textbox. In the Room textbox, input the name of the HipChat chat room you want messages from elmah.io to show up in.
+
Click Save and the app is added to your log. When new errors are logged, messages start appearing in the chat room that you configured.
+
+HipChat doesn't allow more than 500 requests per 5 minutes. If you generate more messages to elmah.io, not all of them will show up in HipChat because of this.
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-ipfilter/index.html b/elmah-io-apps-ipfilter/index.html
new file mode 100644
index 0000000000..688765a8ba
--- /dev/null
+++ b/elmah-io-apps-ipfilter/index.html
@@ -0,0 +1,663 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ignore log messages from one or more IPs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install IP Filter App for elmah.io
+
+The IP Filter app is deprecated. The IP Filter filter on the Filters tab offers more advanced IP filtering.
+
+
The IP Filter app for elmah.io automatically ignores messages from one or more IP addresses. This is a great way to ignore errors generated by both crawlers and errors generated by you.
+
To install IP Filter, click the Install button on the Apps tab. This will show the IP Filter settings page:
+
+
To ignore messages from a single IP address, input the IP in both the From and To fields. To ignore messages from a range of IP addresses, input the start and end IP address in the From and To fields. Both IP addresses are included in the ignored range.
+
The IP Filter app ignores every message matching the specified IP range. This means that if you are logging something like Information messages through Serilog or similar, these messages are also ignored. For a message to have an IP, you will need to specify a server variable named REMOTE_ADDR
when creating the message. This variable is automatically added (if available) when using the integration for ELMAH.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-jira/index.html b/elmah-io-apps-jira/index.html
new file mode 100644
index 0000000000..d6c09733ef
--- /dev/null
+++ b/elmah-io-apps-jira/index.html
@@ -0,0 +1,677 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with Atlassian Jira
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install Jira App for elmah.io
+
Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Jira app and click the Install button:
+
+
Input your site name, which is the first part of the URL you use to log into Jira. For the URL https://elmahio.atlassian.net/
, the site
parameter would be elmahio
. In the Project field, input the key of the project. Note that a project has both a display name and a key. The property we are looking for here is the uppercase identifier of the project.
+
To create issues on Jira, you will need to input the username and password of a user with permission to create issues in the project specified above. You can use your user credentials, but we recommend using a combination of your username and an API token.
+
To generate a new token specific for elmah.io, go to the API Tokens page on your Jira account. Then click the Create API token button and input a label of your choice. Finally, click the Create button and an API token is generated for you. Make sure to copy this token, since you won't be able to access it once the dialog is closed.
+
Go back to elmah.io and input your email in the Username field and the API token from the previous step in the Password field. If you don't like to use an existing user account for the integration, you can create a new Atlassian account for elmah.io and generate the API token from that account instead.
+
Click Save and the app is added to your log. When new errors are logged, issues are automatically created in the configured Jira project.
+
Troubleshooting
+
If errors aren't showing up in Jira, please check that the following are all true:
+
+When clicking the Test button on the Jira app settings screen, the button turns green.
+There's a message logged in the log where you set up the Jira integration.
+The message is marked as new (yellow star next to the title on the search result).
+The message is either of severity Error
or Fatal
.
+
+
To trigger an error manually, go to https://api.elmah.io/swagger/index.html and input an elmah.io API key with the Messages - Write permission enabled. Expand Messages and the POST node with the URL /v3/messages/{logId}
. Input your log ID and the following JSON:
+
{
+ "title": "This is a test",
+ "severity": "Error"
+}
+
+
Finally, click the Try it out! button and verify that the API returns a status code of 201 . The new error should show up in Jira. If testing this multiple times, you will need to make small adjustments to the title field inside the JSON, for additional errors to be marked as new.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-mailman/index.html b/elmah-io-apps-mailman/index.html
new file mode 100644
index 0000000000..a4770205a4
--- /dev/null
+++ b/elmah-io-apps-mailman/index.html
@@ -0,0 +1,663 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Set up mail notifications on new errors
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install Mailman App for elmah.io
+
+The Mailman app is deprecated. Use an email rule available on the Rules tab for a more advanced email integration.
+
+
The Mailman app for elmah.io sends out an email to an address of your choice, every time a new error is logged.
+
To install Mailman, click the Install button on the Apps tab. This will show the Mailman settings page:
+
+
Input a valid email address in the Email input box and click Save .
+
The Mailman app will look at new errors only. Errors are defined by messages with a severity of Error
or Fatal
and with isNew == true
. isNew
is a field automatically added by elmah.io when indexing each message. isNew
is calculated by looking for similarities between the new message and already logged messages.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-pagerduty/index.html b/elmah-io-apps-pagerduty/index.html
new file mode 100644
index 0000000000..4acbce01c7
--- /dev/null
+++ b/elmah-io-apps-pagerduty/index.html
@@ -0,0 +1,712 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with PagerDuty
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Using the PagerDuty integration for elmah.io, you can set up advanced notification rules in PagerDuty when new errors are logged on elmah.io. Receive a phone call, text message, or one of the other options provided by PagerDuty, the second new errors are introduced on your websites or services.
+
To integrate elmah.io with PagerDuty, you need to set up a new integration on PagerDuty and install the PagerDuty app on elmah.io.
+
+
+
+Sign in to PagerDuty.
+
+
+Navigate to the Services page.
+
+
+Select the service that you want to integrate to from elmah.io in the list of services.
+
+
+On the Integrations tab click the Add an integration button.
+
+
+On the Add Integrations page search for elmah.io and select it in the search result:
+
+
+
+
+
+
+Copy the value in the Integration Key field.
+
+
+
Next, the PagerDuty app needs to be installed on elmah.io.
+
+
+Sign in to elmah.io.
+
+
+Navigate to the Log Settings page of the log you want to integrate with PagerDuty.
+
+
+Go to the Apps tab.
+
+
+Locate the PagerDuty app and click the Install button.
+
+
+Input the Integration Key that you copied in a previous step in the INTEGRATION KEY field:
+
+
+
+
+Click the Save button.
+
+
That's it. New errors stored in the selected log now trigger incidents in PagerDuty. To get help with this integration, make sure to reach out through the support widget on the elmah.io website.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-request-a-new-integration/index.html b/elmah-io-apps-request-a-new-integration/index.html
new file mode 100644
index 0000000000..ec698f4cb5
--- /dev/null
+++ b/elmah-io-apps-request-a-new-integration/index.html
@@ -0,0 +1,670 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Request a new integration between elmah.io and another tool
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
We are always on the lookout for creating new and useful integrations. We don't want to integrate with everything (that's what Zapier is for), but commonly used tools by .NET web developers are on our radar.
+
To suggest a new integration feel free to reach out through the support widget in the lower right corner.
+
+Not all integration requests are implemented. Don't feel bad if we decide not implement your suggestion.
+
+
When recommending a new integration we need the following information:
+
+Name of the tool.
+URL of the tool.
+Why do you need this integration?
+What do you expect from this integration?
+Does the tool provide an API?
+Anything else you think would help?
+Do you by any chance know anyone working on the tool?
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-slack/index.html b/elmah-io-apps-slack/index.html
new file mode 100644
index 0000000000..e4c6c1f07f
--- /dev/null
+++ b/elmah-io-apps-slack/index.html
@@ -0,0 +1,668 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with Slack
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install Slack App for elmah.io
+
Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Slack app and click the Install button. You will be redirected to Slack where you need to log into your workspace if not already. Once logged in, select the channel to send messages to:
+
+
Click the Allow button and you will be redirected back to elmah.io. The integration to Slack is now installed.
+
+Slack doesn't allow more than a single request per second. If you generate more than one message to elmah.io per second, not all of them will show up in Slack because of this.
+
+
Troubleshooting
+
Errors don't show up in Slack. Here are a few things to try out.
+
+Make sure that the Slack app is installed on the log as described above.
+Only new errors are sent to Slack. A new error has a severity of Error
or Fatal
and is marked with a yellow star on the search tab. We only send new errors to help you stay out of Slack's API limits. If sending all errors, you could quickly end up in a scenario where the same error is sent multiple times and more important errors get ignored by Slack.
+Make sure that your token is still valid. The only way to resolve an issue where the token is no longer valid is to re-install the Slack app.
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-teams/index.html b/elmah-io-apps-teams/index.html
new file mode 100644
index 0000000000..c644f6e8cb
--- /dev/null
+++ b/elmah-io-apps-teams/index.html
@@ -0,0 +1,680 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with Microsoft Teams
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install Microsoft Teams App for elmah.io
+
To install the integration with Microsoft Teams, go to teams and click the Apps menu item. Search for "elmah.io" and click the app:
+
+
Click the Add to a team button. In the dropdown, search for your team and/or channel:
+
+
Click the Set up a connector button.
+
A new webhook URL is generated. Click the Copy Text button followed by the Save button:
+
+
The elmah.io integration is now configured on Microsoft Teams and you should see the following screen:
+
+
The final step is to input the webhook URL that you just copied, into elmah.io.
+
Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Microsoft Teams app and click the Install button. In the overlay, paste the URL from the previous step:
+
+
Click Save and the app is added to your log. When new errors are logged, messages start appearing in the channel that you configured.
+
+The Office 365 API used behind the scenes for this app uses throttling rather than a maximum of allowed requests. This means that you may start experiencing messages not being sent, if you start logging a large amount of messages. We have experienced a lot of weird error codes when communicating with the API. An example of this is an exception while posting data to the API, but the data is successfully shown on Teams. The result of this error is, that elmah.io retries the failing request multiple times, which causes the same message to be shown multiple times on Teams.
+
+
Troubleshooting
+
Errors don't show up in Teams. Here are a few things to try out.
+
+Make sure that the Teams app is installed on the log as described above.
+Only new errors are sent to Teams. A new error has a severity of Error
or Fatal
and is marked with a yellow star on the search tab. We only send new errors to help you stay out of Teams' API limits. If sending all errors, you could quickly end up in a scenario where the same error is sent multiple times and more important errors get ignored by Teams.
+Re-install the app on elmah.io with the webhook URL provided by Teams.
+Remove the elmah.io configuration from Teams and re-install it. After re-installing the app, you will need to copy the new webhook URL provided by Teams and input it in the elmah.io Teams app as descrived above.
+Go to the Apps page on Teams and search for 'elmah.io'. Remove the app entirely, click F5 to refresh the page, and install the app again. You may be stuck on an older version of our app, which can be fixed by simply removing and installing the app again.
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-trello/index.html b/elmah-io-apps-trello/index.html
new file mode 100644
index 0000000000..ea6faa2237
--- /dev/null
+++ b/elmah-io-apps-trello/index.html
@@ -0,0 +1,677 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with Trello
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install Trello App for elmah.io
+
For elmah.io to communicate with the Trello API, we will need an API key and token. The API key is available here: https://trello.com/app-key
. If you don't have a personal token available on that site, create a new Power-Up as described on the page. When the Power-Up is created, you can create a new API key on that page.
+
To get the token, visit the following URL in your browser: https://trello.com/1/authorize?expiration=never&scope=read,write,account&response_type=token&name=Server%20Token&key=API_KEY
. Remember to replace API_KEY
with your Trello API key located in the previous step. When clicking the Allow button, Trello will generate a new token for you and show it in the browser window.
+
elmah.io will create cards on a board list of your choice. Unfortunately, Trello didn't provide a way to obtain list IDs. The easiest way is to open Developer Tools in your browser and click an existing card inside the list you want elmah.io to create new cards in. Locate the request for the card details in the Network tab and click the Preview tab. The list id is in the card details:
+
+
Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Trello app and click the Install button:
+
+
Input the API key, token, and list ID, all located in the previous steps. Click the Test button to test that everything works and finally, click Save . New errors now trigger elmah.io to create a card with the details of the error in Trello.
+
Troubleshooting
+
If errors aren't showing up in Trello, please check that the following are all true:
+
+When clicking the Test button on the Trello app settings screen, the button turns green.
+There's a message logged in the log where you set up the Trello integration.
+The message is marked as new (yellow star next to the title on the search result).
+The message is either of severity Error
or Fatal
.
+
+
To trigger an error manually, go to https://api.elmah.io/swagger/index.html and input an elmah.io API key with the Messages - Write permission enabled. Expand Messages and the POST node with the URL /v3/messages/{logId}
. Input your log ID and the following JSON:
+
{
+ "title": "This is a test",
+ "severity": "Error"
+}
+
+
Finally, click the Try it out! button and verify that the API returns a status code of 201 . The new error should show up in Trello. If testing this multiple times, you will need to make small adjustments to the title field inside the JSON, for additional errors to be marked as new.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-twilio/index.html b/elmah-io-apps-twilio/index.html
new file mode 100644
index 0000000000..b546b005c4
--- /dev/null
+++ b/elmah-io-apps-twilio/index.html
@@ -0,0 +1,683 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with Twilio
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install Twilio for elmah.io
+
To send SMS/Text messages with Twilio, you will need to sign up for Twilio first. Twilio provides a range of good tutorials when signing up, why we don't want to duplicate them here. When signed up, you will have access to a Twilio phone number to send messages from, an Account SID and a token needed to authenticate Twilio. These pieces of information will be used below when installing the Twilio app on elmah.io.
+
Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Twilio app and click the Install button:
+
+
Input your Twilio phone number (available on https://www.twilio.com/console/phone-numbers/incoming) in the From field. Input the phone number you want receiving error reports from elmah.io in the To field. Remember to fully qualify the number with a plus and the language code (US example: +12025550170 - UK example: +441632960775). Copy your Account SID and Auth Token from the Twilio Dashboard and input them in the fields on elmah.io.
+
Click Save and the app is added to your log. When new errors are logged, an SMS/Text message is automatically sent to the configured phone number.
+
Troubleshooting
+
If errors aren't being sent to your phone, verify that the configured variables work. To do so, replace the four variables in the top of this PowerShell script and execute it:
+
$sid = "INSERT_SID"
+$token = "INSERT_TOKEN"
+$from = "INSERT_FROM"
+$to = "INSERT_TO"
+
+$url = "https://api.twilio.com/2010-04-01/Accounts/$sid/Messages.json"
+
+$pair = "$($sid):$($token)"
+$encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
+$basicAuthValue = "Basic $encodedCreds"
+$Headers = @{
+ Authorization = $basicAuthValue
+ ContentType = "application/x-www-form-urlencoded"
+}
+
+$from = $from.Replace("+", "%2B")
+$to = $to.Replace("+", "%2B")
+
+$response = Invoke-WebRequest -Uri $url -Method POST -Headers $Headers -Body "Body=Affirmative&From=$from&To=$to"
+
+
You should see a text message on your phone. The script will output any errors from Twilio if something isn't working.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/elmah-io-apps-webclient/index.html b/elmah-io-apps-webclient/index.html
new file mode 100644
index 0000000000..dd0a482870
--- /dev/null
+++ b/elmah-io-apps-webclient/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Redirecting...
+
+
+
+
+
+
+Redirecting...
+
+
diff --git a/elmah-io-apps-youtrack/index.html b/elmah-io-apps-youtrack/index.html
new file mode 100644
index 0000000000..76b696ec67
--- /dev/null
+++ b/elmah-io-apps-youtrack/index.html
@@ -0,0 +1,663 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with YouTrack
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Install YouTrack App for elmah.io
+
Get your token
+
To allow elmah.io to create issues on YouTrack, you will need a permanent token. Go to your YouTrack profile, click the Account Security . Here you can generate a new token:
+
+
Copy the generated token.
+
Install the YouTrack App on elmah.io
+
Log into elmah.io and go to the log settings. Click the Apps tab. Locate the YouTrack app and click the Install button. Input your token and the base URL of your YouTrack Cloud installation. Next, click the Login button to fetch the list of projects from YouTrack:
+
+
Click Save and the app is added to your log. When new errors are logged, issues are automatically created in the configured YouTrack project.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/email-troubleshooting/index.html b/email-troubleshooting/index.html
new file mode 100644
index 0000000000..2f57deae51
--- /dev/null
+++ b/email-troubleshooting/index.html
@@ -0,0 +1,680 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Email troubleshooting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Email troubleshooting
+
+
So, you aren't receiving emails from elmah.io? Here is a collection of things to know/try out.
+
Emails on new errors only
+
The most common reason for not receiving emails when errors are logged is that elmah.io only sends the New Error email when an error that we haven't seen before is logged. New errors are marked with a yellow star next to the log message in the UI and can be searched through either search filters or full-text search:
+
isNew:true
+
+
The new detection algorithm is implemented by looking at a range of fields like the title, type, and severity. Only severities Error
and Fatal
marked as isNew
trigger an email.
+
Email bounced
+
We use AWS to send out all transactional emails from elmah.io. We get a notification from AWS when an email bounces and we stop sending to that email address, even if any new emails wouldn't cause a bounce. Beneath your profile, you will be able to see if your email caused a bounce:
+
+
As the error message says, get in contact for us to try and reach the email address again.
+
Invalid email
+
Ok, this may seem obvious. But this happens more often than you would think. Typos are a common cause of invalid emails. Specifying a mailing list or group address doesn't always play nice with elmah.io either. For instance, Office 365 distribution groups block external emails as the default. The easiest way to check your inputted email address is to send a new message to that address from an external email provider.
+
+
We do a lot to keep our email reputation high. But some email clients may treat similar-looking emails as promotional or spam. Remember to check those folders and mark messages as important if spotting them in the wrong folder.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/export-data-from-elmah-io-to-json/index.html b/export-data-from-elmah-io-to-json/index.html
new file mode 100644
index 0000000000..7432e23d49
--- /dev/null
+++ b/export-data-from-elmah-io-to-json/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Redirecting...
+
+
+
+
+
+
+Redirecting...
+
+
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000000..12b824181a
Binary files /dev/null and b/favicon.ico differ
diff --git a/handle-elmah-io-downtime/index.html b/handle-elmah-io-downtime/index.html
new file mode 100644
index 0000000000..b2ac288f45
--- /dev/null
+++ b/handle-elmah-io-downtime/index.html
@@ -0,0 +1,706 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Handle elmah.io downtime
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Handle elmah.io downtime
+
Like every other SaaS product out there, we cannot promise you 100% uptime on elmah.io. We understand, that your logging data is extremely important for your business and we do everything in our power to secure that elmah.io is running smoothly. To monitor our APIs and websites, check out status.elmah.io .
+
It is our general recommendation to implement code that listens for communication errors with the elmah.io API and log errors elsewhere. How you do this depends on which elmah.io NuGet package you have installed. The documentation for each package will show how to subscribe to errors. For Elmah.Io.Client
it would look similar to this:
+
var elmahIo = ElmahioAPI.Create("API_KEY");
+elmahIo.Messages.OnMessageFail += (sender, args) =>
+{
+ var message = args.Message;
+ var exception = args.Error;
+
+ // TODO: log it
+};
+
+
For a logging framework like Serilog, it would look similar to this:
+
Log.Logger = new LoggerConfiguration()
+ .WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID"))
+ {
+ OnError = (msg, ex) =>
+ {
+ // TODO: log it
+ }
+ })
+ .CreateLogger();
+
+
It is important not to log errors in OnMessageFail
and OnError
callbacks to elmah.io, since that could cause an infinite loop. Check out the documentation for the package you are using for additional details.
+
Response explanation
+
Here's an overview of the types of errors you can experience from the API:
+
+
+
+Response
+Meaning
+
+
+
+
+Timeout
+Something is very wrong with our API or Azure. You can be sure that we are working 24/7 to fix it.
+
+
+500
+The API is reachable, but we have a problem communicating with Azure Service bus. Azure has great uptime and all of our resources are dedicated and replicated. Still, we experience short periods of downtime from time to time.
+
+
+429
+We allow a maximum (per API key) of 500 requests per minute and 3600 per hour. 429 means that you have crossed that line. This status code doesn't indicate that the API is down.
+
+
+4xx
+Something is wrong with the request. Check out the API documentation for details. This status code doesn't indicate that the API is down.
+
+
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/heartbeats-troubleshooting/index.html b/heartbeats-troubleshooting/index.html
new file mode 100644
index 0000000000..f07cd9929a
--- /dev/null
+++ b/heartbeats-troubleshooting/index.html
@@ -0,0 +1,678 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Heartbeats Troubleshooting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Heartbeats Troubleshooting
+
Common problems and how to fix them
+
Here you will a list of common problems and how to solve them.
+
Timeout when creating heartbeats through Elmah.Io.Client
+
If you experience a timeout when calling the Healthy
, Degraded
, or Unhealthy
method, you may want to adjust the default HTTP timeout. Elmah.Io.Client
has a default timeout of 5 seconds to make sure that logging to elmah.io from a web application won't slow down the web app too much in case of slow response time from the elmah.io API. While 99.9% of the requests to the elmah.io API finish within this timeout, problems with Azure, the network connection, and a lot of other issues can happen.
+
Since heartbeats typically run outside the scope of a web request, it's safe to increase the default HTTP timeout in this case:
+
var api = ElmahioAPI.Create("API_KEY", new ElmahIoOptions
+{
+ Timeout = new TimeSpan(0, 0, 30)
+});
+
+
The example set a timeout of 30 seconds.
+
SocketException when creating heartbeats through Elmah.Io.Client
+
A System.Net.Sockets.SocketException
when communicating with the elmah.io API can mean multiple things. The API can be down or there's network problems between your machine and the API. Increasing the timeout as shown in the previous section should be step one. If you still experience socket exceptions, it might help to implement retries. This can be done by setting up a custom HttpClient
:
+
builder.Services
+ .AddHttpClient("elmahio")
+ .AddPolicyHandler(HttpPolicyExtensions
+ .HandleTransientHttpError()
+ .WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(i)));
+
+
The AddPolicyHandler
is available when installing the Microsoft.Extensions.Http.Polly
NuGet package. Next, create the elmah.io client with the custom HttpClient
:
+
var httpClient = httpClientFactory.CreateClient("elmahio");
+var elmahIoClient = ElmahioAPI.Create("API_KEY", options, httpClient);
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/how-does-the-new-detection-work/index.html b/how-does-the-new-detection-work/index.html
new file mode 100644
index 0000000000..e9ce53ff79
--- /dev/null
+++ b/how-does-the-new-detection-work/index.html
@@ -0,0 +1,666 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ How does the new detection work
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How does the new detection work
+
Being able to identify when a logged error is new or a duplicate of an already logged error, is one of the most important features on elmah.io. A lot of other features are based on this mechanism to help you reduce the number of emails, Slack/Teams messages, and much more. We often get questions about how this works and how to tweak it, why this article should bring some clarity to questions about this feature.
+
When logging messages to elmah.io using either one of the integrations or through the API, we automatically set a flag named isNew
on each log message. Calculating the value of this field is based on a rather complex algorithm. The implementation is closed-source but not a huge secret. Each message is assigned a hash value based on a range of fields like the message template, the severity, the URL, and more. Some values are normalized or modified before being sent as input to the hash function. An example of this is removing numbers from the log message which will ensure that Error on product 1
and Error on product 2
will be considered the same log message. When receiving a new log message, we check if an existing message with the same hash is already stored in the log. If not, the new message will be marked as New by setting the isNew
flag to true
. If we already found one or more log messages with the same hash, the new message will have its isNew
flag set to false
.
+
Messages and apps
+
Most apps and features around sending messages from elmah.io are based on the isNew
flag. This means that only new errors trigger the New Error Email , the Slack and Teams apps, etc. This is done to avoid flooding the recipient system with emails or messages. You typically don't want 1,000 emails if the same error occurs 1,000 times. Error occurrence is still important, why there are other features to help you deal with this like the Error Occurrence Email and spike-based machine learning features.
+
Modifying the hash function
+
We sometimes get requests to modify the hash function, but unfortunately, that's currently not possible. We change the implementation from time to time to improve the uniqueness detection over time. If you have an example of two log messages that should have been considered unique or not considered unique, feel free to reach out. This may or may not result in changes to the hash function.
+
There are still some possibilities to force two log messages to not be unique. The obvious is to include different variables inside the log message. Remember that numbers are removed, why this must consist of letters. Another approach is to put individual values in the source
field. This can be done in all integrations by implementing the OnMessage
action. Some integrations also support setting the source
field by including it as structured properties like Error from {source}
.
+
Setting re-occurring messages as New
+
A common request is to get a notification if an error that you believed was fixed re-occur. This scenario is built into elmah.io's issue tracker. When marking a log message as fixed through the UI or API, elmah.io automatically marks all instances of the log message as fixed. If a new log message with the same hash is logged at some point, the isNew
flag on this message will be set to true
. This will trigger the New Error Email and most of the integrations again.
+
Retention
+
Depending on your current plan, each subscription provides x days of retention for log messages. This means that log messages are automatically deleted after x days in the database. Once all instances of a log message are deleted, a new log message generating the same hash as the deleted messages will be marked as new. To increase the chance of log messages being marked as new, you can lower the retention on each log on the Log Settings page.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/how-prices-are-calculated/index.html b/how-prices-are-calculated/index.html
new file mode 100644
index 0000000000..a46cc73130
--- /dev/null
+++ b/how-prices-are-calculated/index.html
@@ -0,0 +1,668 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ How prices are calculated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How prices are calculated
+
When using elmah.io, you may experience the need to switch plans, update your credit card, or do other tasks which will require changes to your subscription. This document explains the different upgrade/downgrade and payment paths.
+
Switch to a higher plan
+
You can switch to a larger plan at all times. If you purchased a Small Business ($29) on June 1 and want to upgrade to Business ($49) on June 15, we charge you ~ $35. You already paid $15 for half of June on the Small Business plan, so the remaining amount is deducted from the $49. Your next payment will be on July 15.
+
Switch to a lower plan
+
You can switch to a lower plan when your current subscription is renewed. If you purchased a Business plan ($49) on June 15. and downgrade to a Small Business plan ($29) on July 1, you would be charged $49 on June 15, and $29 on July 15. You commit to either a month or a year in advance.
+
Update your credit card
+
A new credit card can be inputted at any time during your subscription. The payment provider will automatically charge the new credit card on the next payment.
+
Purchase a top-up
+
If you need additional messages but don't want to upgrade to a larger plan permanently, you can purchase a top-up. The purchase is made on the credit card already configured on the subscription. If you want to buy the top-up on a different credit card, you will need to use the Update credit card feature first. The price of the top-up is always $19, and you can purchase as many top-ups as you want.
+
Switch payment interval
+
Switching from monthly to annual or annual to monthly is possible. If you switch from monthly to annual, you are charged the difference between the two plans as in Switch to a higher plan . If you switch from annual to monthly, one of two scenarios can happen. If you switch to the same plan or lower, your plan will automatically switch when your current subscription renews (like in Switch to a lower plan ). If you switch to a higher plan, your plan will switch immediately as in Switch to a higher plan . If the price of the remaining time on your current plan covers the price of the new plan, the exceeding amount will be credited to your balance and used to pay for the new plan and any upcoming invoices. Paying for top-ups with the balance is currently not supported.
+
Purchase a subscription from Denmark
+
We are based in Denmark, why selling services to another Danish company requires us to include 25% VAT (moms). The price on the various dialogs will automatically show the price including VAT as well as the VAT amount. Your invoices will include the amount in VAT to inform SKAT.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/how-to-avoid-emails-getting-classified-as-spam/index.html b/how-to-avoid-emails-getting-classified-as-spam/index.html
new file mode 100644
index 0000000000..2d8afa7294
--- /dev/null
+++ b/how-to-avoid-emails-getting-classified-as-spam/index.html
@@ -0,0 +1,662 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ How to avoid emails getting classified as spam
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How to avoid emails getting classified as spam
+
We do everything in our power to maintain a good email domain reputation. Sometimes emails sent from elmah.io may be classified as spam in your email client. The easiest way to avoid this is to inspect the sender in an email received from @elmah.io and add it to your contact list (we primarily send emails from info@elmah.io
and noreply@elmah.io
). How you do this depends on your email client but all major clients have this option. In the sections below, there are alternatives to the contact list approach for various email clients.
+
Gmail
+
If you don't want to add elmah.io addresses to your contact list you can use Gmail's Filters feature to always classify *@elmah.io
as not spam. To do so, go to Settings | Filters and create a new filter:
+
+
Click Create filter and check the Never send it to Spam option:
+
+
Finally, click the Create filter button, and emails from elmah.io will no longer be classified as spam.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/how-to-configure-api-key-permissions/index.html b/how-to-configure-api-key-permissions/index.html
new file mode 100644
index 0000000000..c8c6c7ae93
--- /dev/null
+++ b/how-to-configure-api-key-permissions/index.html
@@ -0,0 +1,662 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ How to configure API key permissions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Security on the elmah.io API is handled through the use of API keys. All requests to the API must be accompanied by an API key. When creating your organization, a default API key was automatically created. API keys can be revoked and you can create multiple API keys for different purposes and projects. Much like a user can be awarded different levels of access on elmah.io, API keys also have a set of permissions. The default created API key for your organization, only have permission to write log messages to elmah.io.
+
To configure permissions for a new or existing API key, either click the Edit
or Add API Key
button on the API Keys tab of your organization settings. This will show the API key editor view:
+
+
As mentioned previously, new keys have the messages_write
permission enabled only. This permission will cover logging from your application to elmah.io. If your application needs to browse messages from elmah.io, create new logs/applications, etc. you will need to enable the corresponding permission. Notice that read permissions don't need to be enabled, for you to browse logs and log messages on the elmah.io UI. API keys are used by the range of client integrations only.
+
+Your API key shouldn't be shared outside your organization. In some situations, you will need to share your API key (like when logging from JavaScript). In these cases, it's essential that your API key only has the messages_write
permission enabled. With all permissions enabled, everyone will be able to browse your logs.
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/how-to-correlate-messages-across-services/index.html b/how-to-correlate-messages-across-services/index.html
new file mode 100644
index 0000000000..83ea978e42
--- /dev/null
+++ b/how-to-correlate-messages-across-services/index.html
@@ -0,0 +1,869 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ How to correlate messages across services
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How to correlate messages across services
+
+
A common architecture is to spread out code across multiple services. Some developers like to split up their code into microservices. Others have requirements for running code asynchronous behind a queue. The common theme here is that code often runs in multiple processes spread across multiple servers. While logging can be easily set up in different projects and services, being able to correlate log messages across multiple services isn't normally available when logging to individual log outputs.
+
Imagine a console application making an HTTP request to your public API. The API calls an internal API, which again puts a message on a queue. A service consumes messages from the queue and ends up logging an error in the error log. Seeing the entire log trace from receiving the request on the API straight down the chain resulting in the error, highly increases the chance of figuring out what went wrong. With the Correlation feature on elmah.io, we want to help you achieve just that. In this article, you will learn how to set up Correlations.
+
+Correlation currently requires all of your applications to log to the same log on elmah.io. For you to separate log messages from each service, we recommend setting the Application
property on all log messages. Filtering on log messages is available through the elmah.io UI. We may want to explore ways to use Correlation across multiple error logs in the future.
+
+
CorrelationId explained
+
The way log messages are correlated on elmah.io is based on a property on all log messages named correlationId
. The field is available both when creating log messages through our API as well as in the Elmah.Io.Client
NuGet package. The property is a string which means that you can use whatever schema you want for the correlation ID.
+
To set the correlation ID using the client, use the following code:
+
var myCorrelationId = "42";
+var client = ElmahioAPI.Create("API_KEY");
+await client.Messages.CreateAndNotifyAsync(new Guid("LOG_ID"), new CreateMessage
+{
+ Title = "Hello World",
+ // ...
+ CorrelationId = myCorrelationId
+});
+
+
In a real-world scenario, myCorrelationId
wouldn't be hardcoded but pulled from a shared header, message ID, or similar. As long as all services set CorrelationId
to the same ID, log messages within a correlation can be searched on the elmah.io UI:
+
+
Setting CorrelationId
+
How you set the correlation ID depends on which integration you are using. In some cases, a correlation ID is set automatically while others will require a few lines of code.
+
Elmah.Io.Client.Extensions.Correlation
+
We have developed a NuGet package dedicated to setting the correlation ID from the current activity in an easy way. The package can be used together with all of the various client integrations we offer (like Elmah.Io.AspNetCore
and Elmah.Io.NLog
). Start by installing the package:
+
Install-Package Elmah.Io.Client.Extensions.Correlation
+
dotnet add package Elmah.Io.Client.Extensions.Correlation
+
<PackageReference Include="Elmah.Io.Client.Extensions.Correlation" Version="5.*" />
+
paket add Elmah.Io.Client.Extensions.Correlation
+
+
Next, call the WithCorrelationIdFromActivity
method as part of the OnMessage
action/event. How you do this depends on which of the client integrations you are using. For Elmah.Io.Client
it can be done like this:
+
var elmahIo = ElmahioAPI.Create("API_KEY");
+elmahIo.Messages.OnMessage += (sender, args) =>
+{
+ args.Message.WithCorrelationIdFromActivity();
+};
+
+
For Elmah.Io.AspNetCore
it can be done like this:
+
builder.Services.AddElmahIo(options =>
+{
+ // ...
+ options.OnMessage = msg =>
+ {
+ msg.WithCorrelationIdFromActivity();
+ };
+});
+
+
ASP.NET Core
+
When logging uncaught errors using the Elmah.Io.AspNetCore
package, we automatically pick up any traceparent
header and put the trace ID as part of the error logged to elmah.io. For an overview of wrapping calls to your ASP.NET Core API in an Activity
check out the section about W3C Trace Context.
+
If you want to set the correlation ID manually, you can use the OnMessage
action:
+
builder.Services.Configure<ElmahIoOptions>(o =>
+{
+ o.OnMessage = msg =>
+ {
+ msg.CorrelationId = "42";
+ };
+});
+
+
When requested through the browser, a traceparent
is not automatically added, unless you manually do so by using an extension as shown in the W3C section. In this case, you can either install the Elmah.Io.Client.Extensions.Correlation
package as already explained, or set the correlationId
manually by installing the System.Diagnostics.DiagnosticSource
NuGet package and adding the following code to the OnMessage
action:
+
o.OnMessage = msg =>
+{
+ msg.CorrelationId = System.Diagnostics.Activity.Current?.TraceId.ToString();
+};
+
+
Microsoft.Extensions.Logging
+
To store a correlation ID when logging through Microsoft.Extensions.Logging you can either set the CorrelationId
property (manually or using the Elmah.Io.Client.Extensions.Correlation
NuGet package) or rely on the automatic behavior built into Microsoft.Extensions.Logging.
+
To manually set the correlation you can include correlationId
as part of the log message:
+
logger.LogInformation("A log message with {correlationId}", "42");
+
+
Or you can put the correlation ID as part of a logging scope:
+
using (logger.BeginScope(new { CorrelationId = "42" }))
+{
+ logger.LogInformation("A log message");
+}
+
+
In some cases, a correlation ID will be set automatically. If there is a current active Activity
(see later), Microsoft.Extensions.Logging automatically decorates all log messages with a custom property named TraceId
. The elmah.io backend will pick up any value in the TraceId
and use that as the correlation ID.
+
Serilog
+
When logging through Serilog a correlation ID can be added to one or more log messages in multiple ways. The most obvious being on the log message itself:
+
Log.Information("A log message with {correlationId}", "42");
+
+
If you don't want correlation ID as part of the log message, you can push the property using LogContext
:
+
using (LogContext.PushProperty("correlationId", "42"))
+{
+ Log.Information("A log message");
+}
+
+
You will need to install the LogContext
enricher for this to work:
+
Log.Logger =
+ new LoggerConfiguration()
+ .Enrich.FromLogContext()
+ // ...
+ .CreateLogger();
+
+
The elmah.io sink for Serilog is an async batching sync. This means that log messages are not logged in the same millisecond as one of the logging methods on the Log
class is called and the current activity is no longer set. When logging from a web application or other project types where the activity is short-lived, you either need to include the correlation ID as part of the message (as shown in the previous examples) or you need to store the correlation ID as part of the request. For ASP.NET Core this can be done using middleware:
+
app.Use(async (ctx, next) =>
+{
+ IDisposable disposeMe = null;
+ var activity = Activity.Current;
+ if (activity != null)
+ {
+ disposeMe = LogContext.PushProperty("correlationid", activity.TraceId.ToString());
+ }
+
+ try
+ {
+ await next();
+ }
+ finally
+ {
+ disposeMe?.Dispose();
+ }
+});
+
+
All calls to Serilog within the web request will have the correlation ID set to the TraceId
of the current activity. Other frameworks support similar features for enriching the current request or invocation with custom properties.
+
NLog
+
Correlation ID can be set on log messages logged through NLog in multiple ways. The first approach is to include the ID directly in the log message:
+
logger.Info("A log message with {correlationId}", "42");
+
+
If you don't want the ID as part of the log message you can use either NLog's two context objects:
+
using (MappedDiagnosticsLogicalContext.SetScoped("correlationId", "42"))
+{
+ logger.Info("A log message");
+}
+GlobalDiagnosticsContext.Set("correlationId", "42");
+
+
You can also add the property manually to the log message using the log event builder API available in NLog:
+
var infoMessage = new LogEventInfo(LogLevel.Info, "", "A log message");
+infoMessage.Properties.Add("correlationid", "42");
+logger.Info(infoMessage);
+
+
log4net
+
Correlation ID can be set on log messages logged through log4net in multiple ways. You can include it directly on the LoggingEvent
:
+
var properties = new PropertiesDictionary();
+properties["correlationid"] = "42";
+log.Logger.Log(new LoggingEvent(new LoggingEventData
+{
+ Level = Level.Info,
+ TimeStampUtc = DateTime.UtcNow,
+ Properties = properties,
+ Message = "A log message",
+}));
+
+
You most likely use the Info
, Warn
, and similar helper methods to store log messages. In this case, you can set the correlation ID on the ThreadContext
:
+
ThreadContext.Properties["correlationid"] = "42";
+log.Info("A log message");
+
+
Please notice that correlationid
in both examples must be in all lowercase.
+
W3C Trace Context
+
The class Activity
has been mentioned a couple of times already. Let's take a look at what that is and how it relates to W3C Trace Context. Trace Context is a specification by W3C for implementing distributed tracing across multiple processes which are already widely adopted. If you generate a trace identifier in a client initiating a chain of events, different Microsoft technologies like ASP.NET Core already pick up the extended set of headers and include those as part of log messages logged through Microsoft.Extensions.Logging.
+
Let's say we have a console application calling an API and we want to log messages in both the console app and in the API and correlate them in elmah.io. In both the console app and in the ASP.NET Core application, you would set up Elmah.Io.Extensions.Logging
using the default configuration. Then in the console application, you will wrap the call to the API in an Activity
:
+
var httpClient = new HttpClient();
+var activity = new Activity("ApiCall").Start();
+try
+{
+ using (logger.BeginScope(new
+ {
+ TraceId = activity.TraceId,
+ ParentId = activity.ParentSpanId,
+ SpanId = activity.SpanId }))
+ {
+ logger.LogInformation("Fetching data from the API");
+ var result = await httpClient.GetStreamAsync("https://localhost:44396/ping");
+ // ...
+ }
+}
+finally
+{
+ activity.Stop();
+}
+
+
By creating and starting a new Activity
before we call the API, log messages in the console app can be decorated with three additional properties: TraceId
, ParentId
, and SpanId
. This will make sure that all log messages logged within this scope will get the correlation ID set on elmah.io. HttpClient
automatically picks up the activity and decorates the request with additional headers.
+
On the API we simply log as normal:
+
[ApiController]
+[Route("[controller]")]
+public class PingController : ControllerBase
+{
+ private readonly ILogger<PingController> _logger;
+
+ public PingController(ILogger<PingController> logger)
+ {
+ _logger = logger;
+ }
+
+ [HttpGet]
+ public string Get()
+ {
+ _logger.LogInformation("Received a ping");
+ return "pong";
+ }
+}
+
+
Notice that we didn't have to decorate log messages with additional properties. ASP.NET Core automatically picks up the new headers and decorates all log messages with the correct trace ID.
+
If you want to test this through a browser, you'll need to modify the request headers before the request is made to your web application. There is a range of extensions available to help with this. For the following example, we'll use ModHeader :
+
+
The extension will enrich all requests with a header named traceparent
in the format VERSION-TRACE_ID-SPAN_ID-TRACE_FLAGS
. elmah.io will automatically pick up this header and set correlationId
to the value of TRACE_ID
.
+
If you don't get a correlation ID set on your log messages, we recommend installing the Elmah.Io.Client.Extensions.Correlation
NuGet package and calling the following method in the OnMessage
event/action:
+
msg.WithCorrelationIdFromActivity();
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/how-to-enable-two-factor-login/index.html b/how-to-enable-two-factor-login/index.html
new file mode 100644
index 0000000000..7898f4add2
--- /dev/null
+++ b/how-to-enable-two-factor-login/index.html
@@ -0,0 +1,678 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ How to enable two-factor login
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How to enable two-factor login
+
+
elmah.io supports two-factor login through either one of the social providers or a two-factor app like Google Authenticator or Authy.
+
Two-factor with an elmah.io username and password
+
When signing into elmah.io with a username and password, two-factor authentication can be enabled on the Security tab on your profile:
+
+
Follow the instructions on the page to install either Google Authenticator or Authy. Once you have the app installed, scan the on-screen QR code and input the generated token in the field in step 3.
+
Once two-factor authentication has been successfully set up, the following screen is shown:
+
+
Two-factor authentication can be disabled at any time by inputting a new code from the authenticator app in the text field and clicking the Deactivate two-factor login button.
+
We recommend that you sign out after enabling two-factor authentication to invalidate the current session.
+
+Popular authenticator apps like Google Authenticator and Microsoft Authenticator support cloud backup. Make sure to enable this in case you lose your phone. When cloud backup is enabled, you can sign in with your main account when you get a new phone and all of your stored accounts will be automatically restored.
+
+
Two-factor with a social provider
+
When using one of the social providers to log in to elmah.io, two-factor authentication can be enabled through either Twitter, Facebook, Microsoft, or Google. Check out the documentation for each authentication mechanism for details on how to enable two-factor authentication.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/how-to-get-elmah-io-to-resolve-the-correct-client-ip/index.html b/how-to-get-elmah-io-to-resolve-the-correct-client-ip/index.html
new file mode 100644
index 0000000000..5b6089d240
--- /dev/null
+++ b/how-to-get-elmah-io-to-resolve-the-correct-client-ip/index.html
@@ -0,0 +1,671 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ How to get elmah.io to resolve the correct client IP
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How to get elmah.io to resolve the correct client IP
+
elmah.io try to resolve the IP of the client causing a log message, no matter what severity (Error, Information, etc.) and platform (browser, web-server, etc.) a log message is sent from. This is done by looking at multiple pieces of information provided by the sender. In some cases, elmah.io may not be able to resolve the IP or resolve a wrong IP address. In this document, you will find help getting the right client IP into elmah.io.
+
Missing IP when using a proxy
+
If you are using a proxy layer in between the client and your web server, you may experience log messages without a client IP. This is probably caused by the proxy hiding the original IP from your web server. Most proxies offer an alternative server variable like the X-Forwarded-For
header. You can inspect the server variables on the Server Variables tab on elmah.io and check if your proxy includes the original IP in any of the variables. We support custom headers from a range of proxies (like Cloudflare). Most proxies support some kind of settings area where the X-Forwarded-For
header can be enabled. If you are using a proxy that uses custom headers, please make sure to reach out and we may want to include the custom header to elmah.io.
+
Wrong IP when using a proxy
+
If the client IP is wrong when behind a proxy, it is typically because the proxy replaces the client IP when calling your server with the IP of its server. This is a poor practice and makes it very hard for elmah.io to figure out which IP belongs to the user and which one to the proxy. Luckily, this is configurable in a lot of proxies through their settings area.
+
Missing IP
+
This can be caused by several issues. In most instances, the client doesn't include any server variables that reveal the IP address. In this case, the client IP will not be available within the elmah.io UI. In some cases, you may know the user's IP from session variables or similar. To include an IP on messages logged to elmah.io, you can implement the OnMessage
event or action, depending on which integration you are using. In this example, we use the OnMessage
event on the Elmah.Io.Client
package to include the user's IP manually:
+
var userIp = "1.1.1.1"; // <-- just for the demo
+var client = ElmahioAPI.Create("API_KEY");
+client.Messages.OnMessage += (sender, args) =>
+{
+ var message = args.Message;
+ if (message.ServerVariables == null) message.ServerVariables = new List<Item>();
+ message.ServerVariables.Add(new Item("X-FORWARDED-FOR", userIp));
+};
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/how-to-get-the-sql-tab-to-show-up/index.html b/how-to-get-the-sql-tab-to-show-up/index.html
new file mode 100644
index 0000000000..1149869978
--- /dev/null
+++ b/how-to-get-the-sql-tab-to-show-up/index.html
@@ -0,0 +1,742 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ How to get the SQL tab to show up
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How to get the SQL tab to show up
+
The elmah.io UI can offer to show any SQL code part of the current context of logging a message. The code will show up in the log message details as a tab named SQL :
+
+
The tab shows a formatted view of the SQL code including any parameters and/or syntax errors. This can help debug exceptions thrown while executing SQL code against a relational database.
+
To make the SQL tab show up, custom information needs to be included in the Data
dictionary of a log message. The following sections will go into detail on how to include the custom information in various ways.
+
Entity Framework Core
+
Entity Framework Core is easy since it already includes any SQL code as part of the messages logged through Microsoft.Extensions.Logging's ILogger
. The SQL code and parameters are logged as two properties named commandText
and parameters
. elmah.io will automatically pick up these properties and show the SQL tab with the formatted results.
+
As a default, all values in parameters are not logged as part of the message. You will see this from values being set to ?
in the UI. To have the real values show up, you will need to enable sensitive data logging when setting up EF Core:
+
services.AddDbContext<Context>(options =>
+{
+ // Other code like: options.UseSqlServer(connectionString);
+ options.EnableSensitiveDataLogging(true); // ⬅️ Set this to true
+});
+
+
This should not be set if you include sensitive details like social security numbers, passwords, and similar as SQL query parameters.
+
Manually
+
If you want to attach SQL to a log message made manually, you can go one of two ways. The first way is to fill in the commandText
and parameters
Data entries shown above. When creating a message on Elmah.Io.Client
it could look like this:
+
client.Messages.CreateAndNotify(logId, new CreateMessage
+{
+ Title = "Log message with SQL attached",
+ Severity = Severity.Error.ToString(),
+ Data = new List<Item>
+ {
+ new Item
+ {
+ Key = "commandText",
+ Value = "SELECT * FROM USERS WHERE Name = @columnValue OR Name = @columnTwoValue"
+ },
+ new Item
+ {
+ Key = "parameters",
+ Value = "columnValue='Eduard' (Nullable = false) (Size = 6), columnTwoValue='Thomas' (Nullable = false) (Size = 6)"
+ },
+ },
+});
+
+
The value of the parameters
item needs to correspond to the format of that Entity Framework and the System.Data
namespace uses.
+
The second approach is to provide elmah.io with a single Data item named X-ELMAHIO-SQL
. The value of this item should be a JSON format as seen in the following example:
+
var sql = new
+{
+ Raw = "SELECT * FROM USERS WHERE Name = @columnValue OR Name = @columnTwoValue",
+ Parameters = new[]
+ {
+ new
+ {
+ IsNullable = false,
+ Size = 6,
+ Name = "columnValue",
+ Value = "Eduard"
+ },
+ new
+ {
+ IsNullable = false,
+ Size = 6,
+ Name = "columnTwoValue",
+ Value = "Thomas"
+ },
+ },
+};
+client.Messages.CreateAndNotify(logId, new CreateMessage
+{
+ Title = "Log message with SQL attached",
+ Severity = Severity.Error.ToString(),
+ Data = new List<Item>
+ {
+ new Item { Key = "X-ELMAHIO-SQL", Value = JsonConvert.SerializeObject(sql) },
+ },
+});
+
+
The JSON generated by serializing the anonymous object will look like this:
+
{
+ "Raw": "SELECT * FROM USERS WHERE Name = @columnValue OR Name = @columnTwoValue",
+ "Parameters": [
+ {
+ "IsNullable": false,
+ "Size": 6,
+ "Name": "columnValue",
+ "Value": "Eduard"
+ },
+ {
+ "IsNullable": false,
+ "Size": 6,
+ "Name": "columnTwoValue",
+ "Value": "Thomas"
+ }
+ ]
+}
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/how-to-include-source-code-in-log-messages/index.html b/how-to-include-source-code-in-log-messages/index.html
new file mode 100644
index 0000000000..7c8e2362ca
--- /dev/null
+++ b/how-to-include-source-code-in-log-messages/index.html
@@ -0,0 +1,740 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ How to include source code in log messages
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How to include source code in log messages
+
+
Sometimes, being able to see the exact code causing an error, is much more helpful than looking at other details around the current HTTP context and similar. If you often find yourself opening Visual Studio or Code to inspect the failing line, embedding source code in errors and log messages will speed up the process. In this article, you will learn how to configure elmah.io to include source code when logging messages using the Elmah.Io.Client.Extensions.SourceCode
NuGet package.
+
+The Elmah.Io.Client.Extensions.SourceCode
package requires Elmah.Io.Client
version 4.0
or newer.
+
+
No matter what integration you are using (with a few exceptions) you are using the Elmah.Io.Client
NuGet package to communicate with the elmah.io API. We have built a range of extensions for this package, to avoid including too many features not related to communicating with the API into the client package. One of them is for including source code when logging messages. Start by installing the Elmah.Io.Client.Extensions.SourceCode
NuGet package:
+
Install-Package Elmah.Io.Client.Extensions.SourceCode
+
dotnet add package Elmah.Io.Client.Extensions.SourceCode
+
<PackageReference Include="Elmah.Io.Client.Extensions.SourceCode" Version="5.*" />
+
paket add Elmah.Io.Client.Extensions.SourceCode
+
+
There are currently three ways of including source code with log messages. The first two ways require the Elmah.Io.Client.Extensions.SourceCode
package, while the third one can be done manually.
+
From the file system
+
This is the most simple approach meant for local development. When logging a stack trace from your local machine, the trace includes the absolute path to the file on your file system, as well as the line causing a log message (typically an error). To set this up, you will need to implement the OnMessage
event through the Elmah.Io.Client
package. Depending on which integration you are using, the name of that event or action can vary. What you are looking to do is to call the WithSourceCodeFromFileSystem
method on log messages you want to include source code. This is an example when using the Elmah.Io.Client
directly:
+
var elmahIoClient = ElmahioAPI.Create("API_KEY");
+elmahIoClient.Messages.OnMessage += (sender, e) => e.Message.WithSourceCodeFromFileSystem();
+
+
Using an integration like Elmah.Io.AspNetCore
uses the same method:
+
services.AddElmahIo(options =>
+{
+ options.OnMessage = msg => msg.WithSourceCodeFromFileSystem();
+});
+
+
This will automatically instruct Elmah.Io.Client.Extensions.Source
to try and parse any stack trace in the details property and embed the source code.
+
For an example of how to use the WithSourceCodeFromFileSystem
method, check out the following sample: Elmah.Io.Client.Extensions.SourceCode.FileSystem .
+
From the PDB file
+
When deploying your code on another environment, you typically don't have the original code available. If you copy your source code to the same absolute path as when building, you can use the file-system approach shown above. If not, embedding the source code in the PDB file can be the option. Before doing so, make sure you include filename and line numbers in stack traces on all environments as shown here: Include filename and line number in stack traces . For the old project template the Debugging information field needs a value of Portable
. For the new project template the Debug symbols field needs a value of PDB file, portable across platforms
.
+
To embed source code in the PDB file built alongside your DLL files, include the following property in your csproj
file:
+
<PropertyGroup>
+ <EmbedAllSources>true</EmbedAllSources>
+</PropertyGroup>
+
+
Be aware that this will include your original source code in your deployment which may not be a good approach if other people have access to the environment or binary files. Next, call the WithSourceCodeFromPdb
method:
+
var elmahIoClient = ElmahioAPI.Create("API_KEY");
+elmahIoClient.Messages.OnMessage += (sender, e) => e.Message.WithSourceCodeFromPdb();
+
+
For an example of how to do this from ASP.NET Core, you can use the same approach as specified in the previous section:
+
services.AddElmahIo(options =>
+{
+ options.OnMessage = msg => msg.WithSourceCodeFromPdb();
+});
+
+
All of our integrations support a message callback somehow.
+
For an example of how to use the WithSourceCodeFromPdb
method, check out the following sample: Elmah.Io.Client.Extensions.SourceCode.PdbSample for .NET and Elmah.Io.Client.Extensions.SourceCode.NetFrameworkPdb for .NET Framework.
+
Manually
+
In case you want to include source code manually, you can use the OnMessage
event and the Code
property on the CreateMessage
class:
+
var elmahIoClient = ElmahioAPI.Create("API_KEY");
+elmahIoClient.Messages.OnMessage += (sender, e) =>
+{
+ e.Message.Code = FetchCode();
+}
+
+
You will need to implement the FetchCode
method to return the source code to include. Only 21 lines of code are supported for now.
+
In case you want elmah.io to show the correct line numbers, you will need to tell us how the first line number in the provided code matches your original source file as well as the line number causing the error. This is done by adding two Item
s to the Data
dictionary on CreateMessage
:
+
var elmahIoClient = ElmahioAPI.Create("API_KEY");
+elmahIoClient.Messages.OnMessage += (sender, e) =>
+{
+ e.Message.Code = FetchCode();
+ if (e.Message.Data == null) e.Message.Data = new List<Item>();
+ e.Message.Data.Add(new Item("X-ELMAHIO-CODESTARTLINE", "42"));
+ e.Message.Data.Add(new Item("X-ELMAHIO-CODELINE", "51"));
+}
+
+
This will show line number 42
next to the first code line and highlight line number 51
in the elmah.io UI.
+
Troubleshooting
+
If no source code shows up on elmah.io log messages, you can start by running through the following checks:
+
+Make sure that the log message contains a stack trace in the details field.
+Make sure that the stack trace contains absolute path filenames and line numbers for the code causing the stack trace.
+Make sure that you are calling the WithSourceCodeFromPdb
or WithSourceCodeFromFileSystem
method.
+Make sure that the Elmah.Io.Client.Extensions.SourceCode.dll
file is in your deployed application.
+Make sure that your project has Portable
set in Debugging information or PDB File, portable across platforms
set in Debug symbols .
+For PDB files, make sure that you have included the EmbedAllSources
element in your csproj
file.
+Look inside the Data tab on the logged message. It may contain a key named X-ELMAHIO-CODEERROR
with a value explaining what went wrong.
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/how-to-manage-subscriptions-update-credit-cards-etc/index.html b/how-to-manage-subscriptions-update-credit-cards-etc/index.html
new file mode 100644
index 0000000000..0c2df7cc6c
--- /dev/null
+++ b/how-to-manage-subscriptions-update-credit-cards-etc/index.html
@@ -0,0 +1,667 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ How to manage subscriptions, update credit cards, etc.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How to manage subscriptions, update credit cards, etc.
+
Your subscription is managed from the organization settings page.
+
To open organization settings, click the gears icon next to your organization name on either the left menu or through the dashboard:
+
+
When on the organization settings page, click the Subscription tab. Your subscription can be managed by scrolling to the bottom of the page and looking for the currently active subscription:
+
+
In the following sections, we will go through each button.
+
Purchase top-up
+
If getting near your monthly log message limit you can purchase a top-up to avoid having to permanently upgrade to a larger plan. Top-ups are priced at $19 and will add 25,000 messages and 1,000 emails to your subscription for the rest of the calendar month.
+
Update Credit Card
+
If your recent payment failed or you received a new credit card from your bank, you can use this button to input the new credit card.
+
Cancel Subscription
+
To cancel your current subscription you can click the Cancel Subscription button. This will open the chat where we will guide you through the process. We don't use a manual offboarding process to try and convince your to stay or to make it hard for you to leave. When leaving a system like elmah.io, we need additional details from you like when you want to cancel (mostly geared towards annual subscriptions) and to make sure that you have backed up all data from elmah.io before it is cleaned up.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/how-to-rename-a-log/index.html b/how-to-rename-a-log/index.html
new file mode 100644
index 0000000000..f3f2601409
--- /dev/null
+++ b/how-to-rename-a-log/index.html
@@ -0,0 +1,658 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ How to rename a log
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How to rename a log
+
Logs can be renamed from the Dashboard by anyone with Administrator access to the log. To rename a log, click the small icon in the lower right corner:
+
+
When clicking the icon the log box will flip and you will be able to input a new name. The edit log box also lets you assign the log to an environment, subscribe/unsubscribe from emails, as well as change the color of the log. Be aware that changing the name, environment, or color will be visible for all users with access to the log.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/how-to-run-elmah-io-in-dark-mode/index.html b/how-to-run-elmah-io-in-dark-mode/index.html
new file mode 100644
index 0000000000..7ae96606dd
--- /dev/null
+++ b/how-to-run-elmah-io-in-dark-mode/index.html
@@ -0,0 +1,660 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ How to run elmah.io in dark mode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How to run elmah.io in dark mode
+
Some developers prefer applications with a dark mode either full-time or when working those late hours. The elmah.io application (app.elmah.io) has a light theme but can be run in dark mode using a browser extension. We recommend the following extensions for running elmah.io in dark mode:
+
Dark Mode - Night Eye - Chrome - Edge - Firefox
+
+
Dark Reader - Chrome - Edge - Firefox
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/how-to-search-custom-data/index.html b/how-to-search-custom-data/index.html
new file mode 100644
index 0000000000..8f203a10b3
--- /dev/null
+++ b/how-to-search-custom-data/index.html
@@ -0,0 +1,743 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ How to search custom data
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
How to search custom data
+
Custom data is not searchable by default. Sometimes it makes sense that errors can be searched from values logged as part of custom data. For now, this feature is supported through the use of variable naming, but we may extend this to a configuration option through the API or UI as well.
+
To make a custom variable and its value searchable through the UI (as well as through the API), name the variable with the prefix X-ELMAHIO-SEARCH-
. The variable will become searchable through the name added after the prefix.
+
Examples:
+
+
+
+
+
Elmah.ErrorLog.GetDefault(null);
+var logger = Elmah.Io.ErrorLog.Client;
+logger.OnMessage += (sender, args) =>
+{
+ if (args.Message.Data == null) args.Message.Data = new List<Item>();
+ args.Message.Data.Add(new Item { Key = "X-ELMAHIO-SEARCH-author", Value = "Walter Sobchak" });
+};
+
+
+
+
builder.Services.AddElmahIo(o =>
+{
+ o.OnMessage = message =>
+ {
+ if (message.Data == null) message.Data = new List<Item>();
+ message.Data.Add(new Item { Key = "X-ELMAHIO-SEARCH-author", Value = "Walter Sobchak" });
+ };
+});
+
+
+
+
using (LogContext.PushProperty("X-ELMAHIO-SEARCH-author", "Walter Sobchak"))
+{
+ logger.Error("You see what happens, Larry?");
+}
+
+
+
+
var errorMessage = new LogEventInfo(LogLevel.Error, "", "You see what happens, Larry?");
+errorMessage.Properties.Add("X-ELMAHIO-SEARCH-author", "Walter Sobchak");
+log.Error(errorMessage);
+
+
+
+
var properties = new PropertiesDictionary();
+properties["X-ELMAHIO-SEARCH-author"] = "Walter Sobchak";
+log.Logger.Log(new LoggingEvent(new LoggingEventData
+{
+ Level = Level.Error,
+ TimeStampUtc = DateTime.UtcNow,
+ Properties = properties,
+ Message = "You see what happens, Larry?",
+}));
+
+
+
+
var scope = new Dictionary<string, object> { { "X-ELMAHIO-SEARCH-author", "Walter Sobchak" } };
+using (logger.BeginScope(scope}))
+{
+ logger.LogError("You see what happens, Larry?");
+}
+
+
+
+
The examples will make author
searchable using this query:
+
data.author:"Walter Sobchak"
+
+
Observe how X-ELMAHIO-SEARCH-
is replaced with the data.
prefix when indexed in elmah.io.
+
Adding searchable properties is available when logging exceptions too:
+
try
+{
+ // ...
+}
+catch (NullReferenceException e)
+{
+ e.Data.Add("X-ELMAHIO-SEARCH-author", "Walter Sobchak");
+ // Log the exception or throw e to use this catch for decorating the exception
+}
+
+
To avoid someone filling up our cluster with custom data, only the first three variables containing X-ELMAHIO-SEARCH-
are made searchable. Also, variables with a value containing more than 256 characters are not indexed.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/images/add-api-key-and-log-id-to-application-settings-v2.png b/images/add-api-key-and-log-id-to-application-settings-v2.png
new file mode 100644
index 0000000000..87b0d52686
Binary files /dev/null and b/images/add-api-key-and-log-id-to-application-settings-v2.png differ
diff --git a/images/add-api-key-and-log-id-to-application-settings.png b/images/add-api-key-and-log-id-to-application-settings.png
new file mode 100644
index 0000000000..dc68fd7733
Binary files /dev/null and b/images/add-api-key-and-log-id-to-application-settings.png differ
diff --git a/images/add-powershell-task-v2.png b/images/add-powershell-task-v2.png
new file mode 100644
index 0000000000..cdbf9e5b6d
Binary files /dev/null and b/images/add-powershell-task-v2.png differ
diff --git a/images/add-site-extension-v2.png b/images/add-site-extension-v2.png
new file mode 100644
index 0000000000..ec1b5f550f
Binary files /dev/null and b/images/add-site-extension-v2.png differ
diff --git a/images/add-site-extension.png b/images/add-site-extension.png
new file mode 100644
index 0000000000..1fcde3707f
Binary files /dev/null and b/images/add-site-extension.png differ
diff --git a/images/add_elmah_io_deployment_step.png b/images/add_elmah_io_deployment_step.png
new file mode 100644
index 0000000000..144d4ac326
Binary files /dev/null and b/images/add_elmah_io_deployment_step.png differ
diff --git a/images/add_new_hipchat_rule.png b/images/add_new_hipchat_rule.png
new file mode 100644
index 0000000000..817af7393d
Binary files /dev/null and b/images/add_new_hipchat_rule.png differ
diff --git a/images/add_new_slack_rule.png b/images/add_new_slack_rule.png
new file mode 100644
index 0000000000..11e60b4127
Binary files /dev/null and b/images/add_new_slack_rule.png differ
diff --git a/images/add_powershell_task.png b/images/add_powershell_task.png
new file mode 100644
index 0000000000..cce6e613e6
Binary files /dev/null and b/images/add_powershell_task.png differ
diff --git a/images/add_step_template_to_octopus.png b/images/add_step_template_to_octopus.png
new file mode 100644
index 0000000000..16bee0bae1
Binary files /dev/null and b/images/add_step_template_to_octopus.png differ
diff --git a/images/add_user_to_org.png b/images/add_user_to_org.png
new file mode 100644
index 0000000000..ed30625765
Binary files /dev/null and b/images/add_user_to_org.png differ
diff --git a/images/admin_users_on_log.png b/images/admin_users_on_log.png
new file mode 100644
index 0000000000..8bf21d35e4
Binary files /dev/null and b/images/admin_users_on_log.png differ
diff --git a/images/advanced_build_settings.png b/images/advanced_build_settings.png
new file mode 100644
index 0000000000..75ab760521
Binary files /dev/null and b/images/advanced_build_settings.png differ
diff --git a/images/api-key-on-organization-settings-v2.png b/images/api-key-on-organization-settings-v2.png
new file mode 100644
index 0000000000..72a1fe1cf8
Binary files /dev/null and b/images/api-key-on-organization-settings-v2.png differ
diff --git a/images/api-key-on-organization-settings.png b/images/api-key-on-organization-settings.png
new file mode 100644
index 0000000000..9746906cd9
Binary files /dev/null and b/images/api-key-on-organization-settings.png differ
diff --git a/images/api-keys.png b/images/api-keys.png
new file mode 100644
index 0000000000..6ec5e131ff
Binary files /dev/null and b/images/api-keys.png differ
diff --git a/images/apps/azureboards/install-settings.png b/images/apps/azureboards/install-settings.png
new file mode 100644
index 0000000000..8cea053dfa
Binary files /dev/null and b/images/apps/azureboards/install-settings.png differ
diff --git a/images/apps/azureboards/personal-access-token.png b/images/apps/azureboards/personal-access-token.png
new file mode 100644
index 0000000000..26af7e8c7f
Binary files /dev/null and b/images/apps/azureboards/personal-access-token.png differ
diff --git a/images/apps/bitbucket/apikey.png b/images/apps/bitbucket/apikey.png
new file mode 100644
index 0000000000..c85ac430e9
Binary files /dev/null and b/images/apps/bitbucket/apikey.png differ
diff --git a/images/apps/bitbucket/app_password.png b/images/apps/bitbucket/app_password.png
new file mode 100644
index 0000000000..e8f3b1348e
Binary files /dev/null and b/images/apps/bitbucket/app_password.png differ
diff --git a/images/apps/bitbucket/install_settings.png b/images/apps/bitbucket/install_settings.png
new file mode 100644
index 0000000000..5c9cea180f
Binary files /dev/null and b/images/apps/bitbucket/install_settings.png differ
diff --git a/images/apps/bitbucket/install_settings_v2.png b/images/apps/bitbucket/install_settings_v2.png
new file mode 100644
index 0000000000..a8ffc5a69d
Binary files /dev/null and b/images/apps/bitbucket/install_settings_v2.png differ
diff --git a/images/apps/bitbucket/install_settings_v3.png b/images/apps/bitbucket/install_settings_v3.png
new file mode 100644
index 0000000000..24b6eb3003
Binary files /dev/null and b/images/apps/bitbucket/install_settings_v3.png differ
diff --git a/images/apps/chatgpt/chatgpt-install.png b/images/apps/chatgpt/chatgpt-install.png
new file mode 100644
index 0000000000..c3371838d8
Binary files /dev/null and b/images/apps/chatgpt/chatgpt-install.png differ
diff --git a/images/apps/clickup/copy-link.png b/images/apps/clickup/copy-link.png
new file mode 100644
index 0000000000..ae13532b9b
Binary files /dev/null and b/images/apps/clickup/copy-link.png differ
diff --git a/images/apps/clickup/install-settings.png b/images/apps/clickup/install-settings.png
new file mode 100644
index 0000000000..88f0eb2c2f
Binary files /dev/null and b/images/apps/clickup/install-settings.png differ
diff --git a/images/apps/github/generate_token.png b/images/apps/github/generate_token.png
new file mode 100644
index 0000000000..debbdf8b96
Binary files /dev/null and b/images/apps/github/generate_token.png differ
diff --git a/images/apps/github/generate_token_v2.png b/images/apps/github/generate_token_v2.png
new file mode 100644
index 0000000000..d2c96a0432
Binary files /dev/null and b/images/apps/github/generate_token_v2.png differ
diff --git a/images/apps/github/install_github.png b/images/apps/github/install_github.png
new file mode 100644
index 0000000000..e995896d53
Binary files /dev/null and b/images/apps/github/install_github.png differ
diff --git a/images/apps/gitlab/gitlab-access-token.png b/images/apps/gitlab/gitlab-access-token.png
new file mode 100644
index 0000000000..d8f82dbd54
Binary files /dev/null and b/images/apps/gitlab/gitlab-access-token.png differ
diff --git a/images/apps/gitlab/gitlab-settings.png b/images/apps/gitlab/gitlab-settings.png
new file mode 100644
index 0000000000..d2352fb7b0
Binary files /dev/null and b/images/apps/gitlab/gitlab-settings.png differ
diff --git a/images/apps/hipchat/generate_token.png b/images/apps/hipchat/generate_token.png
new file mode 100644
index 0000000000..20f13223ae
Binary files /dev/null and b/images/apps/hipchat/generate_token.png differ
diff --git a/images/apps/hipchat/install_hipchat.png b/images/apps/hipchat/install_hipchat.png
new file mode 100644
index 0000000000..e2fe844c2c
Binary files /dev/null and b/images/apps/hipchat/install_hipchat.png differ
diff --git a/images/apps/jira/install_settings.png b/images/apps/jira/install_settings.png
new file mode 100644
index 0000000000..01f6affc85
Binary files /dev/null and b/images/apps/jira/install_settings.png differ
diff --git a/images/apps/pagerduty/add-integration-v2.png b/images/apps/pagerduty/add-integration-v2.png
new file mode 100644
index 0000000000..0a5f7ad2dd
Binary files /dev/null and b/images/apps/pagerduty/add-integration-v2.png differ
diff --git a/images/apps/pagerduty/add-integration.png b/images/apps/pagerduty/add-integration.png
new file mode 100644
index 0000000000..5b8d0e4cb5
Binary files /dev/null and b/images/apps/pagerduty/add-integration.png differ
diff --git a/images/apps/pagerduty/install-pagerduty-on-elmahio.png b/images/apps/pagerduty/install-pagerduty-on-elmahio.png
new file mode 100644
index 0000000000..9bde414455
Binary files /dev/null and b/images/apps/pagerduty/install-pagerduty-on-elmahio.png differ
diff --git a/images/apps/pagerduty/integration-name-and-key-v2.png b/images/apps/pagerduty/integration-name-and-key-v2.png
new file mode 100644
index 0000000000..6e0eb23356
Binary files /dev/null and b/images/apps/pagerduty/integration-name-and-key-v2.png differ
diff --git a/images/apps/pagerduty/integration-name-and-key.png b/images/apps/pagerduty/integration-name-and-key.png
new file mode 100644
index 0000000000..3b93b2b811
Binary files /dev/null and b/images/apps/pagerduty/integration-name-and-key.png differ
diff --git a/images/apps/slack/generate_token.png b/images/apps/slack/generate_token.png
new file mode 100644
index 0000000000..9b8802c88e
Binary files /dev/null and b/images/apps/slack/generate_token.png differ
diff --git a/images/apps/slack/install_slack.png b/images/apps/slack/install_slack.png
new file mode 100644
index 0000000000..9b76356c53
Binary files /dev/null and b/images/apps/slack/install_slack.png differ
diff --git a/images/apps/teams/step1.png b/images/apps/teams/step1.png
new file mode 100644
index 0000000000..b518dc5d0b
Binary files /dev/null and b/images/apps/teams/step1.png differ
diff --git a/images/apps/teams/step2.png b/images/apps/teams/step2.png
new file mode 100644
index 0000000000..1146d792bc
Binary files /dev/null and b/images/apps/teams/step2.png differ
diff --git a/images/apps/teams/step3.png b/images/apps/teams/step3.png
new file mode 100644
index 0000000000..b408e5d0f7
Binary files /dev/null and b/images/apps/teams/step3.png differ
diff --git a/images/apps/teams/step4.png b/images/apps/teams/step4.png
new file mode 100644
index 0000000000..20e62cb354
Binary files /dev/null and b/images/apps/teams/step4.png differ
diff --git a/images/apps/teams/step5.png b/images/apps/teams/step5.png
new file mode 100644
index 0000000000..0cf30e7e27
Binary files /dev/null and b/images/apps/teams/step5.png differ
diff --git a/images/apps/trello/install_settings.png b/images/apps/trello/install_settings.png
new file mode 100644
index 0000000000..8564140505
Binary files /dev/null and b/images/apps/trello/install_settings.png differ
diff --git a/images/apps/trello/trello-list-id.png b/images/apps/trello/trello-list-id.png
new file mode 100644
index 0000000000..8a595026e4
Binary files /dev/null and b/images/apps/trello/trello-list-id.png differ
diff --git a/images/apps/twilio/install-twilio-app.png b/images/apps/twilio/install-twilio-app.png
new file mode 100644
index 0000000000..9e1dfe9e8d
Binary files /dev/null and b/images/apps/twilio/install-twilio-app.png differ
diff --git a/images/apps/webclient/install.png b/images/apps/webclient/install.png
new file mode 100644
index 0000000000..b3b828ceb2
Binary files /dev/null and b/images/apps/webclient/install.png differ
diff --git a/images/apps/youtrack/generate_permanent_token.png b/images/apps/youtrack/generate_permanent_token.png
new file mode 100644
index 0000000000..431067b9b9
Binary files /dev/null and b/images/apps/youtrack/generate_permanent_token.png differ
diff --git a/images/apps/youtrack/generate_permanent_token_v2.png b/images/apps/youtrack/generate_permanent_token_v2.png
new file mode 100644
index 0000000000..fa59a20bdf
Binary files /dev/null and b/images/apps/youtrack/generate_permanent_token_v2.png differ
diff --git a/images/apps/youtrack/install_youtrack_app.png b/images/apps/youtrack/install_youtrack_app.png
new file mode 100644
index 0000000000..12bded4ea7
Binary files /dev/null and b/images/apps/youtrack/install_youtrack_app.png differ
diff --git a/images/apps/youtrack/install_youtrack_app_v2.png b/images/apps/youtrack/install_youtrack_app_v2.png
new file mode 100644
index 0000000000..dcbeb07e0e
Binary files /dev/null and b/images/apps/youtrack/install_youtrack_app_v2.png differ
diff --git a/images/apps/youtrack/install_youtrack_app_v3.png b/images/apps/youtrack/install_youtrack_app_v3.png
new file mode 100644
index 0000000000..9b6a387c48
Binary files /dev/null and b/images/apps/youtrack/install_youtrack_app_v3.png differ
diff --git a/images/authorize_elmah_io_and_zapier.png b/images/authorize_elmah_io_and_zapier.png
new file mode 100644
index 0000000000..5ef01a17a8
Binary files /dev/null and b/images/authorize_elmah_io_and_zapier.png differ
diff --git a/images/aws-environment-properties-core.png b/images/aws-environment-properties-core.png
new file mode 100644
index 0000000000..071b016168
Binary files /dev/null and b/images/aws-environment-properties-core.png differ
diff --git a/images/aws-environment-properties-v2.png b/images/aws-environment-properties-v2.png
new file mode 100644
index 0000000000..b9ab978618
Binary files /dev/null and b/images/aws-environment-properties-v2.png differ
diff --git a/images/aws-environment-properties.png b/images/aws-environment-properties.png
new file mode 100644
index 0000000000..1f7325e013
Binary files /dev/null and b/images/aws-environment-properties.png differ
diff --git a/images/azure-application-settings-v2.png b/images/azure-application-settings-v2.png
new file mode 100644
index 0000000000..4c602d1200
Binary files /dev/null and b/images/azure-application-settings-v2.png differ
diff --git a/images/azure-application-settings.png b/images/azure-application-settings.png
new file mode 100644
index 0000000000..22bde7977d
Binary files /dev/null and b/images/azure-application-settings.png differ
diff --git a/images/bamboo.png b/images/bamboo.png
new file mode 100644
index 0000000000..f3d6f315f2
Binary files /dev/null and b/images/bamboo.png differ
diff --git a/images/bounced-email.png b/images/bounced-email.png
new file mode 100644
index 0000000000..4e2551ea1c
Binary files /dev/null and b/images/bounced-email.png differ
diff --git a/images/choose_a_trigger_and_action.png b/images/choose_a_trigger_and_action.png
new file mode 100644
index 0000000000..f45ce74630
Binary files /dev/null and b/images/choose_a_trigger_and_action.png differ
diff --git a/images/choose_a_trigger_and_action2.png b/images/choose_a_trigger_and_action2.png
new file mode 100644
index 0000000000..f45ce74630
Binary files /dev/null and b/images/choose_a_trigger_and_action2.png differ
diff --git a/images/choose_a_trigger_and_action_filled.png b/images/choose_a_trigger_and_action_filled.png
new file mode 100644
index 0000000000..b6a4f76ed2
Binary files /dev/null and b/images/choose_a_trigger_and_action_filled.png differ
diff --git a/images/choose_elmah_io_account.png b/images/choose_elmah_io_account.png
new file mode 100644
index 0000000000..693ae7053a
Binary files /dev/null and b/images/choose_elmah_io_account.png differ
diff --git a/images/choose_elmah_io_account2.png b/images/choose_elmah_io_account2.png
new file mode 100644
index 0000000000..43f8bf5a6f
Binary files /dev/null and b/images/choose_elmah_io_account2.png differ
diff --git a/images/connect_elmah_io_account.png b/images/connect_elmah_io_account.png
new file mode 100644
index 0000000000..0339d06da7
Binary files /dev/null and b/images/connect_elmah_io_account.png differ
diff --git a/images/context_properties_from_log4net.png b/images/context_properties_from_log4net.png
new file mode 100644
index 0000000000..8b2112e7d6
Binary files /dev/null and b/images/context_properties_from_log4net.png differ
diff --git a/images/copy_log_id_dialog.png b/images/copy_log_id_dialog.png
new file mode 100644
index 0000000000..241e3be369
Binary files /dev/null and b/images/copy_log_id_dialog.png differ
diff --git a/images/correlation-id-filter.png b/images/correlation-id-filter.png
new file mode 100644
index 0000000000..83962dd6fb
Binary files /dev/null and b/images/correlation-id-filter.png differ
diff --git a/images/create-cloudflare-firewall-rule.png b/images/create-cloudflare-firewall-rule.png
new file mode 100644
index 0000000000..0461364cbf
Binary files /dev/null and b/images/create-cloudflare-firewall-rule.png differ
diff --git a/images/create-heartbeat-v2.png b/images/create-heartbeat-v2.png
new file mode 100644
index 0000000000..1e2301cabd
Binary files /dev/null and b/images/create-heartbeat-v2.png differ
diff --git a/images/create-heartbeat.png b/images/create-heartbeat.png
new file mode 100644
index 0000000000..d74415ffa1
Binary files /dev/null and b/images/create-heartbeat.png differ
diff --git a/images/create-heartbeats-api-key-v2.png b/images/create-heartbeats-api-key-v2.png
new file mode 100644
index 0000000000..0f159c3767
Binary files /dev/null and b/images/create-heartbeats-api-key-v2.png differ
diff --git a/images/create-heartbeats-api-key.png b/images/create-heartbeats-api-key.png
new file mode 100644
index 0000000000..53652137df
Binary files /dev/null and b/images/create-heartbeats-api-key.png differ
diff --git a/images/create_aspnetmvc_website.png b/images/create_aspnetmvc_website.png
new file mode 100644
index 0000000000..b4f1135b8b
Binary files /dev/null and b/images/create_aspnetmvc_website.png differ
diff --git a/images/create_ignore_rule.png b/images/create_ignore_rule.png
new file mode 100644
index 0000000000..ea38b0cb43
Binary files /dev/null and b/images/create_ignore_rule.png differ
diff --git a/images/create_new_log.png b/images/create_new_log.png
new file mode 100644
index 0000000000..0ea5b5110c
Binary files /dev/null and b/images/create_new_log.png differ
diff --git a/images/create_requestbin.png b/images/create_requestbin.png
new file mode 100644
index 0000000000..d36dc6252b
Binary files /dev/null and b/images/create_requestbin.png differ
diff --git a/images/deploy-notification/marketplace_get_it_free.png b/images/deploy-notification/marketplace_get_it_free.png
new file mode 100644
index 0000000000..fdf409361e
Binary files /dev/null and b/images/deploy-notification/marketplace_get_it_free.png differ
diff --git a/images/deploy-notification/marketplace_select_organization.png b/images/deploy-notification/marketplace_select_organization.png
new file mode 100644
index 0000000000..12d0aafb4b
Binary files /dev/null and b/images/deploy-notification/marketplace_select_organization.png differ
diff --git a/images/deploy-notification/octopus-step-template-v2.png b/images/deploy-notification/octopus-step-template-v2.png
new file mode 100644
index 0000000000..1c6b5e5144
Binary files /dev/null and b/images/deploy-notification/octopus-step-template-v2.png differ
diff --git a/images/deploy-notification/octopus_search_step_template-v2.png b/images/deploy-notification/octopus_search_step_template-v2.png
new file mode 100644
index 0000000000..1bb4847cbb
Binary files /dev/null and b/images/deploy-notification/octopus_search_step_template-v2.png differ
diff --git a/images/deploy-notification/octopus_search_step_template.png b/images/deploy-notification/octopus_search_step_template.png
new file mode 100644
index 0000000000..aa6daa9ae2
Binary files /dev/null and b/images/deploy-notification/octopus_search_step_template.png differ
diff --git a/images/deploy-notification/octopus_step_template.png b/images/deploy-notification/octopus_step_template.png
new file mode 100644
index 0000000000..a46528bbf0
Binary files /dev/null and b/images/deploy-notification/octopus_step_template.png differ
diff --git a/images/deploy-notification/release-pipeline-task-v2.png b/images/deploy-notification/release-pipeline-task-v2.png
new file mode 100644
index 0000000000..6fbd45a060
Binary files /dev/null and b/images/deploy-notification/release-pipeline-task-v2.png differ
diff --git a/images/deploy-notification/release_pipeline_task.png b/images/deploy-notification/release_pipeline_task.png
new file mode 100644
index 0000000000..6fe13f50e7
Binary files /dev/null and b/images/deploy-notification/release_pipeline_task.png differ
diff --git a/images/deployment-tracking-api-key-v2.png b/images/deployment-tracking-api-key-v2.png
new file mode 100644
index 0000000000..d4d05d2334
Binary files /dev/null and b/images/deployment-tracking-api-key-v2.png differ
diff --git a/images/deployment-tracking-api-key.png b/images/deployment-tracking-api-key.png
new file mode 100644
index 0000000000..fe90bce27f
Binary files /dev/null and b/images/deployment-tracking-api-key.png differ
diff --git a/images/deployments_post.png b/images/deployments_post.png
new file mode 100644
index 0000000000..3c8f2e3f49
Binary files /dev/null and b/images/deployments_post.png differ
diff --git a/images/detailed-usage-report-v2.png b/images/detailed-usage-report-v2.png
new file mode 100644
index 0000000000..7a4b10d2df
Binary files /dev/null and b/images/detailed-usage-report-v2.png differ
diff --git a/images/detailed-usage-report.png b/images/detailed-usage-report.png
new file mode 100644
index 0000000000..ae82a649aa
Binary files /dev/null and b/images/detailed-usage-report.png differ
diff --git a/images/edit-api-key.png b/images/edit-api-key.png
new file mode 100644
index 0000000000..6842c29c0e
Binary files /dev/null and b/images/edit-api-key.png differ
diff --git a/images/edit_api_key.png b/images/edit_api_key.png
new file mode 100644
index 0000000000..c625196056
Binary files /dev/null and b/images/edit_api_key.png differ
diff --git a/images/elmah_io_account_selected_on_zapier.png b/images/elmah_io_account_selected_on_zapier.png
new file mode 100644
index 0000000000..b48ae7b578
Binary files /dev/null and b/images/elmah_io_account_selected_on_zapier.png differ
diff --git a/images/elmah_io_and_trigger_selected.png b/images/elmah_io_and_trigger_selected.png
new file mode 100644
index 0000000000..cb60a674e2
Binary files /dev/null and b/images/elmah_io_and_trigger_selected.png differ
diff --git a/images/elmah_io_error_on_hipchat.png b/images/elmah_io_error_on_hipchat.png
new file mode 100644
index 0000000000..0e63fb4cc5
Binary files /dev/null and b/images/elmah_io_error_on_hipchat.png differ
diff --git a/images/elmah_io_error_on_slack.png b/images/elmah_io_error_on_slack.png
new file mode 100644
index 0000000000..709935b9d6
Binary files /dev/null and b/images/elmah_io_error_on_slack.png differ
diff --git a/images/elmah_io_vs1.png b/images/elmah_io_vs1.png
new file mode 100644
index 0000000000..7b56260abe
Binary files /dev/null and b/images/elmah_io_vs1.png differ
diff --git a/images/elmah_io_vs2.png b/images/elmah_io_vs2.png
new file mode 100644
index 0000000000..25951e712f
Binary files /dev/null and b/images/elmah_io_vs2.png differ
diff --git a/images/elmah_io_vs3.png b/images/elmah_io_vs3.png
new file mode 100644
index 0000000000..3109599ebf
Binary files /dev/null and b/images/elmah_io_vs3.png differ
diff --git a/images/elmah_io_vs4.png b/images/elmah_io_vs4.png
new file mode 100644
index 0000000000..78eb19f456
Binary files /dev/null and b/images/elmah_io_vs4.png differ
diff --git a/images/elmahio-darkreader.png b/images/elmahio-darkreader.png
new file mode 100644
index 0000000000..f78c2f863e
Binary files /dev/null and b/images/elmahio-darkreader.png differ
diff --git a/images/elmahio-nighteye.png b/images/elmahio-nighteye.png
new file mode 100644
index 0000000000..7b23c1a1ac
Binary files /dev/null and b/images/elmahio-nighteye.png differ
diff --git a/images/enable-two-factor.png b/images/enable-two-factor.png
new file mode 100644
index 0000000000..5893ce0d8d
Binary files /dev/null and b/images/enable-two-factor.png differ
diff --git a/images/enabled_disable_log.png b/images/enabled_disable_log.png
new file mode 100644
index 0000000000..0b26d76b49
Binary files /dev/null and b/images/enabled_disable_log.png differ
diff --git a/images/environments-dropdown.png b/images/environments-dropdown.png
new file mode 100644
index 0000000000..ec7d0b7378
Binary files /dev/null and b/images/environments-dropdown.png differ
diff --git a/images/environments.png b/images/environments.png
new file mode 100644
index 0000000000..79c34bc30b
Binary files /dev/null and b/images/environments.png differ
diff --git a/images/error-details-v2.png b/images/error-details-v2.png
new file mode 100644
index 0000000000..68b418231c
Binary files /dev/null and b/images/error-details-v2.png differ
diff --git a/images/error_details.png b/images/error_details.png
new file mode 100644
index 0000000000..51c6d871a2
Binary files /dev/null and b/images/error_details.png differ
diff --git a/images/errorsinumbraco.png b/images/errorsinumbraco.png
new file mode 100644
index 0000000000..ce94b3e4b6
Binary files /dev/null and b/images/errorsinumbraco.png differ
diff --git a/images/exclude_generated_debug_symbols.png b/images/exclude_generated_debug_symbols.png
new file mode 100644
index 0000000000..34ba53dfc9
Binary files /dev/null and b/images/exclude_generated_debug_symbols.png differ
diff --git a/images/extended-user-details-v2.png b/images/extended-user-details-v2.png
new file mode 100644
index 0000000000..fd7faa20f1
Binary files /dev/null and b/images/extended-user-details-v2.png differ
diff --git a/images/extended_user_details.png b/images/extended_user_details.png
new file mode 100644
index 0000000000..19b7fc30c9
Binary files /dev/null and b/images/extended_user_details.png differ
diff --git a/images/fill-powershell-task-v2.png b/images/fill-powershell-task-v2.png
new file mode 100644
index 0000000000..d3b9929127
Binary files /dev/null and b/images/fill-powershell-task-v2.png differ
diff --git a/images/fill_powershell_task.png b/images/fill_powershell_task.png
new file mode 100644
index 0000000000..fa6743e8da
Binary files /dev/null and b/images/fill_powershell_task.png differ
diff --git a/images/filter_elmah_io_triggers.png b/images/filter_elmah_io_triggers.png
new file mode 100644
index 0000000000..c78a6002c3
Binary files /dev/null and b/images/filter_elmah_io_triggers.png differ
diff --git a/images/filters.png b/images/filters.png
new file mode 100644
index 0000000000..6a81acf667
Binary files /dev/null and b/images/filters.png differ
diff --git a/images/full-text-search-v2.png b/images/full-text-search-v2.png
new file mode 100644
index 0000000000..8f7def6b9f
Binary files /dev/null and b/images/full-text-search-v2.png differ
diff --git a/images/full-text-search.png b/images/full-text-search.png
new file mode 100644
index 0000000000..d4911c02e2
Binary files /dev/null and b/images/full-text-search.png differ
diff --git a/images/gitlab-access-token.png b/images/gitlab-access-token.png
new file mode 100644
index 0000000000..51c1799190
Binary files /dev/null and b/images/gitlab-access-token.png differ
diff --git a/images/gitlab-settings.png b/images/gitlab-settings.png
new file mode 100644
index 0000000000..d2352fb7b0
Binary files /dev/null and b/images/gitlab-settings.png differ
diff --git a/images/gmail-never-send-it-to-spam.png b/images/gmail-never-send-it-to-spam.png
new file mode 100644
index 0000000000..663349161f
Binary files /dev/null and b/images/gmail-never-send-it-to-spam.png differ
diff --git a/images/gmail-search-all-conversations.png b/images/gmail-search-all-conversations.png
new file mode 100644
index 0000000000..a26fb41e24
Binary files /dev/null and b/images/gmail-search-all-conversations.png differ
diff --git a/images/hipchat_api_page.png b/images/hipchat_api_page.png
new file mode 100644
index 0000000000..eea469be0f
Binary files /dev/null and b/images/hipchat_api_page.png differ
diff --git a/images/ignore-like-this-v2.png b/images/ignore-like-this-v2.png
new file mode 100644
index 0000000000..d786c33114
Binary files /dev/null and b/images/ignore-like-this-v2.png differ
diff --git a/images/ignore_like_this.png b/images/ignore_like_this.png
new file mode 100644
index 0000000000..6d73451e05
Binary files /dev/null and b/images/ignore_like_this.png differ
diff --git a/images/input_log_id.png b/images/input_log_id.png
new file mode 100644
index 0000000000..cdf013236c
Binary files /dev/null and b/images/input_log_id.png differ
diff --git a/images/invoices-tab-v2.png b/images/invoices-tab-v2.png
new file mode 100644
index 0000000000..dd60d623c5
Binary files /dev/null and b/images/invoices-tab-v2.png differ
diff --git a/images/invoices-tab.png b/images/invoices-tab.png
new file mode 100644
index 0000000000..ed23a3e707
Binary files /dev/null and b/images/invoices-tab.png differ
diff --git a/images/ipfiltersettings.png b/images/ipfiltersettings.png
new file mode 100644
index 0000000000..b6fff68ec1
Binary files /dev/null and b/images/ipfiltersettings.png differ
diff --git a/images/isbot-log-messages.png b/images/isbot-log-messages.png
new file mode 100644
index 0000000000..f858b7a18e
Binary files /dev/null and b/images/isbot-log-messages.png differ
diff --git a/images/log-settings-page-v2.png b/images/log-settings-page-v2.png
new file mode 100644
index 0000000000..04795c50bc
Binary files /dev/null and b/images/log-settings-page-v2.png differ
diff --git a/images/log-settings-page.png b/images/log-settings-page.png
new file mode 100644
index 0000000000..161474565c
Binary files /dev/null and b/images/log-settings-page.png differ
diff --git a/images/log-settings-v2.png b/images/log-settings-v2.png
new file mode 100644
index 0000000000..6c1a6b647d
Binary files /dev/null and b/images/log-settings-v2.png differ
diff --git a/images/log-settings.png b/images/log-settings.png
new file mode 100644
index 0000000000..053194ff19
Binary files /dev/null and b/images/log-settings.png differ
diff --git a/images/looking_at_custom_variables.gif b/images/looking_at_custom_variables.gif
new file mode 100644
index 0000000000..004f421a4a
Binary files /dev/null and b/images/looking_at_custom_variables.gif differ
diff --git a/images/mailmansettings.png b/images/mailmansettings.png
new file mode 100644
index 0000000000..452e5985c3
Binary files /dev/null and b/images/mailmansettings.png differ
diff --git a/images/manage-email-access.png b/images/manage-email-access.png
new file mode 100644
index 0000000000..f0ef58ad54
Binary files /dev/null and b/images/manage-email-access.png differ
diff --git a/images/manage-log-access-v2.png b/images/manage-log-access-v2.png
new file mode 100644
index 0000000000..a34bf721f5
Binary files /dev/null and b/images/manage-log-access-v2.png differ
diff --git a/images/manage-log-access.png b/images/manage-log-access.png
new file mode 100644
index 0000000000..c650ada631
Binary files /dev/null and b/images/manage-log-access.png differ
diff --git a/images/manage-subscription.png b/images/manage-subscription.png
new file mode 100644
index 0000000000..0171226894
Binary files /dev/null and b/images/manage-subscription.png differ
diff --git a/images/managing-environments.png b/images/managing-environments.png
new file mode 100644
index 0000000000..ad92bf4323
Binary files /dev/null and b/images/managing-environments.png differ
diff --git a/images/mark-message-with-isbot.png b/images/mark-message-with-isbot.png
new file mode 100644
index 0000000000..16d9d791f7
Binary files /dev/null and b/images/mark-message-with-isbot.png differ
diff --git a/images/marketplace_get_it_free.png b/images/marketplace_get_it_free.png
new file mode 100644
index 0000000000..3a4ee96f57
Binary files /dev/null and b/images/marketplace_get_it_free.png differ
diff --git a/images/marketplace_select_organization.png b/images/marketplace_select_organization.png
new file mode 100644
index 0000000000..3f064e30c4
Binary files /dev/null and b/images/marketplace_select_organization.png differ
diff --git a/images/match_up_elmah_io_error_to_github_issue.png b/images/match_up_elmah_io_error_to_github_issue.png
new file mode 100644
index 0000000000..faba16db29
Binary files /dev/null and b/images/match_up_elmah_io_error_to_github_issue.png differ
diff --git a/images/modheader.png b/images/modheader.png
new file mode 100644
index 0000000000..39f6299f6a
Binary files /dev/null and b/images/modheader.png differ
diff --git a/images/name_and_turn_this_zap_on.png b/images/name_and_turn_this_zap_on.png
new file mode 100644
index 0000000000..11fee3ef5a
Binary files /dev/null and b/images/name_and_turn_this_zap_on.png differ
diff --git a/images/new-event-filter.png b/images/new-event-filter.png
new file mode 100644
index 0000000000..e2cad8fa01
Binary files /dev/null and b/images/new-event-filter.png differ
diff --git a/images/new-isbot-rule.png b/images/new-isbot-rule.png
new file mode 100644
index 0000000000..fb0d5961de
Binary files /dev/null and b/images/new-isbot-rule.png differ
diff --git a/images/no-heartbeats-v2.png b/images/no-heartbeats-v2.png
new file mode 100644
index 0000000000..55aad85c74
Binary files /dev/null and b/images/no-heartbeats-v2.png differ
diff --git a/images/no-heartbeats.png b/images/no-heartbeats.png
new file mode 100644
index 0000000000..e148596cef
Binary files /dev/null and b/images/no-heartbeats.png differ
diff --git a/images/octopus_deploy_library.png b/images/octopus_deploy_library.png
new file mode 100644
index 0000000000..eed1f3359c
Binary files /dev/null and b/images/octopus_deploy_library.png differ
diff --git a/images/octopus_upload_source_map-v2.png b/images/octopus_upload_source_map-v2.png
new file mode 100644
index 0000000000..aeaa3e4bdf
Binary files /dev/null and b/images/octopus_upload_source_map-v2.png differ
diff --git a/images/octopus_upload_source_map.png b/images/octopus_upload_source_map.png
new file mode 100644
index 0000000000..b9bbbaecea
Binary files /dev/null and b/images/octopus_upload_source_map.png differ
diff --git a/images/open_manage_nuget_packages.png b/images/open_manage_nuget_packages.png
new file mode 100644
index 0000000000..498b897f39
Binary files /dev/null and b/images/open_manage_nuget_packages.png differ
diff --git a/images/organisation_settings-v2.png b/images/organisation_settings-v2.png
new file mode 100644
index 0000000000..b5de67c8e1
Binary files /dev/null and b/images/organisation_settings-v2.png differ
diff --git a/images/organisation_settings.png b/images/organisation_settings.png
new file mode 100644
index 0000000000..55b80694bc
Binary files /dev/null and b/images/organisation_settings.png differ
diff --git a/images/organization-settings-v2.png b/images/organization-settings-v2.png
new file mode 100644
index 0000000000..9c77e30d59
Binary files /dev/null and b/images/organization-settings-v2.png differ
diff --git a/images/organization-settings.png b/images/organization-settings.png
new file mode 100644
index 0000000000..219bebf8d2
Binary files /dev/null and b/images/organization-settings.png differ
diff --git a/images/permalink-search-result.png b/images/permalink-search-result.png
new file mode 100644
index 0000000000..929a640fb9
Binary files /dev/null and b/images/permalink-search-result.png differ
diff --git a/images/permalink.png b/images/permalink.png
new file mode 100644
index 0000000000..caae4df346
Binary files /dev/null and b/images/permalink.png differ
diff --git a/images/pick-environment.png b/images/pick-environment.png
new file mode 100644
index 0000000000..c315c2e975
Binary files /dev/null and b/images/pick-environment.png differ
diff --git a/images/pipedream-add-a-trigger.png b/images/pipedream-add-a-trigger.png
new file mode 100644
index 0000000000..e985dad986
Binary files /dev/null and b/images/pipedream-add-a-trigger.png differ
diff --git a/images/pipedream-create-a-new-connection.png b/images/pipedream-create-a-new-connection.png
new file mode 100644
index 0000000000..2ac9c67db3
Binary files /dev/null and b/images/pipedream-create-a-new-connection.png differ
diff --git a/images/pipedream-create-trigger-done.png b/images/pipedream-create-trigger-done.png
new file mode 100644
index 0000000000..a030abcef1
Binary files /dev/null and b/images/pipedream-create-trigger-done.png differ
diff --git a/images/pipedream-create-trigger.png b/images/pipedream-create-trigger.png
new file mode 100644
index 0000000000..a6cbaebdb4
Binary files /dev/null and b/images/pipedream-create-trigger.png differ
diff --git a/images/pipedream-select-event.png b/images/pipedream-select-event.png
new file mode 100644
index 0000000000..6e63f5e430
Binary files /dev/null and b/images/pipedream-select-event.png differ
diff --git a/images/pipelines-environment-variable-v2.png b/images/pipelines-environment-variable-v2.png
new file mode 100644
index 0000000000..9c6d0b23b9
Binary files /dev/null and b/images/pipelines-environment-variable-v2.png differ
diff --git a/images/pipelines_environment_variable.png b/images/pipelines_environment_variable.png
new file mode 100644
index 0000000000..8c166d23d9
Binary files /dev/null and b/images/pipelines_environment_variable.png differ
diff --git a/images/release_pipeline_task.png b/images/release_pipeline_task.png
new file mode 100644
index 0000000000..161bc34337
Binary files /dev/null and b/images/release_pipeline_task.png differ
diff --git a/images/rename-log-v2.png b/images/rename-log-v2.png
new file mode 100644
index 0000000000..5a55bf473b
Binary files /dev/null and b/images/rename-log-v2.png differ
diff --git a/images/rename-log.png b/images/rename-log.png
new file mode 100644
index 0000000000..8f55957ba9
Binary files /dev/null and b/images/rename-log.png differ
diff --git a/images/requestbin_message.png b/images/requestbin_message.png
new file mode 100644
index 0000000000..2553c5cc1a
Binary files /dev/null and b/images/requestbin_message.png differ
diff --git a/images/requestbin_settings.png b/images/requestbin_settings.png
new file mode 100644
index 0000000000..fcf5d7162b
Binary files /dev/null and b/images/requestbin_settings.png differ
diff --git a/images/roslyn-analyzers.png b/images/roslyn-analyzers.png
new file mode 100644
index 0000000000..2da1ce6932
Binary files /dev/null and b/images/roslyn-analyzers.png differ
diff --git a/images/rulestab.png b/images/rulestab.png
new file mode 100644
index 0000000000..c0c6635f00
Binary files /dev/null and b/images/rulestab.png differ
diff --git a/images/save_notification_step.png b/images/save_notification_step.png
new file mode 100644
index 0000000000..2658b00e46
Binary files /dev/null and b/images/save_notification_step.png differ
diff --git a/images/search-by-isbot.png b/images/search-by-isbot.png
new file mode 100644
index 0000000000..657cb059b7
Binary files /dev/null and b/images/search-by-isbot.png differ
diff --git a/images/search-filters-error-details-v2.gif b/images/search-filters-error-details-v2.gif
new file mode 100644
index 0000000000..a42685db7f
Binary files /dev/null and b/images/search-filters-error-details-v2.gif differ
diff --git a/images/search-filters-error-details-v2.png b/images/search-filters-error-details-v2.png
new file mode 100644
index 0000000000..c4dfa9199d
Binary files /dev/null and b/images/search-filters-error-details-v2.png differ
diff --git a/images/search-filters-error-details.gif b/images/search-filters-error-details.gif
new file mode 100644
index 0000000000..6ee2718f75
Binary files /dev/null and b/images/search-filters-error-details.gif differ
diff --git a/images/search-filters-error-details.png b/images/search-filters-error-details.png
new file mode 100644
index 0000000000..96455fb7b7
Binary files /dev/null and b/images/search-filters-error-details.png differ
diff --git a/images/search-filters-v2.gif b/images/search-filters-v2.gif
new file mode 100644
index 0000000000..fe64dabe2a
Binary files /dev/null and b/images/search-filters-v2.gif differ
diff --git a/images/search-filters-v2.png b/images/search-filters-v2.png
new file mode 100644
index 0000000000..6d4c52d09f
Binary files /dev/null and b/images/search-filters-v2.png differ
diff --git a/images/search-filters.gif b/images/search-filters.gif
new file mode 100644
index 0000000000..de3b78ce52
Binary files /dev/null and b/images/search-filters.gif differ
diff --git a/images/search-filters.png b/images/search-filters.png
new file mode 100644
index 0000000000..a109961529
Binary files /dev/null and b/images/search-filters.png differ
diff --git a/images/search_for_elmah_io.png b/images/search_for_elmah_io.png
new file mode 100644
index 0000000000..ba9e4dc0ab
Binary files /dev/null and b/images/search_for_elmah_io.png differ
diff --git a/images/select-elmah-io-site-extension-v2.png b/images/select-elmah-io-site-extension-v2.png
new file mode 100644
index 0000000000..bc1a66a729
Binary files /dev/null and b/images/select-elmah-io-site-extension-v2.png differ
diff --git a/images/select-elmah-io-site-extension.png b/images/select-elmah-io-site-extension.png
new file mode 100644
index 0000000000..4dcf410ff6
Binary files /dev/null and b/images/select-elmah-io-site-extension.png differ
diff --git a/images/select_log_on_zapier.png b/images/select_log_on_zapier.png
new file mode 100644
index 0000000000..40f15e915b
Binary files /dev/null and b/images/select_log_on_zapier.png differ
diff --git a/images/select_project_template.png b/images/select_project_template.png
new file mode 100644
index 0000000000..85c7b089a1
Binary files /dev/null and b/images/select_project_template.png differ
diff --git a/images/send_http_request_to_hipchat.png b/images/send_http_request_to_hipchat.png
new file mode 100644
index 0000000000..bae458da5b
Binary files /dev/null and b/images/send_http_request_to_hipchat.png differ
diff --git a/images/send_http_request_to_slack.png b/images/send_http_request_to_slack.png
new file mode 100644
index 0000000000..90220a828b
Binary files /dev/null and b/images/send_http_request_to_slack.png differ
diff --git a/images/sign_into_elmah_io_zapier_popup.png b/images/sign_into_elmah_io_zapier_popup.png
new file mode 100644
index 0000000000..27ad0339d0
Binary files /dev/null and b/images/sign_into_elmah_io_zapier_popup.png differ
diff --git a/images/slack_allow_access.png b/images/slack_allow_access.png
new file mode 100644
index 0000000000..56890a7dca
Binary files /dev/null and b/images/slack_allow_access.png differ
diff --git a/images/slack_authentication_page.png b/images/slack_authentication_page.png
new file mode 100644
index 0000000000..29f1fc1b64
Binary files /dev/null and b/images/slack_authentication_page.png differ
diff --git a/images/slack_select_channel-v2.png b/images/slack_select_channel-v2.png
new file mode 100644
index 0000000000..fb0e50abde
Binary files /dev/null and b/images/slack_select_channel-v2.png differ
diff --git a/images/slack_select_channel.png b/images/slack_select_channel.png
new file mode 100644
index 0000000000..6140834413
Binary files /dev/null and b/images/slack_select_channel.png differ
diff --git a/images/sql-tab.png b/images/sql-tab.png
new file mode 100644
index 0000000000..2f7e032724
Binary files /dev/null and b/images/sql-tab.png differ
diff --git a/images/ssl-checks.gif b/images/ssl-checks.gif
new file mode 100644
index 0000000000..82b8db0fdf
Binary files /dev/null and b/images/ssl-checks.gif differ
diff --git a/images/start-a-program-powershell.png b/images/start-a-program-powershell.png
new file mode 100644
index 0000000000..9216ec16a3
Binary files /dev/null and b/images/start-a-program-powershell.png differ
diff --git a/images/teams_installapp.png b/images/teams_installapp.png
new file mode 100644
index 0000000000..f8fd1d1a18
Binary files /dev/null and b/images/teams_installapp.png differ
diff --git a/images/teams_webhook.png b/images/teams_webhook.png
new file mode 100644
index 0000000000..70851c23d5
Binary files /dev/null and b/images/teams_webhook.png differ
diff --git a/images/test_this_zap.png b/images/test_this_zap.png
new file mode 100644
index 0000000000..8ec90cdc01
Binary files /dev/null and b/images/test_this_zap.png differ
diff --git a/images/test_zapier_trigger.png b/images/test_zapier_trigger.png
new file mode 100644
index 0000000000..f79573cea6
Binary files /dev/null and b/images/test_zapier_trigger.png differ
diff --git a/images/thenemail.png b/images/thenemail.png
new file mode 100644
index 0000000000..9ee15f72ca
Binary files /dev/null and b/images/thenemail.png differ
diff --git a/images/thenhide.png b/images/thenhide.png
new file mode 100644
index 0000000000..28c43b36d1
Binary files /dev/null and b/images/thenhide.png differ
diff --git a/images/thenhttp.png b/images/thenhttp.png
new file mode 100644
index 0000000000..868ae7eca9
Binary files /dev/null and b/images/thenhttp.png differ
diff --git a/images/thenignore.png b/images/thenignore.png
new file mode 100644
index 0000000000..d6827361e6
Binary files /dev/null and b/images/thenignore.png differ
diff --git a/images/tour/azure-apps-services.jpg b/images/tour/azure-apps-services.jpg
new file mode 100644
index 0000000000..3a4a12838d
Binary files /dev/null and b/images/tour/azure-apps-services.jpg differ
diff --git a/images/tour/deployment-tracking.jpg b/images/tour/deployment-tracking.jpg
new file mode 100644
index 0000000000..0dec1b5b9d
Binary files /dev/null and b/images/tour/deployment-tracking.jpg differ
diff --git a/images/tour/ignore-filters-and-rules.jpg b/images/tour/ignore-filters-and-rules.jpg
new file mode 100644
index 0000000000..6a37365657
Binary files /dev/null and b/images/tour/ignore-filters-and-rules.jpg differ
diff --git a/images/tour/installation.jpg b/images/tour/installation.jpg
new file mode 100644
index 0000000000..7d7021fc3f
Binary files /dev/null and b/images/tour/installation.jpg differ
diff --git a/images/tour/uptime-monitoring.jpg b/images/tour/uptime-monitoring.jpg
new file mode 100644
index 0000000000..c2fba530d0
Binary files /dev/null and b/images/tour/uptime-monitoring.jpg differ
diff --git a/images/tour/user-administration.jpg b/images/tour/user-administration.jpg
new file mode 100644
index 0000000000..4cda22bcd1
Binary files /dev/null and b/images/tour/user-administration.jpg differ
diff --git a/images/two-factor-enabled.png b/images/two-factor-enabled.png
new file mode 100644
index 0000000000..816dea040d
Binary files /dev/null and b/images/two-factor-enabled.png differ
diff --git a/images/umbraco-uno-enable-custom-code.png b/images/umbraco-uno-enable-custom-code.png
new file mode 100644
index 0000000000..2d0381924f
Binary files /dev/null and b/images/umbraco-uno-enable-custom-code.png differ
diff --git a/images/under-construction.gif b/images/under-construction.gif
new file mode 100644
index 0000000000..ab87cfc56c
Binary files /dev/null and b/images/under-construction.gif differ
diff --git a/images/uptime-checks.png b/images/uptime-checks.png
new file mode 100644
index 0000000000..6a33e8e7c4
Binary files /dev/null and b/images/uptime-checks.png differ
diff --git a/images/uptime_check_with_error.png b/images/uptime_check_with_error.png
new file mode 100644
index 0000000000..89e9d3026d
Binary files /dev/null and b/images/uptime_check_with_error.png differ
diff --git a/images/usage-graph-v2.png b/images/usage-graph-v2.png
new file mode 100644
index 0000000000..7e12d93c6b
Binary files /dev/null and b/images/usage-graph-v2.png differ
diff --git a/images/usage_graph.png b/images/usage_graph.png
new file mode 100644
index 0000000000..9d19c81425
Binary files /dev/null and b/images/usage_graph.png differ
diff --git a/images/users-security-new.png b/images/users-security-new.png
new file mode 100644
index 0000000000..505aeb93ee
Binary files /dev/null and b/images/users-security-new.png differ
diff --git a/images/users-security.png b/images/users-security.png
new file mode 100644
index 0000000000..3b780b6480
Binary files /dev/null and b/images/users-security.png differ
diff --git a/images/version-details-v2.png b/images/version-details-v2.png
new file mode 100644
index 0000000000..1c81145872
Binary files /dev/null and b/images/version-details-v2.png differ
diff --git a/images/version-search-v2.png b/images/version-search-v2.png
new file mode 100644
index 0000000000..43ec164037
Binary files /dev/null and b/images/version-search-v2.png differ
diff --git a/images/versiondetails.png b/images/versiondetails.png
new file mode 100644
index 0000000000..a78c566578
Binary files /dev/null and b/images/versiondetails.png differ
diff --git a/images/versiondetails2.png b/images/versiondetails2.png
new file mode 100644
index 0000000000..84196af41e
Binary files /dev/null and b/images/versiondetails2.png differ
diff --git a/images/versionsearch.png b/images/versionsearch.png
new file mode 100644
index 0000000000..7301bc19a6
Binary files /dev/null and b/images/versionsearch.png differ
diff --git a/images/visualstudio-authorize.png b/images/visualstudio-authorize.png
new file mode 100644
index 0000000000..f4e1ecb4f1
Binary files /dev/null and b/images/visualstudio-authorize.png differ
diff --git a/images/visualstudio-browse.png b/images/visualstudio-browse.png
new file mode 100644
index 0000000000..c870978f06
Binary files /dev/null and b/images/visualstudio-browse.png differ
diff --git a/images/visualstudio-details.png b/images/visualstudio-details.png
new file mode 100644
index 0000000000..a8074919c8
Binary files /dev/null and b/images/visualstudio-details.png differ
diff --git a/images/visualstudio-selectorganization.png b/images/visualstudio-selectorganization.png
new file mode 100644
index 0000000000..9482908919
Binary files /dev/null and b/images/visualstudio-selectorganization.png differ
diff --git a/images/visualstudio-signin.png b/images/visualstudio-signin.png
new file mode 100644
index 0000000000..a93758da4e
Binary files /dev/null and b/images/visualstudio-signin.png differ
diff --git a/images/vsts_add_task.png b/images/vsts_add_task.png
new file mode 100644
index 0000000000..f5805eb957
Binary files /dev/null and b/images/vsts_add_task.png differ
diff --git a/images/vsts_extension.png b/images/vsts_extension.png
new file mode 100644
index 0000000000..f8e563a140
Binary files /dev/null and b/images/vsts_extension.png differ
diff --git a/images/vsts_release_definition.png b/images/vsts_release_definition.png
new file mode 100644
index 0000000000..57b07862a4
Binary files /dev/null and b/images/vsts_release_definition.png differ
diff --git a/images/vsts_select_account.png b/images/vsts_select_account.png
new file mode 100644
index 0000000000..92c4675b83
Binary files /dev/null and b/images/vsts_select_account.png differ
diff --git a/images/vsts_task_added.png b/images/vsts_task_added.png
new file mode 100644
index 0000000000..4dd6605aaa
Binary files /dev/null and b/images/vsts_task_added.png differ
diff --git a/include-filename-and-line-number-in-stacktraces/index.html b/include-filename-and-line-number-in-stacktraces/index.html
new file mode 100644
index 0000000000..42055041da
--- /dev/null
+++ b/include-filename-and-line-number-in-stacktraces/index.html
@@ -0,0 +1,668 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Include filename and line number in stack traces
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Include filename and line number in stack traces
+
+If you are running .NET Core/.NET 5 you no longer need to enable filenames and line numbers manually.
+
+
When deploying your application to the test and production environment, you normally want to use the Release configuration. When doing so, your code is optimized, web.config transformation is running, and a few additional things. But, part of running on a Release build is, that you lose the ability to see filenames and line numbers in the stack traces produced by your system.
+
.NET offers the concept of PDB files, which are automatically generated when building your code. The PDB file contains information for the debugger to work, like which file to look up when a breakpoint is reached in your code. Unless you have changed the default settings inside Visual Studio, both the Debug and Release configuration generates a PDB file.
+
So, if both Debug and Release produce a PDB file, why do Debug builds include file name and line number in stack traces, while the Release build doesn't? The reason is most often caused by the fact that PDB files aren't published as part of the deployment. To do so, right-click your project in Visual Studio and select Properties . Click the Package/Publish Web tab and make sure that the Release configuration is selected in the dropdown. Next, remove the checkmark in Exclude generated debug symbols :
+
+
Also, make sure that the PDB file is generated as part of Release builds. Select the Build tab and click Advanced... . In Debug Info you want to make sure that either Pdb-only
or Portable
is selected (Pdb-only
being the default):
+
+
On your next deployment, PDB files are published as part of the build.
+
+Depending on who you talk to, deploying PDB files as part of your build may be considered a hack. Since PDB files can contain sensitive information about your implementation, publishing these files should only be done, if you have full control of the environment you are deploying to. When releasing software to external users/customers, you don't want to include your PDB files. In this case, you should store the PDB files internally, in a symbol server or similar.
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000000..a8056949b5
--- /dev/null
+++ b/index.html
@@ -0,0 +1,824 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Documentation for integrations and elmah.io features
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
elmah.io Installation Quick Start
+
Welcome to the quick-start installation guide. Here you will find a quick introduction to installing elmah.io. For the full overview, read through the individual guides by clicking a technology below:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
ASP.NET / MVC / Web API
+
Install the Elmah.Io
NuGet package:
+
Install-Package Elmah.Io
+
dotnet add package Elmah.Io
+
<PackageReference Include="Elmah.Io" Version="5.*" />
+
+
During the installation, you will be asked for your API key and log ID.
+
For more information, check out the installation guides for WebForms , MVC , and Web API . There is a short video tutorial available here:
+
+
+
+
+
ASP.NET Core
+
Install the Elmah.Io.AspNetCore
NuGet package:
+
Install-Package Elmah.Io.AspNetCore
+
dotnet add package Elmah.Io.AspNetCore
+
<PackageReference Include="Elmah.Io.AspNetCore" Version="5.*" />
+
paket add Elmah.Io.AspNetCore
+
+
Once installed, call AddElmahIo
and UseElmahIo
in the Program.cs
file:
+
var builder = WebApplication.CreateBuilder(args);
+// ...
+builder.Services.AddElmahIo(options => // 👈
+{
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+});
+// ...
+var app = builder.Build();
+// ...
+app.UseElmahIo(); // 👈
+// ...
+app.Run();
+
+
Make sure to insert your API key and log ID.
+
For more information, check out the installation guides for ASP.NET Core and Microsoft.Extensions.Logging .
+
JavaScript
+
Install the elmah.io.javascript
npm package:
+
npm install elmah.io.javascript
+
+
Reference the installed script and include your API key and log ID as part of the URL:
+
<script src="~/node_modules/elmah.io.javascript/dist/elmahio.min.js?apiKey=YOUR-API-KEY&logId=YOUR-LOG-ID" type="text/javascript"></script>
+
+
For more information, check out the installation guide for JavaScript .
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/integrate-elmah-io-with-hipchat/index.html b/integrate-elmah-io-with-hipchat/index.html
new file mode 100644
index 0000000000..484a6c6ac2
--- /dev/null
+++ b/integrate-elmah-io-with-hipchat/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Redirecting...
+
+
+
+
+
+
+Redirecting...
+
+
diff --git a/integrate-elmah-io-with-pipedream/index.html b/integrate-elmah-io-with-pipedream/index.html
new file mode 100644
index 0000000000..57ca101752
--- /dev/null
+++ b/integrate-elmah-io-with-pipedream/index.html
@@ -0,0 +1,668 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with Pipedream
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Integrate with Pipedream
+
Pipedream is a service similar to Zapier and IFTTT to help integrate systems without having to write code. In this article, we use an integration point provided by elmah.io and Pipedream called a trigger. A trigger is something that triggers an action in Pipedream. In the case of elmah.io, the trigger available is when new errors are logged to your log. Actions exist on the other side of the integration and tell Pipedream what to do every time a trigger is fired. This guide will show you how to set up the trigger. What action you want to execute when new errors are logged will depend on the tools and workflows used in your organization.
+
Create a new account on pipedream.com. Then click the New button on the Workflows page. The create new workflow page is shown:
+
+
Search for elmah.io in the search field and select the app and the New Error trigger:
+
+
Click the Connect new account button and input an API key with permission to both get logs and messages in the api_key field:
+
+
Click the Save button and select the log to integrate with in the Log ID dropdown:
+
+
Click the Create source button and wait for:
+
+
If no events are shown, force an error from the application integrated with the chosen log or create a test error through the API. Remember that only errors marked with the new flag are shown as events in Pipedream.
+
Select an event and click the Continue button. The elmah.io trigger is now configured. Select an app and event of your choice to create actions on the newly created trigger.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/integrate-elmah-io-with-slack/index.html b/integrate-elmah-io-with-slack/index.html
new file mode 100644
index 0000000000..e84f479f6e
--- /dev/null
+++ b/integrate-elmah-io-with-slack/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Redirecting...
+
+
+
+
+
+
+Redirecting...
+
+
diff --git a/integrate-elmah-io-with-zapier/index.html b/integrate-elmah-io-with-zapier/index.html
new file mode 100644
index 0000000000..fffbb8397f
--- /dev/null
+++ b/integrate-elmah-io-with-zapier/index.html
@@ -0,0 +1,674 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrate elmah.io with Zapier
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Integrate with Zapier
+
Zapier is the place to go if you need to integrate two or more online systems. In this article, we use an integration point provided by elmah.io and Zapier called a trigger. A trigger is (as the name suggest) something that triggers an action in Zapier. In the case of elmah.io, the trigger available is when new errors are logged to your log. Actions exist on the other side of the integration and tell Zapier what to do every time a trigger is fired. This guide will show you how to set up the trigger. What action you want to execute when new errors are logged will depend on the tools and workflows used in your organization.
+
Create a new account on zapier.com. Then click the Make a Zap button. The create new Zap page is shown:
+
+
Search for elmah.io in the search field and select the app and the New Error trigger:
+
+
Click Continue and you will be presented with the following screen:
+
+
Click the Sign in to elmah.io button or select an existing account if you have already set up other zaps using elmah.io. Adding a new account will show a popup asking you to sign in to elmah.io:
+
+
Sign in with your elmah.io username/password or social provider. On the following screen you will be asked to authorize elmah.io to notify Zapier every time a new error is logged in a log selected on a later stage:
+
+
Click the Authorize button and your account will be added to the account list on Zapier:
+
+
Click Continue . In the following step you will select the elmah.io log that you want to integrate with Zapier:
+
+
The dropdown contains all of the logs you have access to within your organization. Select a log and click Continue . In the following step you will test the trigger:
+
+
Click Test trigger and Zapier will pull recent errors from the chosen log. Select the error the represents how a typical error in the chosen log looks like. The values from the chosen error will be used when filling in the action, why selecting a good example in this step can make it easier to configure later on.
+
When you are done, click the Continue button. The elmah.io trigger is now configured. Select an app and event of your choice to create actions on the newly created trigger.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/integrations-high-level-overview/index.html b/integrations-high-level-overview/index.html
new file mode 100644
index 0000000000..06e64ab754
--- /dev/null
+++ b/integrations-high-level-overview/index.html
@@ -0,0 +1,824 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Integrations high-level overview
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Integrations high-level overview
+
elmah.io offers integration with an extensive list of logging- and web-frameworks. Get the full overview here. The table below shows all of the frameworks with official support. The Async column contains a checkmark if the integration is logging asynchronously. The Bulk column contains a checkmark if the integration is logging to elmah.io in bulk. In addition, elmah.io can be installed in a range of different products and services not mentioned (using the integrations below). Look through the menu to the left to see all of the possible integrations.
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/javascript-troubleshooting/index.html b/javascript-troubleshooting/index.html
new file mode 100644
index 0000000000..480b30310c
--- /dev/null
+++ b/javascript-troubleshooting/index.html
@@ -0,0 +1,706 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ JavaScript Troubleshooting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
JavaScript Troubleshooting
+
Errors aren't logged
+
If errors aren't logged from JavaScript, here's a list of things to try out:
+
+Make sure that the log with the specified ID exists.
+Make sure that the log isn't disabled and/or contains any ignore filters that could ignore client-side errors.
+Make sure that the API key is valid and contains the Messages | Write permission.
+Enable debugging when initializing elmah.io.javascript
to get additional debug and error messages from within the script printed to the browser console:
+
+
new Elmahio({
+ apiKey: 'API_KEY',
+ logId: 'LOG_ID',
+ debug: true
+});
+
+
+If your webserver includes the Content-Security-Policy
header make sure to include api.elmah.io
as an allowed domain.
+
+
+
When logging uncaught errors with elmah.io.javascript
you get a lot of additional information stored as part of the log messages. Like the client IP and browser details. If you don't see this information on the messages logged from your application, it's probably because you are using the log
function:
+
logger.log({
+ title: 'This is a custom log message',
+ severity: 'Error'
+});
+
+
The log
function only logs what you tell it to log. To include the additional information, switch to use the message
builder:
+
var msg = logger.message(); // Get a prefilled message
+msg.title = 'This is a custom log message';
+msg.severity = 'Error';
+logger.log(msg);
+
+
Missing stack trace on errors
+
If errors logged through elmah.io.javascript
have a stack trace, it is logged as part of the error on elmah.io. If errors don't include a stack trace, the following actions may fix it:
+
+Not all errors include a stack trace. Make sure that the thrown error does include a stack trace by inspecting:
+
+
e.stack
+
+
+Move the elmahio.js
script import to the top of the list of all referenced JavaScript files.
+Remove any defer
or async
attributes from the elmahio.js
script import. The elmahio.js
script import can include those attributes, but errors during initialization may not include stack trace or even be omitted if elmah.io.javascript
hasn't been loaded yet.
+
+
CORS problems when running on localhost
+
When running with elmah.io.javascript
on localhost you may see errors in the console like this:
+
Access to XMLHttpRequest at 'https://api.elmah.io/v3/messages/...' from origin 'http://localhost' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
+
+
Browsers like Chrome don't allow CORS when running locally. There are three ways to fix this:
+
+Run Chrome with the --disable-web-security
switch.
+Run your website on a hostname like https://mymachine
.
+Allow CORS on localhost with extensions like CORS Unblock for Chrome or Allow CORS: Access-Control-Allow-Origin for Firefox.
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-breadcrumbs-from-asp-net-core/index.html b/logging-breadcrumbs-from-asp-net-core/index.html
new file mode 100644
index 0000000000..c3c7c6e3cb
--- /dev/null
+++ b/logging-breadcrumbs-from-asp-net-core/index.html
@@ -0,0 +1,736 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging breadcrumbs from ASP.NET Core
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging breadcrumbs from ASP.NET Core
+
+
You can log one or more breadcrumbs as part of both automatic and manually logged errors. Breadcrumbs indicate steps happening just before a message logged by Elmah.Io.AspNetCore
. Breadcrumbs with elmah.io and ASP.NET Core are supported in two ways: manual and through Microsoft.Extensions.Logging.
+
Manually logging breadcrumbs
+
If you want to log a breadcrumb manually as part of an MVC controller action or similar, you can use the ElmahIoApi
class:
+
ElmahIoApi.AddBreadcrumb(
+ new Breadcrumb(DateTime.UtcNow, message: "Requesting the front page"),
+ HttpContext);
+
+
Notice that the Breadcrumb
class is located in the Elmah.Io.Client
package that will be automatically installed when installing Elmah.Io.AspNetCore
. The Breadcrumb
class is either in the Elmah.Io.Client
or Elmah.Io.Client.Models
namespace, depending on which version of Elmah.Io.Client
you have installed.
+
The best example of a helpful breadcrumb is logging the input model to all endpoints as a breadcrumb. This will show you exactly which parameters the user sends to your website. The following example is created for ASP.NET Core MVC, but similar solutions can be built for other MVC features as well.
+
Create a new class named BreadcrumbFilterAttribute
and place it somewhere inside your MVC project:
+
public class BreadcrumbFilterAttribute : ActionFilterAttribute
+{
+ public override void OnActionExecuting(ActionExecutingContext context)
+ {
+ var arguments = context.ActionArguments;
+ if (arguments.Count == 0) return;
+
+ ElmahIoApi.AddBreadcrumb(
+ new Breadcrumb(
+ DateTime.UtcNow,
+ "Information",
+ "Request",
+ string.Join(", ", arguments.Select(a => $"{a.Key} = {JsonSerializer.Serialize(a.Value)}"))),
+ context.HttpContext);
+ }
+}
+
+
The action filter converts the action arguments to a comma-separated string and logs it as a breadcrumb. You can either decorate each controller with the BreadcrumbFilterAttribute
or add it globally:
+
builder.Services.AddControllersWithViews(options =>
+{
+ options.Filters.Add(new BreadcrumbFilterAttribute());
+});
+
+
Logging breadcrumbs from Microsoft.Extensions.Logging
+
We also provide an automatic generation of breadcrumbs using Microsoft.Extensions.Logging. This will pick up all log messages logged through the ILogger
and include those as part of an error logged. This behavior is currently in opt-in mode, meaning that you will need to enable it in options:
+
builder.Services.AddElmahIo(options =>
+{
+ // ...
+ options.TreatLoggingAsBreadcrumbs = true;
+});
+
+
The boolean can also be configured through appsettings.json
:
+
{
+ // ...
+ "ElmahIo": {
+ // ...
+ "TreatLoggingAsBreadcrumbs": true
+ }
+}
+
+
When enabling this automatic behavior, you may need to adjust the log level included as breadcrumbs. This is done in the appsettings.json
file by including the following JSON:
+
{
+ "Logging": {
+ // ...
+ "ElmahIoBreadcrumbs": {
+ "LogLevel": {
+ "Default": "Information"
+ }
+ }
+ }
+}
+
+
Filtering breadcrumbs
+
Breadcrumbs can be filtered using one or more rules as well:
+
builder.Services.AddElmahIo(options =>
+{
+ // ...
+ options.OnFilterBreadcrumb =
+ breadcrumb => breadcrumb.Message == "A message we don't want as a breadcrumb";
+});
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-custom-data/index.html b/logging-custom-data/index.html
new file mode 100644
index 0000000000..db8ab6a06c
--- /dev/null
+++ b/logging-custom-data/index.html
@@ -0,0 +1,684 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging custom data to elmah.io
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging custom data
+
ELMAH stores a lot of contextual information when an error occurs. Things like cookies, stack trace, server variables, and much more are stored to ease debugging the error at a later point in time. Most error log implementations for ELMAH doesn't support custom variables. Luckily, this is not the case for the elmah.io client.
+
Let's look at some code. You have two options for decorating your errors with custom variables.
+
Use the Data dictionary on .NET's Exception type
+
All exceptions in .NET contain a property named Data
and of type IDictionary
. The Data
dictionary is intended for user-defined information about the exception. The elmah.io client iterates through key/values in this dictionary and ships it off to elmah.io's API . To log custom data using Data
, just add a new key/value pair to the Data
dictionary:
+
try
+{
+ CallSomeBusinessLogic(inputValue);
+}
+catch (Exception e)
+{
+ e.Data.Add("InputValueWas", inputValue);
+ ErrorSignal.FromCurrentContext().Raise(e);
+}
+
+
In the example, a custom variable named InputValueWas
with the value of the inputValue
variable is added. This way you will be able to see which input value caused the exception.
+
Use the OnMessage
hook in the elmah.io client
+
You may not use ELMAH's ErrorSignal feature but rely on ELMAH to log uncaught exceptions only. In this scenario, you probably don't have access to the thrown exception. The elmah.io client offers a hook for you to be able to execute code every time something is logged:
+
Elmah.ErrorLog.GetDefault(null); // Forces creation of logger client
+var logger = Elmah.Io.ErrorLog.Client;
+logger.OnMessage += (sender, args) =>
+{
+ if (args.Message.Data == null) args.Message.Data = new List<Item>();
+ args.Message.Data.Add(new Item { Key = "SomeOtherVariable", Value = someVariable });
+};
+
+
You may not have seen the Logger type of elmah.io before, but what's important to know right now is, that Logger is responsible for logging messages to the elmah.io API. Another new term here is Message. A message is the type encapsulating all of the information about the thrown exception.
+
In the code example, a new event handler is subscribed to the OnMessage
event. This tells the elmah.io client to execute your event handler, before actually logging an exception to elmah.io. The event is used to add a custom variable to the Data
dictionary of the message logged to elmah.io.
+
Looking at your custom data
+
Custom data are shown on the Data tab on the extended messages details page. To open inspect custom data go to the log search page, extend a log message, click the three bars (hamburger) icon in the upper right corner. The custom data is beneath the Data tab. As the content in the other tabs of the message details, you will be able to filter results by the variable key.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-errors-programmatically/index.html b/logging-errors-programmatically/index.html
new file mode 100644
index 0000000000..60d47f4c4f
--- /dev/null
+++ b/logging-errors-programmatically/index.html
@@ -0,0 +1,683 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging errors programmatically
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging errors programmatically
+
So you've set up a shiny new ELMAH log and all of your unhandled errors are logged to ELMAH. Now you're wondering: "How do I log my handled errors to ELMAH programmatically?"
+
You are in luck! ELMAH provides a nice API to do just that through error signaling. Consider the following code:
+
try
+{
+ int i = 0;
+ int result = 42 / i;
+}
+catch (DivideByZeroException e)
+{
+ // What to do?
+}
+
+
In this example, a System.DivideByZeroException is thrown when trying to divide by zero, but what if we want to catch (and log) that exception instead of throwing it back through the call stack? With ELMAH's ErrorSignal
class we can log the error:
+
try
+{
+ int i = 0;
+ int result = 42 / i;
+}
+catch (DivideByZeroException e)
+{
+ ErrorSignal.FromCurrentContext().Raise(e);
+}
+
+
We call the static method FromCurrentContext
on the ErrorSignal
class, which returns a new object for doing the actual logging. Logging happens through the Raise method, which logs the exception to the configured ELMAH storage endpoint.
+
In the example above, I use the FromCurrentContext
helper to create a new instance of ErrorSignal
. ELMAH also works outside the context of a webserver and in this case, you would simply use the default logger with null
as the HTTP context:
+
ErrorLog.GetDefault(null).Log(new Error(e));
+
+
If you simply want to log text messages and don't need all of the HTTP context information, consider using one of our integrations for popular logging frameworks like log4net , NLog , or Serilog . Also, the Elmah.Io.Client package contains a logging API documented here .
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-from-a-custom-http-module/index.html b/logging-from-a-custom-http-module/index.html
new file mode 100644
index 0000000000..c1423d77a6
--- /dev/null
+++ b/logging-from-a-custom-http-module/index.html
@@ -0,0 +1,681 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging from a custom HTTP module
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging from a custom HTTP module
+
Some developers like to gather all logging into a single module. An example of this would be to log to multiple log destinations and maybe even enrich log messages to multiple loggers with the same info. We always recommend using the modules and handlers that come with ELMAH. But in case you want to log from a module manually, here's the recipe:
+
public class CustomLoggingModule : IHttpModule
+{
+ public void Init(HttpApplication context)
+ {
+ context.Error += Application_Error;
+ }
+
+ public void Application_Error(object sender, EventArgs messageData)
+ {
+ HttpApplication application = (HttpApplication)sender;
+ var context = application.Context;
+ var error = new Error(application.Server.GetLastError(), context);
+ var log = ErrorLog.GetDefault(context);
+ log.Log(error);
+ }
+
+ public void Dispose()
+ {
+ }
+}
+
+
In the example, I've created a new module named CustomLoggingModule
. The module needs to be configured in web.config
as explained here . When starting up the application, ASP.NET calls the Init
-method. In this method, an Error
event handler is set. Every time a new error is happening in your web application, ASP.NET now calls the Application_Error
-method. In this method, I wrap the last thrown error in ELMAH's Error
object and log it through the ErrorLog
class.
+
+Be aware that logging errors this way, disables ELMAH's built-in events like filtering.
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-heartbeats-from-asp-net-core/index.html b/logging-heartbeats-from-asp-net-core/index.html
new file mode 100644
index 0000000000..8251d1e5a9
--- /dev/null
+++ b/logging-heartbeats-from-asp-net-core/index.html
@@ -0,0 +1,784 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging heartbeats from ASP.NET Core
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging heartbeats from ASP.NET Core
+
+
ASP.NET Core offers a feature called Health Checks from version 2.2 and forward. For more information about health checks, check out our blog post: ASP.NET Core 2.2 Health Checks Explained . The Heartbeats feature on elmah.io supports health checks too, by publishing health check results as heartbeats.
+
To publish health checks as elmah.io heartbeats, install the Elmah.Io.AspNetCore.HealthChecks
NuGet package:
+
Install-Package Elmah.Io.AspNetCore.HealthChecks
+
dotnet add package Elmah.Io.AspNetCore.HealthChecks
+
<PackageReference Include="Elmah.Io.AspNetCore.HealthChecks" Version="5.*" />
+
paket add Elmah.Io.AspNetCore.HealthChecks
+
+
Then configure the elmah.io health check publisher:
+
builder
+ .Services
+ .AddHealthChecks()
+ .AddElmahIoPublisher(options =>
+ {
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+ options.HeartbeatId = "HEARTBEAT_ID";
+ });
+
+
Replace the variables with the correct values as explained in Set up Heartbeats . Remember to use an API key that includes the Heartbeats - Write permission.
+
Additional options
+
Application name
+
Much like the error logging integration with ASP.NET Core, you can set an application name on log messages produced by Heartbeats. To do so, set the Application
property when adding the publisher:
+
.AddElmahIoPublisher(options =>
+{
+ ...
+ options.Application = "My app";
+});
+
+
If Application
is not set, log messages will receive a default value of Heartbeats
to make the messages distinguishable from other messages.
+
Callbacks
+
The elmah.io publisher offer callbacks already known from Elmah.Io.AspNetCore
.
+
OnHeartbeat
+
The OnHeartbeat
callback can be used to set a version number on all log messages produced by a heartbeat and/or trigger custom code every time a heartbeat is logged to elmah.io:
+
.AddElmahIoPublisher(options =>
+{
+ ...
+ options.OnHeartbeat = hb =>
+ {
+ hb.Version = "1.0.0";
+ };
+});
+
+
OnFilter
+
The OnFilter
callback can used to ignore one or more heartbeats:
+
.AddElmahIoPublisher(options =>
+{
+ ...
+ options.OnFilter = hb =>
+ {
+ return hb.Result == "Degraded";
+ };
+});
+
+
The example ignores any Degraded
heartbeats.
+
OnError
+
The OnError
callback can be used to listen for errors communicating with the elmah.io API:
+
.AddElmahIoPublisher(options =>
+{
+ ...
+ options.OnError = (hb, ex) =>
+ {
+ // Do something
+ };
+});
+
+
The elmah.io publisher already logs any internal errors through Microsoft.Extensions.Logging, why you don't need to do that in the OnError
handler. If you are using another logging framework and don't have that hooked up on Microsoft.Extensions.Logging, the OnError
is a good place to add some additional logging.
+
Period
+
As default, ASP.NET Core runs health checks every 30 seconds when setting up a publisher. To change this interval, add the following code:
+
builder.Services.Configure<HealthCheckPublisherOptions>(options =>
+{
+ options.Period = TimeSpan.FromMinutes(5);
+});
+
+
There's a bug in ASP.NET Core 2.2 that requires you to use reflection when setting Period
:
+
builder.Services.Configure<HealthCheckPublisherOptions>(options =>
+{
+ var prop = options.GetType().GetField("_period", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
+ prop.SetValue(options, TimeSpan.FromMinutes(5));
+});
+
+
If setting Period
to 5 minutes, you should set the heartbeat interval on elmah.io to 5 minutes and grace to 1 minute.
+
Ignoring heartbeats on localhost, staging, etc.
+
Monitoring heartbeats is important in your production environment. When running locally or even on staging, you probably don't want to monitor heartbeats. ASP.NET Core health checks doesn't seem to support a great deal of configuration through appsettings.json
, Azure app settings, etc. The easiest way to tell ASP.NET Core to log heartbeats to elmah.io is to avoid setting up health checks unless a heartbeat id is configured:
+
if (!string.IsNullOrWhiteSpace(Configuration["ElmahIo:HeartbeatId"]))
+{
+ builder.Services.AddHealthChecks().AddElmahIoPublisher();
+}
+
+
In this example, we only configure health checks and the elmah.io publisher if the ElmahIo:HeartbeatId
setting is defined in config.
+
Troubleshooting
+
Here's a list of things to check for if no heartbeats are registered:
+
+Did you include both API_KEY
, LOG_ID
, and HEARTBEAT_ID
?
+The publisher needs to be called before the AddElmahIo
call from Elmah.Io.AspNetCore
:
+
+
builder
+ .Services
+ .AddHealthChecks()
+ .AddElmahIoPublisher();
+
+builder.Services.AddElmahIo();
+
+
+If you are using Health Checks UI, it needs to be configured after the AddElmahIoPublisher
-method:
+
+
builder
+ .Services
+ .AddHealthChecks()
+ .AddElmahIoPublisher();
+
+builder
+ .Services
+ .AddHealthChecksUI();
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-heartbeats-from-aws-lambdas/index.html b/logging-heartbeats-from-aws-lambdas/index.html
new file mode 100644
index 0000000000..cf867f5095
--- /dev/null
+++ b/logging-heartbeats-from-aws-lambdas/index.html
@@ -0,0 +1,694 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging heartbeats from AWS Lambdas
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging heartbeats from AWS Lambdas
+
AWS Lambdas running on a schedule are great candidates for logging heartbeats to elmah.io. To send a healthy heartbeat when the Lambda runs successfully and an unhealthy heartbeat when an error happens, start by installing the Elmah.Io.Client
NuGet package:
+
Install-Package Elmah.Io.Client
+
dotnet add package Elmah.Io.Client
+
<PackageReference Include="Elmah.Io.Client" Version="5.*" />
+
paket add Elmah.Io.Client
+
+
Include elmah.io API key, log ID, and heartbeat ID in your code. In this example, they are added as static fields:
+
private const string ApiKey = "API_KEY";
+private const string HeartbeatId = "HEARTBEAT_ID";
+private static Guid LogId = new Guid("LOG_ID");
+
+
Replace API_KEY
with an API key with the Heartbeats | Write permission (Where is my API key? ), HEARTBEAT_ID
with the ID of the heartbeat available on the elmah.io UI, and LOG_ID
with the ID of the log containing the heartbeat (Where is my log ID? ).
+
Create the elmah.io client and store the IHeartbeat
object somewhere. In the following example, it is initialized in the Main
method and stored in a static field:
+
private static IHeartbeats heartbeats;
+
+private static async Task Main(string[] args)
+{
+ heartbeats = ElmahioAPI.Create(ApiKey).Heartbeats;
+ // ...
+}
+
+
In the function handler, wrap your code in try/catch and call either the Healthy
or Unhealthy
method:
+
public static string FunctionHandler(string input, ILambdaContext context)
+{
+ try
+ {
+ // Lambda code goes here
+
+ heartbeats.Healthy(LogId, HeartbeatId);
+ return input?.ToUpper();
+ }
+ catch (Exception e)
+ {
+ heartbeats.Unhealthy(LogId, HeartbeatId, e.ToString());
+ throw;
+ }
+}
+
+
When the lambda code runs (replace the Lambda code goes here
comment with your code) without exceptions, a healthy heartbeat is logged to elmah.io. In case of an exception, an unhealthy heartbeat is logged to elmah.io. In case your Lambda doesn't run at all, elmah.io automatically logs a missing heartbeat.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-heartbeats-from-azure-functions/index.html b/logging-heartbeats-from-azure-functions/index.html
new file mode 100644
index 0000000000..e9940107c2
--- /dev/null
+++ b/logging-heartbeats-from-azure-functions/index.html
@@ -0,0 +1,883 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging heartbeats from Azure Functions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging heartbeats from Azure Functions
+
+
Azure Functions are great candidates for adding heartbeats. For web APIs implemented with Azure Functions, you should create a /health
endpoint and ping that using Uptime Monitoring . But for timer triggered, queue triggers, and similar function apps, heartbeats are a great way to verify that your function is successfully running. The rest of this document is split into different ways of adding heartbeats to one or more functions.
+
Using a filter in Elmah.Io.Functions
+
The easiest way of including a heartbeat is to include the ElmahIoHeartbeatFilter
available in the Elmah.Io.Functions
package. This will automatically publish a Healthy
or Unhealthy
heartbeat, depending on if your functions execute successfully. This option is great for timer-triggered functions like nightly batch jobs.
+
Start by installing the Elmah.Io.Functions
package:
+
Install-Package Elmah.Io.Functions
+
dotnet add package Elmah.Io.Functions
+
<PackageReference Include="Elmah.Io.Functions" Version="5.*" />
+
paket add Elmah.Io.Functions
+
+
Elmah.Io.Functions
requires dependency injection part of the Microsoft.Azure.Functions.Extensions
package, why you will need this package if not already added.
+
Extend the Startup.cs
(or whatever you named your function startup class) file with the following code:
+
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
+using Microsoft.Azure.WebJobs.Host;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Elmah.Io.Functions;
+
+[assembly: FunctionsStartup(typeof(My.FunctionApp.Startup))]
+
+namespace My.FunctionApp
+{
+ public class Startup : FunctionsStartup
+ {
+ public override void Configure(IFunctionsHostBuilder builder)
+ {
+ var config = new ConfigurationBuilder()
+ .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
+ .AddEnvironmentVariables()
+ .Build();
+
+ builder.Services.Configure<ElmahIoFunctionOptions>(o =>
+ {
+ o.ApiKey = config["apiKey"];
+ o.LogId = new Guid(config["logId"]);
+ o.HeartbeatId = config["heartbeatId"];
+ });
+
+ builder.Services.AddSingleton<IFunctionFilter, ElmahIoHeartbeatFilter>();
+ }
+ }
+}
+
+
The code installs the ElmahIoHeartbeatFilter
class, which will handle all of the communication with the elmah.io API.
+
Finally, add the config variables (apiKey
, logId
, and heartbeatId
) to the local.settings.json
file, environment variables, Azure configuration settings, or in whatever way you specify settings for your function app.
+
Manually using Elmah.Io.Client
+
The example above installs the heartbeat filter for all functions. If you have multiple functions inside your function app, or you want greater control of when and how to send heartbeats, you can use Elmah.Io.Client
to create heartbeats.
+
Start by installing the Elmah.Io.Client
NuGet package:
+
Install-Package Elmah.Io.Client
+
dotnet add package Elmah.Io.Client
+
<PackageReference Include="Elmah.Io.Client" Version="5.*" />
+
paket add Elmah.Io.Client
+
+
Extend the Startup.cs
file with the following code:
+
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
+using Microsoft.Azure.WebJobs.Host;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Elmah.Io.Client;
+
+[assembly: FunctionsStartup(typeof(My.FunctionApp.Startup))]
+
+namespace My.FunctionApp
+{
+ public class Startup : FunctionsStartup
+ {
+ public override void Configure(IFunctionsHostBuilder builder)
+ {
+ var config = new ConfigurationBuilder()
+ .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
+ .AddEnvironmentVariables()
+ .Build();
+ builder.Services.AddSingleton(config);
+
+ var elmahIo = ElmahioAPI.Create(config["apiKey"]);
+ builder.Services.AddSingleton(elmahIo.Heartbeats);
+ }
+ }
+}
+
+
Inside your function, wrap all of the code in try/catch
and add code to create either a Healthy
or Unhealthy
heartbeat:
+
using System;
+using System.Threading.Tasks;
+using Elmah.Io.Client;
+using Microsoft.Azure.WebJobs;
+using Microsoft.Extensions.Configuration;
+
+namespace My.FunctionApp
+{
+ public class TimedFunction
+ {
+ private readonly IHeartbeats heartbeats;
+ private readonly IConfiguration configuration;
+
+ public TimedFunction(IHeartbeats heartbeats, IConfiguration configuration)
+ {
+ this.heartbeats = heartbeats;
+ this.configuration = configuration;
+ }
+
+ [FunctionName("TimedFunction")]
+ public async Task Run([TimerTrigger("0 0 * * * *")]TimerInfo myTimer)
+ {
+ var heartbeatId = configuration["heartbeatId"];
+ var logId = configuration["logId"];
+ try
+ {
+ // Your function code goes here
+
+ await heartbeats.CreateAsync(heartbeatId, logId, new CreateHeartbeat
+ {
+ Result = "Healthy"
+ });
+ }
+ catch (Exception e)
+ {
+ await heartbeats.CreateAsync(heartbeatId, logId, new CreateHeartbeat
+ {
+ Result = "Unhealthy",
+ Reason = e.ToString(),
+ });
+ }
+ }
+ }
+}
+
+
If your function code executes successfully, a Healthy
heartbeat is created. If an exception is thrown, an Unhealthy
heartbeat with the thrown exception in Reason
is created.
+
Be aware that configuring a function to run in an internal (like every hour for the example above) does not ensure that the function is executed exactly on the hour. We recommend to set the grace period for these types of heartbeats to 15-30 minutes to avoid heartbeat errors when the timed function is past due.
+
Using a separate heartbeat function
+
You may want a single heartbeat representing your entire function app consisting of multiple functions. This is a good option if you want to create heartbeats from queue-triggered functions or similar. In these cases, you don't want to create a heartbeat every time a message from the queue is handled, but you will want to notify elmah.io if dependencies like database connection suddenly aren't available. We recommend creating a new heartbeat function for this kind of Function. Like in the previous example, make sure to extend your Startup.cs
file like this:
+
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
+using Microsoft.Azure.WebJobs.Host;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Elmah.Io.Client;
+
+[assembly: FunctionsStartup(typeof(My.FunctionApp.Startup))]
+
+namespace My.FunctionApp
+{
+ public class Startup : FunctionsStartup
+ {
+ public override void Configure(IFunctionsHostBuilder builder)
+ {
+ var config = new ConfigurationBuilder()
+ .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
+ .AddEnvironmentVariables()
+ .Build();
+ builder.Services.AddSingleton(config);
+
+ var elmahIo = ElmahioAPI.Create(config["apiKey"]);
+ builder.Services.AddSingleton(elmahIo.Heartbeats);
+ }
+ }
+}
+
+
Then create a new timed function with the following code:
+
using System;
+using System.Threading.Tasks;
+using Elmah.Io.Client;
+using Microsoft.Azure.WebJobs;
+using Microsoft.Extensions.Configuration;
+
+namespace My.FunctionApp
+{
+ public class Heartbeat
+ {
+ private readonly IConfiguration config;
+ private readonly IHeartbeats heartbeats;
+
+ public Heartbeat(IHeartbeats heartbeats, IConfiguration config)
+ {
+ this.heartbeats = heartbeats;
+ this.config = config;
+ }
+
+ [FunctionName("Heartbeat")]
+ public async Task Run([TimerTrigger("0 */5 * * * *")]TimerInfo myTimer)
+ {
+ var result = "Healthy";
+ var reason = (string)null;
+ try
+ {
+ // Check your dependencies here
+ }
+ catch (Exception e)
+ {
+ result = "Unhealthy";
+ reason = e.ToString();
+ }
+
+ await heartbeats.CreateAsync(config["heartbeatId"], config["logId"], new CreateHeartbeat
+ {
+ Result = result,
+ Reason = reason,
+ });
+ }
+ }
+}
+
+
In the example above, the new function named Heartbeat
(the name is entirely up to you) executes every 5 minutes. Replace the comment with your checks like opening a connection to the database. If everything works as it should, a Healthy
heartbeat is logged to elmah.io. If an exception is thrown while checking your dependencies, an Unhealthy
heartbeat is created.
+
When running locally, you may want to disable heartbeats. You can use the Disable
attribute for that by including the following code:
+
#if DEBUG
+ [Disable]
+#endif
+ public class Heartbeat
+ {
+ // ...
+ }
+
+
or add the following to local.settings.json
:
+
{
+ // ...
+ "Values": {
+ "AzureWebJobs.Heartbeat.Disabled": true,
+ // ...
+ }
+}
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-heartbeats-from-curl/index.html b/logging-heartbeats-from-curl/index.html
new file mode 100644
index 0000000000..495e8c1061
--- /dev/null
+++ b/logging-heartbeats-from-curl/index.html
@@ -0,0 +1,663 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging heartbeats from cURL
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging heartbeats from cURL
+
Sometimes is just easier to use cURL when needing to call a REST API. Creating elmah.io heartbeats is easy using cURL and fits well into scripts, scheduled tasks, and similar.
+
To create a new heartbeat, include the following cURL command in your script:
+
curl -X POST "https://api.elmah.io/v3/heartbeats/LOG_ID/HEARTBEAT_ID?api_key=API_KEY" -H "accept: application/json" -H "Content-Type: application/json-patch+json" -d "{ \"result\": \"Healthy\"}"
+
+
Remember to place LOG_ID
, HEARTBEAT_ID
, and API_KEY
with the values found on the Heartbeats tab in elmah.io.
+
To create an Unhealthy
heartbeat, change the result
in the body and include a reason
:
+
curl -X POST "https://api.elmah.io/v3/heartbeats/LOG_ID/HEARTBEAT_ID?api_key=API_KEY" -H "accept: application/json" -H "Content-Type: application/json-patch+json" -d "{ \"result\": \"Unhealthy\", \"reason\": \"Something isn't working\" }"
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-heartbeats-from-hangfire/index.html b/logging-heartbeats-from-hangfire/index.html
new file mode 100644
index 0000000000..64a6b2e18a
--- /dev/null
+++ b/logging-heartbeats-from-hangfire/index.html
@@ -0,0 +1,743 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging heartbeats from Hangfire
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging heartbeats from Hangfire
+
Scheduling recurring tasks with Hangfire is easy. Monitoring if tasks are successfully executed or even run can be a challenge. With elmah.io Heartbeats, we provide native monitoring of Hangfire recurring tasks.
+
To publish heartbeats from Hangifre, install the Elmah.Io.Heartbeats.Hangfire
NuGet package:
+
Install-Package Elmah.Io.Heartbeats.Hangfire
+
dotnet add package Elmah.Io.Heartbeats.Hangfire
+
<PackageReference Include="Elmah.Io.Heartbeats.Hangfire" Version="5.*" />
+
paket add Elmah.Io.Heartbeats.Hangfire
+
+
For this example, we'll schedule a method named Test
to execute every minute:
+
RecurringJob.AddOrUpdate(() => Test(), Cron.Minutely);
+
+
To automatically publish a heartbeat when the job is executed, add the following using
:
+
using Elmah.Io.Heartbeats.Hangfire;
+
+
And decorate the Test
-method with the ElmahIoHeartbeat
attribute:
+
[ElmahIoHeartbeat("API_KEY", "LOG_ID", "HEARTBEAT_ID")]
+public void Test()
+{
+ // ...
+}
+
+
Replace API_KEY
(Where is my API key? ), LOG_ID
(Where is my log ID? ), and HEARTBEAT_ID
with the correct variables from elmah.io.
+
When the job successfully runs, a Healthy
heartbeat is logged to elmah.io. If an exception is thrown an Unhealthy
heartbeat is logged. elmah.io will automatically create an error if a heartbeat is missing, as long as the heartbeat is correctly configured as explained in Set up Heartbeats .
+
Move configuration to config files
+
You normally don't include your API key, log ID, and heartbeat ID in C# code as shown in the example above. Unfortunately, Hangfire attributes doesn't support dependency injection or configuration from config files. There's a small "hack" that you can use to move the configuration to a configuration file by creating a custom attribute:
+
using Elmah.Io.Heartbeats.Hangfire;
+using Hangfire.Common;
+using Hangfire.Server;
+using System.Configuration;
+
+public class AppSettingsElmahIoHeartbeatAttribute : JobFilterAttribute, IServerFilter
+{
+ private readonly ElmahIoHeartbeatAttribute _inner;
+
+ public AppSettingsElmahIoHeartbeatAttribute()
+ {
+ var apiKey = ConfigurationManager.AppSettings["apiKey"];
+ var logId = ConfigurationManager.AppSettings["logId"];
+ var heartbeatId = ConfigurationManager.AppSettings["heartbeatId"];
+ _inner = new ElmahIoHeartbeatAttribute(apiKey, logId, heartbeatId);
+ }
+
+ public void OnPerformed(PerformedContext filterContext)
+ {
+ _inner.OnPerformed(filterContext);
+ }
+
+ public void OnPerforming(PerformingContext filterContext)
+ {
+ _inner.OnPerforming(filterContext);
+ }
+}
+
+
In the example the AppSettingsElmahIoHeartbeatAttribute
class wrap ElmahIoHeartbeatAttribute
. By doing so, it is possible to fetch configuration from application settings as part of the constructor. The approach would be similar when using IConfiguration
(like in ASP.NET Core), but you will need to share a reference to the configuration object somehow.
+
To use AppSettingsElmahIoHeartbeatAttribute
simply add it to the method:
+
[AppSettingsElmahIoHeartbeat]
+public void Test()
+{
+ // ...
+}
+
+
As an alternative, you can register the ElmahIoHeartbeatAttribute
as a global attribute. In this example we use IConfiguration
in ASP.NET Core to fetch configuration from the appsettings.json
file:
+
public class Startup
+{
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddHangfire(config => config
+ // ...
+ .UseFilter(new ElmahIoHeartbeatAttribute(
+ Configuration["ElmahIo:ApiKey"],
+ Configuration["ElmahIo:LogId"],
+ Configuration["ElmahIo:HeartbeatId"])));
+ }
+
+ // ...
+}
+
+
This will execute the ElmahIoHeartbeat
filter for every Hangfire job which isn't ideal if running multiple jobs within the same project.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-heartbeats-from-isolated-azure-functions/index.html b/logging-heartbeats-from-isolated-azure-functions/index.html
new file mode 100644
index 0000000000..278d9e7e13
--- /dev/null
+++ b/logging-heartbeats-from-isolated-azure-functions/index.html
@@ -0,0 +1,802 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging heartbeats from Isolated Azure Functions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging heartbeats from Isolated Azure Functions
+
+
Isolated Azure Functions are great candidates for adding heartbeats. For web APIs implemented with Isolated Azure Functions, you should create a /health
endpoint and ping that using Uptime Monitoring . But for timer triggered, queue triggers, and similar function apps, heartbeats are a great way to verify that your function is successfully running. The rest of this document is split into different ways of adding heartbeats to one or more functions.
+
Using middleware in Elmah.Io.Functions.Isolated
+
Scheduled functions or functions not running often can use the heartbeat middleware part of the Elmah.Io.Functions.Isolated
package. This will log a Healthy
or Unhealthy
endpoint every time a function is running. All functions within the same function app uses the same middleware, why this is primarily inteded for function apps with one scheduled function.
+
Start by installing the Elmah.Io.Functions.Isolated
package:
+
Install-Package Elmah.Io.Functions.Isolated
+
dotnet add package Elmah.Io.Functions.Isolated
+
<PackageReference Include="Elmah.Io.Functions.Isolated" Version="5.*" />
+
paket add Elmah.Io.Functions.Isolated
+
+
Extend the Program.cs
file with the following code:
+
.ConfigureFunctionsWorkerDefaults((context, app) =>
+{
+ app.AddHeartbeat(options =>
+ {
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+ options.HeartbeatId = "HEARTBEAT_ID";
+ });
+})
+
+
The code installs the heartbeat middleware, which will handle all of the communication with the elmah.io API.
+
Manually using Elmah.Io.Client
+
The example above installs the heartbeat filter for all functions. If you have multiple functions inside your function app, or you want greater control of when and how to send heartbeats, you can use Elmah.Io.Client
to create heartbeats.
+
Start by installing the Elmah.Io.Client
NuGet package:
+
Install-Package Elmah.Io.Client
+
dotnet add package Elmah.Io.Client
+
<PackageReference Include="Elmah.Io.Client" Version="5.*" />
+
paket add Elmah.Io.Client
+
+
Extend the Program.cs
file with the following code:
+
.ConfigureServices((ctx, services) =>
+{
+ var elmahIo = ElmahioAPI.Create("API_KEY");
+ services.AddSingleton(elmahIo.Heartbeats);
+});
+
+
Inside your function, wrap all of the code in try/catch
and add code to create either a Healthy
or Unhealthy
heartbeat:
+
public class TimedFunction
+{
+ private readonly IHeartbeats heartbeats;
+ private readonly IConfiguration configuration;
+
+ public TimedFunction(IHeartbeats heartbeats, IConfiguration configuration)
+ {
+ this.heartbeats = heartbeats;
+ this.configuration = configuration;
+ }
+
+ [FunctionName("TimedFunction")]
+ public async Task Run([TimerTrigger("0 0 * * * *")]TimerInfo myTimer)
+ {
+ var heartbeatId = configuration["heartbeatId"];
+ var logId = configuration["logId"];
+ try
+ {
+ // Your function code goes here
+
+ await heartbeats.CreateAsync(heartbeatId, logId, new CreateHeartbeat
+ {
+ Result = "Healthy"
+ });
+ }
+ catch (Exception e)
+ {
+ await heartbeats.CreateAsync(heartbeatId, logId, new CreateHeartbeat
+ {
+ Result = "Unhealthy",
+ Reason = e.ToString(),
+ });
+ }
+ }
+}
+
+
If your function code executes successfully, a Healthy
heartbeat is created. If an exception is thrown, an Unhealthy
heartbeat with the thrown exception in Reason
is created.
+
Be aware that configuring a function to run in an internal (like every hour for the example above) does not ensure that the function is executed exactly on the hour. We recommend to set the grace period for these types of heartbeats to 15-30 minutes to avoid heartbeat errors when the timed function is past due.
+
Using a separate heartbeat function
+
You may want a single heartbeat representing your entire function app consisting of multiple functions. This is a good option if you want to create heartbeats from queue-triggered functions or similar. In these cases, you don't want to create a heartbeat every time a message from the queue is handled, but you will want to notify elmah.io if dependencies like database connection suddenly aren't available. We recommend creating a new heartbeat function for this kind of Function. Like in the previous example, make sure to extend your Program.cs
file like this:
+
.ConfigureServices((ctx, services) =>
+{
+ var elmahIo = ElmahioAPI.Create("API_KEY");
+ services.AddSingleton(elmahIo.Heartbeats);
+});
+
+
Then create a new timed function with the following code:
+
using System;
+using System.Threading.Tasks;
+using Elmah.Io.Client;
+using Microsoft.Azure.WebJobs;
+using Microsoft.Extensions.Configuration;
+
+namespace My.FunctionApp
+{
+ public class Heartbeat
+ {
+ private readonly IConfiguration config;
+ private readonly IHeartbeats heartbeats;
+
+ public Heartbeat(IHeartbeats heartbeats, IConfiguration config)
+ {
+ this.heartbeats = heartbeats;
+ this.config = config;
+ }
+
+ [FunctionName("Heartbeat")]
+ public async Task Run([TimerTrigger("0 */5 * * * *")]TimerInfo myTimer)
+ {
+ var result = "Healthy";
+ var reason = (string)null;
+ try
+ {
+ // Check your dependencies here
+ }
+ catch (Exception e)
+ {
+ result = "Unhealthy";
+ reason = e.ToString();
+ }
+
+ await heartbeats.CreateAsync(config["heartbeatId"], config["logId"], new CreateHeartbeat
+ {
+ Result = result,
+ Reason = reason,
+ });
+ }
+ }
+}
+
+
In the example above, the new function named Heartbeat
(the name is entirely up to you) executes every 5 minutes. Replace the comment with your checks like opening a connection to the database. If everything works as it should, a Healthy
heartbeat is logged to elmah.io. If an exception is thrown while checking your dependencies, an Unhealthy
heartbeat is created.
+
When running locally, you may want to disable heartbeats:
+
#if DEBUG
+[FunctionName("Heartbeat")]
+#endif
+public async Task Run([TimerTrigger("0 */5 * * * *")]TimerInfo myTimer)
+{
+ // ...
+}
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-heartbeats-from-net-core-worker-services/index.html b/logging-heartbeats-from-net-core-worker-services/index.html
new file mode 100644
index 0000000000..32af13b8f5
--- /dev/null
+++ b/logging-heartbeats-from-net-core-worker-services/index.html
@@ -0,0 +1,717 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging heartbeats from .NET (Core) Worker Services
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging heartbeats from .NET (Core) Worker Services
+
.NET (Core) offers Worker Services as a way to schedule recurring tasks either hosted inside an ASP.NET Core website or as a Windows Service. Monitoring that Worker Services run successfully, can be easily set up with elmah.io Heartbeats.
+
To register heartbeats from a worker service, start by creating a new heartbeat on the elmah.io UI. For this example, we want to monitor that a Service Worker is running every 5 minutes, why we set Interval to 5 minutes and Grace to 1 minute. Next, install the Elmah.Io.Client
NuGet package:
+
Install-Package Elmah.Io.Client
+
dotnet add package Elmah.Io.Client
+
<PackageReference Include="Elmah.Io.Client" Version="5.*" />
+
paket add Elmah.Io.Client
+
+
In the Program.cs
or Startup.cs
file (depending on where you register dependencies), register IHeartbeats
from the elmah.io client:
+
.ConfigureServices((hostContext, services) =>
+{
+ var elmahIoApi = ElmahioAPI.Create(hostContext.Configuration["ElmahIo:ApiKey"]);
+ services.AddSingleton(elmahIoApi.Heartbeats);
+ // ...
+ services.AddHostedService<Worker>();
+});
+
+
In the example, the configuration should be made available in the appsettings.json
file as shown later in this article.
+
In the service class (Worker
) you can inject the IHeartbeats
object, as well as additional configuration needed to create heartbeats:
+
public class Worker : BackgroundService
+{
+ private readonly IHeartbeats heartbeats;
+ private readonly Guid logId;
+ private readonly string heartbeatId;
+
+ public Worker(IHeartbeats heartbeats, IConfiguration configuration)
+ {
+ this.heartbeats = heartbeats;
+ this.logId = new Guid(configuration["ElmahIo:LogId"]);
+ this.heartbeatId = configuration["ElmahIo:HeartbeatId"];
+ }
+}
+
+
Inside the ExecuteAsync
method, wrap the worker code in try-catch and call the HealthyAsync
method when the worker successfully run and the UnhealthyAsync
method when an exception occurs:
+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+{
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ try
+ {
+ // Do work
+
+ await heartbeats.HealthyAsync(logId, heartbeatId);
+ }
+ catch (Exception e)
+ {
+ await heartbeats.UnhealthyAsync(logId, heartbeatId, e.ToString());
+ }
+
+ await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
+ }
+}
+
+
In the appsettings.json
file, add the elmah.io configuration:
+
{
+ "ElmahIo": {
+ "ApiKey": "API_KEY",
+ "LogId": "LOG_ID",
+ "HeartbeatId": "HEARTBEAT_ID"
+ }
+}
+
+
Replace the values with values found in the elmah.io UI. Remember to enable the Heartbeats | Write permission on the used API key.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-heartbeats-from-powershell/index.html b/logging-heartbeats-from-powershell/index.html
new file mode 100644
index 0000000000..e0983c13bb
--- /dev/null
+++ b/logging-heartbeats-from-powershell/index.html
@@ -0,0 +1,681 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging heartbeats from PowerShell
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging heartbeats from PowerShell
+
The Heartbeats feature is a great way to verify that scripts run successfully too. A lot of people have PowerShell scripts running on a schedule to clean up folders on the file system, make batch changes in a database, and more.
+
To include heartbeats in your PowerShell script, wrap the code in try/catch
and add either Healthy
or Unhealthy
result:
+
$apiKey = "API_KEY"
+$logId = "LOG_ID"
+$heartbeatId = "HEARTBEAT_ID"
+$url = "https://api.elmah.io/v3/heartbeats/$logId/$heartbeatId/?api_key=$apiKey"
+
+try
+{
+ # Your script goes here
+
+ $body = @{
+ result = "Healthy"
+ }
+ Invoke-RestMethod -Method Post -Uri $url -Body ($body|ConvertTo-Json) -ContentType "application/json-patch+json"
+}
+catch
+{
+ $body = @{
+ result = "Unhealthy"
+ reason = $_.Exception.Message
+ }
+ Invoke-RestMethod -Method Post -Uri $url -Body ($body|ConvertTo-Json) -ContentType "application/json-patch+json"
+}
+
+
If everything goes well, a Healthy
heartbeat is logged using the Invoke-RestMethod
cmdlet. If an exception is thrown in your script, an Unhealthy
heartbeat is logged.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-heartbeats-from-umbraco/index.html b/logging-heartbeats-from-umbraco/index.html
new file mode 100644
index 0000000000..9006ca4dbc
--- /dev/null
+++ b/logging-heartbeats-from-umbraco/index.html
@@ -0,0 +1,745 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging heartbeats from Umbraco
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging heartbeats from Umbraco
+
+
Umbraco comes with a nice health check feature that can carry out a range of built-in health checks as well as custom checks you may want to add. Umbraco Health Checks fits perfectly with elmah.io Heartbeats .
+
To start publishing Umbraco Health Checks to elmah.io, create a new health check. Select 1 day in Interval and 5 minutes in Grace . The next step depends on the major version of Umbraco. For both examples, replace API_KEY
, LOG_ID
, and HEARTBEAT_ID
with the values found on the elmah.io UI.
+
When launching the website Umbraco automatically executes the health checks once every 24 hours and sends the results to elmah.io.
+
Umbraco >= 9
+
+Umbraco 9 is targeting .NET 5.0 which is no longer supported by Microsoft. This is why we have chosen to support Umbraco 10 and up only.
+
+
Install the Elmah.Io.Umbraco
NuGet package:
+
Install-Package Elmah.Io.Umbraco
+
dotnet add package Elmah.Io.Umbraco
+
<PackageReference Include="Elmah.Io.Umbraco" Version="5.*" />
+
paket add Elmah.Io.Umbraco
+
+
To publish health check results to your newly created heartbeat, extend the appsettings.json
file:
+
{
+ ...
+ "Umbraco": {
+ "CMS": {
+ ...
+ "HealthChecks": {
+ "Notification": {
+ "Enabled": true,
+ "NotificationMethods": {
+ "elmah.io": {
+ "Enabled": true,
+ "Verbosity": "Summary",
+ "Settings": {
+ "apiKey": "API_KEY",
+ "logId": "LOG_ID",
+ "heartbeatId": "HEARTBEAT_ID"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
Umbraco 8
+
install the Elmah.Io.Umbraco
v4 NuGet package:
+
Install-Package Elmah.Io.Umbraco -Version 4.2.21
+
dotnet add package Elmah.Io.Umbraco --version 4.2.21
+
<PackageReference Include="Elmah.Io.Umbraco" Version="4.2.21" />
+
paket add Elmah.Io.Umbraco --version 4.2.21
+
+
For Umbraco to automatically execute health checks, you will need to set your back office URL in the umbracoSettings.config
file:
+
<?xml version="1.0" encoding="utf-8" ?>
+<settings>
+ <!-- ... -->
+ <web.routing umbracoApplicationUrl="https://localhost:44381/umbraco/">
+ </web.routing>
+</settings>
+
+
(localhost is used as an example and should be replaced with a real URL)
+
Umbraco comes with an email publisher already configured. To publish health check results to your newly created heartbeat, extend the HealthChecks.config
file:
+
<?xml version ="1.0" encoding="utf-8" ?>
+<HealthChecks>
+ <disabledChecks>
+ </disabledChecks>
+ <notificationSettings enabled="true" firstRunTime="" periodInHours="24">
+ <notificationMethods>
+ <notificationMethod alias="elmah.io" enabled="true" verbosity="Summary">
+ <settings>
+ <add key="apiKey" value="API_KEY" />
+ <add key="logId" value="LOG_ID" />
+ <add key="heartbeatId" value="HEARTBEAT_ID" />
+ </settings>
+ </notificationMethod>
+ <notificationMethod alias="email" enabled="false" verbosity="Summary">
+ <settings>
+ <add key="recipientEmail" value="" />
+ </settings>
+ </notificationMethod>
+ </notificationMethods>
+ <disabledChecks>
+ </disabledChecks>
+ </notificationSettings>
+</HealthChecks>
+
+
For this example, I have disabled the email notification publisher but you can run with both if you'd like.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-heartbeats-from-windows-scheduled-tasks/index.html b/logging-heartbeats-from-windows-scheduled-tasks/index.html
new file mode 100644
index 0000000000..84d68e21d9
--- /dev/null
+++ b/logging-heartbeats-from-windows-scheduled-tasks/index.html
@@ -0,0 +1,703 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging heartbeats from Windows Scheduled Tasks
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging heartbeats from Windows Scheduled Tasks
+
How you want to implement heartbeats from Windows Scheduled Tasks depends on how your task is implemented. For tasks written in C#, you typically want to persist heartbeats using the Elmah.Io.Client
package as shown in Set up Heartbeats . For scheduled PowerShell or other scripts, you can trigger the elmah.io API as shown in Logging heartbeats from PowerShell and Logging heartbeats from cURL .
+
Invoking the API either through the elmah.io client or using a REST client is the optimal way since you have greater control over what to log. This manual approach is not available when you are not in control of the scheduled code. Examples of this could be custom tools you install as Scheduled Tasks using schtasks.exe
or tasks automatically registered when installing third-party software on your server.
+
When configuring a heartbeat through the elmah.io UI you set an expected interval and grace period. If a heartbeat is not received in time, we will automatically log a missing heartbeat error. This will indicate that the scheduled task didn't run or fail and is something that should be looked at. In case you want to log a failing heartbeat as soon as the scheduled task is failing, you can do that using events logged to the Windows Event Log. To set it up, go through the following steps:
+
+Open Task Scheduler .
+Go to Task Scheduler Library and click the Create Task... button.
+Give the new task a proper name.
+In the Triggers tab click the New... button.
+In the New Trigger window select On an event in the Begin the task dropdown.
+In Settings select the Custom radio button.
+Click the New Event Filter... button.
+Select the XML tab and check the Edit query manually checkbox.
+Now input the XML from the following screenshot (source code later in this article). The custom query will trigger on all Application Error messages from an app named ConsoleApp14.exe . You will need to change the app name to the filename of the app running in the scheduled task.
+
+Click the OK button.
+In the New Trigger window click the OK button to save the trigger.
+Select the Actions tab.
+Click the New... button.
+Select the Start a program option in the Action dropdown.
+Input values like shown here:
+
+Click the OK button to save the action.
+Click the OK button to save the task.
+The final step is to add the c:\scripts\heartbeat.ps1
PowerShell script invoked by the task. For a better understanding of storing heartbeats through PowerShell check out Logging heartbeats from PowerShell . To log a failing heartbeat to elmah.io you can use the following PowerShell code:
+
+
$apiKey = "API_KEY"
+$logId = "LOG_ID"
+$heartbeatId = "HEARTBEAT_ID"
+$url = "https://api.elmah.io/v3/heartbeats/$logId/$heartbeatId/?api_key=$apiKey"
+
+$body = @{
+ result = "Unhealthy"
+ reason = "Error in scheduled task"
+}
+Invoke-RestMethod -Method Post -Uri $url -Body ($body|ConvertTo-Json) -ContentType "application/json-patch+json"
+
+
That's it. The new task just created will trigger every time there's an application error from your application. Notice that in case your application is manually logging errors to the event log, this will also trigger the task.
+
Source code for reference:
+
<QueryList>
+ <Query Id="0" Path="Application">
+ <Select Path="Application">
+*[System[Provider[@Name='Application Error']]]
+and
+*[EventData[(Data[@Name="AppName"]="ConsoleApp14.exe")]]
+ </Select>
+ </Query>
+</QueryList>
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-through-a-http-proxy/index.html b/logging-through-a-http-proxy/index.html
new file mode 100644
index 0000000000..56a4a892b3
--- /dev/null
+++ b/logging-through-a-http-proxy/index.html
@@ -0,0 +1,717 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging through a HTTP proxy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging through a HTTP proxy
+
You may find yourself in a situation, where your production web servers aren't allowing HTTP requests towards the public Internet. This also impacts the elmah.io client, which requires access to the URL https://api.elmah.io. A popular choice of implementing this kind of restriction nowadays is through a HTTP proxy like Squid or Nginx.
+
Luckily the elmah.io client supports proxy configuration out of the box. Let's look at how to configure a HTTP proxy through web.config
:
+
<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <configSections>
+ <sectionGroup name="elmah">
+ <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah" />
+ <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
+ <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
+ <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
+ </sectionGroup>
+ </configSections>
+ <elmah>
+ <security allowRemoteAccess="false" />
+ <errorLog type="Elmah.Io.ErrorLog, Elmah.Io" apiKey="..." logId="..." />
+ </elmah>
+ <system.net>
+ <defaultProxy>
+ <proxy usesystemdefault="True" proxyaddress="http://192.168.0.1:3128" bypassonlocal="False"/>
+ </defaultProxy>
+ </system.net>
+</configuration>
+
+
The above example is of course greatly simplified.
+
The elmah.io client automatically picks up the defaultProxy
configuration through the system.net
element. defaultProxy
tunnels every request from your server, including requests to elmah.io, through the proxy located on 192.18.0.1 port 3128 (or whatever IP/hostname and port you are using).
+
Proxies with username/password
+
Some proxies require a username/password. Unfortunately, the defaultProxy
element doesn't support authentication. You have two ways to set this up:
+
Use default credentials
+
Make sure to set the useDefaultCredentials
attribute to true
:
+
<system.net>
+ <defaultProxy useDefaultCredentials="true">
+ <!-- ... -->
+ </defaultProxy>
+</system.net>
+
+
Run your web app (application pool) as a user with access to the proxy.
+
Implement your own proxy
+
Add the following class:
+
public class AuthenticatingProxy : IWebProxy
+{
+ public ICredentials Credentials
+ {
+ get { return new NetworkCredential("username", "password"); }
+ set {}
+ }
+
+ public Uri GetProxy(Uri destination)
+ {
+ return new Uri("http://localhost:8888");
+ }
+
+ public bool IsBypassed(Uri host)
+ {
+ return false;
+ }
+}
+
+
Configure the new proxy in web.config
:
+
<defaultProxy useDefaultCredentials="false">
+ <module type="YourNamespace.AuthenticatingProxy, YourAssembly" />
+</defaultProxy>
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-a-running-website-on-azure/index.html b/logging-to-elmah-io-from-a-running-website-on-azure/index.html
new file mode 100644
index 0000000000..3de637379b
--- /dev/null
+++ b/logging-to-elmah-io-from-a-running-website-on-azure/index.html
@@ -0,0 +1,694 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from a running website on Azure
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from a running website on Azure
+
+Adding elmah.io on a running website isn't the recommended way to install. It should be used if you are unable to deploy a new version only.
+
+
To enable error logging to elmah.io, you usually install one of our client integrations through PowerShell or Visual Studio and deploy a new version of your website to a web server. Sometimes you need to monitor an already running website or don't want logging logic as part of your repository. Using the elmah.io Site Extension for Azure App Services, error logging can be added to an already running website.
+
Check out this video tutorial or keep reading for the text version:
+
+
+
+
+
To start logging errors from your Azure web application, go to the Azure Portal and select the website you want to monitor. Click the Extensions tool:
+
+
Click the Add button and select .NET elmah.io for Azure :
+
+
Accept the terms and click the Add button. The elmah.io Site Extension is now added. Once added, restart the website for the new extension to load.
+
Finally, you need to add your API key (Where is my API key? ) and log ID (Where is my log ID? ) to Application settings :
+
+
Make sure to use the app setting names ELMAHIO_APIKEY
and ELMAHIO_LOGID
.
+
Your Azure web application now logs all uncaught exceptions to elmah.io. The elmah.io Site Extension comes with a couple of limitations:
+
+It only works for ASP.NET, MVC, Web API, and similar. ASP.NET Core websites should be installed locally and re-deployed.
+.NET Full Framework 4.6 and newer is required.
+Custom code or configuration may swallow exceptions. Like custom errors or when using the HandleErrorAttribute
attribute in ASP.NET MVC. In this case, the correct NuGet package needs to be installed in your code and deployed to Azure (like the Elmah.Io.Mvc
package for ASP.NET MVC).
+
+
Troubleshooting
+
ConfigurationErrorsException: Could not load file or assembly 'Elmah' or one of its dependencies. The system cannot find the file specified.
+
After uninstalling the elmah.io site extension, you may see the configuration error above. This means that elmah.io's uninstall script for some reason wasn't allowed to run or resulted in an error.
+
To make sure that elmah.io is completely removed, follow these steps:
+
+Stop your website.
+Browse your website files through Kudu.
+Remove all files starting with Elmah
.
+Start your website.
+
+
Error while uninstalling the site extension
+
While uninstalling the site extension you may see errors like this:
+
Failed to delete Site Extension: .NET elmah.io for Azure.{"Message":"An error has occurred.","ExceptionMessage":"The system cannot find the file specified.
+C:\home\SiteExtensions\Elmah.Io.Azure.SiteExtension\uninstall.cmd
+
+
In this case, the elmah.io for Azure site extension needs to be uninstalled manually. To do that, go to Kudu Services beneath the Advanced Tools section in the website on Azure. In the Debug console navigate to site/wwwroot/bin
and delete all files prefixed with Elmah
(up to four files).
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-a-running-website-on-iis/index.html b/logging-to-elmah-io-from-a-running-website-on-iis/index.html
new file mode 100644
index 0000000000..b5e1494afe
--- /dev/null
+++ b/logging-to-elmah-io-from-a-running-website-on-iis/index.html
@@ -0,0 +1,671 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from a running website on IIS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from a running website on IIS
+
+Adding elmah.io on a running website isn't the recommended way to install. It should be used if you are unable to deploy a new version only.
+
+
To enable error logging to elmah.io, you usually install one of our client integrations through PowerShell or Visual Studio and deploy a new version of your website to a web server. Sometimes you need to monitor an already running website or don't want logging logic as part of your repository. elmah.io can be added to a running website by following this guide.
+
Run the following command somewhere on your computer:
+
nuget install elmah.io
+
+
It is recommended to run this locally to avoid having to install nuget.exe
on the machine running IIS (typically a production environment). If you don't have NuGet installed, there are a range of download options available here .
+
From the folder where you ran the command, copy the following files to the bin
folder of your running website:
+
elmah.corelibrary.x.y.z\lib\Elmah.dll
+elmah.io.x.y.z\lib\net45\Elmah.Io.dll
+Elmah.Io.Client.x.y.z\lib\<.net version your website is using>\Elmah.Io.Client.dll
+Newtonsoft.Json.x.y.z\lib\<.net version your website is using>\Newtonsoft.Json.dll
+
+
Configure elmah.io in Web.config
as described here: Configure elmah.io manually (you don't need to call the Install-Package
command). Notice that the AppDomain will restart when saving changes to the Web.config
file.
+
If the website doesn't start logging errors to elmah.io, you may need to restart it.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-angular/index.html b/logging-to-elmah-io-from-angular/index.html
new file mode 100644
index 0000000000..6a6dba2e35
--- /dev/null
+++ b/logging-to-elmah-io-from-angular/index.html
@@ -0,0 +1,721 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Angular
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Angular
+
elmah.io.javascript
works great with Angular applications too. To log all errors happening in your Angular app, install elmah.io.javascript
through npm as described in Logging from JavaScript .
+
In the same folder as the app.module.ts
file add a new file named elmah-io-error-handler.ts
and include the following content:
+
import {ErrorHandler} from '@angular/core';
+
+import * as Elmahio from 'elmah.io.javascript';
+
+export class ElmahIoErrorHandler implements ErrorHandler {
+ logger: Elmahio;
+ constructor() {
+ this.logger = new Elmahio({
+ apiKey: 'API_KEY',
+ logId: 'LOG_ID'
+ });
+ }
+ handleError(error) {
+ if (error && error.message) {
+ this.logger.error(error.message, error);
+ } else {
+ this.logger.error('Error in application', error);
+ }
+ }
+}
+
+
Reference both ErrorHandler
and ElmahIoErrorHandler
in the app.module.ts
file:
+
import { BrowserModule } from '@angular/platform-browser';
+import {ErrorHandler, NgModule} from '@angular/core'; // ⬅️ Add ErrorHandler
+
+import { AppRoutingModule } from './app-routing.module';
+import { AppComponent } from './app.component';
+
+import {ElmahIoErrorHandler} from './elmah-io-error-handler'; // ⬅️ Reference ElmahIoErrorHandler
+
+@NgModule({
+ declarations: [
+ AppComponent
+ ],
+ imports: [
+ BrowserModule,
+ AppRoutingModule
+ ],
+ // ⬇️ Reference both handlers
+ providers: [{ provide: ErrorHandler, useClass: ElmahIoErrorHandler }],
+ bootstrap: [AppComponent]
+})
+export class AppModule { }
+
+
All errors are shipped to the handleError
-function by Angular and logged to elmah.io. Check out the Elmah.Io.JavaScript.AngularAspNetCore and Elmah.Io.JavaScript.AngularWebpack samples for some real working code.
+
AngularJS/Angular 1
+
For AngularJS you need to implement the $exceptionHandler
instead:
+
(function () {
+ 'use strict';
+ angular.module('app').factory('$exceptionHandler', ['$log', function controller($log) {
+ var logger = new Elmahio({
+ apiKey: 'API_KEY',
+ logId: 'LOG_ID'
+ });
+ return function elmahExceptionHandler(exception, cause) {
+ $log.error(exception, cause);
+ logger.error(exception.message, exception);
+ };
+ }]);
+})();
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-aspnet-core/index.html b/logging-to-elmah-io-from-aspnet-core/index.html
new file mode 100644
index 0000000000..401f8a65ca
--- /dev/null
+++ b/logging-to-elmah-io-from-aspnet-core/index.html
@@ -0,0 +1,1002 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging from ASP.NET Core
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from ASP.NET Core
+
+
If you are looking to log all uncaught errors from ASP.NET Core, you've come to the right place. For help setting up general .NET Core logging similar to log4net, check out Logging from Microsoft.Extensions.Logging .
+
To log all warnings and errors from ASP.NET Core, install the following NuGet package:
+
Install-Package Elmah.Io.AspNetCore
+
dotnet add package Elmah.Io.AspNetCore
+
<PackageReference Include="Elmah.Io.AspNetCore" Version="5.*" />
+
paket add Elmah.Io.AspNetCore
+
+
In the Startup.cs
file, add a new using
statement:
+
using Elmah.Io.AspNetCore;
+
+
+
+
+
+
Call AddElmahIo
in the ConfigureServices
-method:
+
public void ConfigureServices(IServiceCollection services)
+{
+ services.AddElmahIo(options =>
+ {
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+ });
+ // ...
+}
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
(Where is my log ID? ) with the log Id of the log you want to log to.
+
Call UseElmahIo
in the Configure
-method:
+
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory fac)
+{
+ // ...
+ app.UseElmahIo();
+ // ...
+}
+
+
+
+
Call AddElmahIo
in the Program.cs
file:
+
builder.Services.AddElmahIo(options =>
+{
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+});
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
(Where is my log ID? ) with the log Id of the log you want to log to.
+
Call UseElmahIo
in the Program.cs
file:
+
app.UseElmahIo();
+
+
+
+
+Make sure to call the UseElmahIo
-method after installation of other pieces of middleware handling exceptions and auth (like UseDeveloperExceptionPage
, UseExceptionHandler
, UseAuthentication
, and UseAuthorization
), but before any calls to UseEndpoints
, UseMvc
, MapRazorPages
, and similar.
+
+
That's it. Every uncaught exception will be logged to elmah.io. For an example of configuring elmah.io with ASP.NET Core minimal APIs, check out this sample .
+
Configuring API key and log ID in options
+
If you have different environments (everyone has a least localhost and production), you should consider adding the API key and log ID in your appsettings.json
file:
+
{
+ // ...
+ "ElmahIo": {
+ "ApiKey": "API_KEY",
+ "LogId": "LOG_ID"
+ }
+}
+
+
Configuring elmah.io is done by calling the Configure
-method before AddElmahIo
:
+
+
+
+
+
public void ConfigureServices(IServiceCollection services)
+{
+ services.Configure<ElmahIoOptions>(Configuration.GetSection("ElmahIo"));
+ services.AddElmahIo();
+}
+
+
Notice that you still need to call AddElmahIo
to correctly register middleware dependencies.
+
Finally, call the UseElmahIo
-method (as you would do with config in C# too):
+
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+{
+ // ...
+ app.UseElmahIo();
+ // ...
+}
+
+
You can still configure additional options on the ElmahIoOptions
object:
+
public void ConfigureServices(IServiceCollection services)
+{
+ services.Configure<ElmahIoOptions>(Configuration.GetSection("ElmahIo"));
+ services.Configure<ElmahIoOptions>(options =>
+ {
+ options.OnMessage = msg =>
+ {
+ msg.Version = "1.0.0";
+ };
+ });
+ services.AddElmahIo();
+}
+
+
+
+
builder.Services.Configure<ElmahIoOptions>(builder.Configuration.GetSection("ElmahIo"));
+builder.Services.AddElmahIo();
+
+
Notice that you still need to call AddElmahIo
to correctly register middleware dependencies.
+
Finally, call the UseElmahIo
-method (as you would do with config in C# too):
+
app.UseElmahIo();
+
+
You can still configure additional options on the ElmahIoOptions
object:
+
builder.Services.Configure<ElmahIoOptions>(builder.Configuration.GetSection("ElmahIo"));
+builder.Services.Configure<ElmahIoOptions>(o =>
+{
+ o.OnMessage = msg =>
+ {
+ msg.Version = "1.0.0";
+ };
+});
+builder.Services.AddElmahIo();
+
+
+
+
Logging exceptions manually
+
While automatically logging all uncaught exceptions is definitely a nice feature, sometimes you may want to catch exceptions and log them manually. If you just want to log the exception details, without all of the contextual information about the HTTP context (cookies, server variables, etc.), we recommend you to look at our integration for Microsoft.Extensions.Logging . If the context is important for the error, you can utilize the Ship
-methods available in Elmah.Io.AspNetCore
:
+
try
+{
+ var i = 0;
+ var result = 42/i;
+}
+catch (DivideByZeroException e)
+{
+ e.Ship(HttpContext);
+}
+
+
When catching an exception (in this example an DivideByZeroException ), you call the Ship
extension method with the current HTTP context as parameter.
+
From Elmah.Io.AspNetCore
version 3.12.*
or newer, you can log manually using the ElmahIoApi
class as well:
+
ElmahIoApi.Log(e, HttpContext);
+
+
The Ship
-method uses ElmahIoApi
underneath why both methods will give the same end result.
+
Breadcrumbs
+
See Logging breadcrumbs from ASP.NET Core .
+
Additional options
+
Setting application name
+
If logging to the same log from multiple web apps it is a good idea to set unique application names from each app. This will let you search and filter errors on the elmah.io UI. To set an application name, add the following code to the options:
+
builder.Services.AddElmahIo(o =>
+{
+ // ...
+ o.Application = "MyApp";
+});
+
+
The application name can also be configured through appsettings.json
:
+
{
+ // ...
+ "ElmahIo": {
+ // ...
+ "Application": "MyApp"
+ }
+}
+
+
Hooks
+
elmah.io for ASP.NET Core supports a range of actions for hooking into the process of logging messages. Hooks are registered as actions when installing the elmah.io middleware:
+
builder.Services.AddElmahIo(options =>
+{
+ // ...
+ options.OnMessage = message =>
+ {
+ message.Version = "42";
+ };
+ options.OnError = (message, exception) =>
+ {
+ logger.LogError(1, exception, "Error during log to elmah.io");
+ };
+});
+
+
The actions provide a mechanism for hooking into the log process. The action registered in the OnMessage
property is called by elmah.io just before logging a new message to the API. Use this action to decorate/enrich your log messages with additional data, like a version number. The OnError
action is called if communication with the elmah.io API failed. If this happens, you should log the message to a local log (through Microsoft.Extensions.Logging, Serilog or similar).
+
+Do not log to elmah.io in your OnError
action, since that could cause an infinite loop in your code.
+
+
While elmah.io supports ignore rules serverside, you may want to filter out errors without even hitting the elmah.io API. Using the OnFilter
function on the options object, filtering is easy:
+
builder.Services.AddElmahIo(options =>
+{
+ // ...
+ options.OnFilter = message =>
+ {
+ return message.Type == "System.NullReferenceException";
+ };
+});
+
+
The example above, ignores all messages of type System.NullReferenceException .
+
Decorate from HTTP context
+
When implementing the OnMessage
action as shown above you don't have access to the current HTTP context. Elmah.Io.AspNetCore
already tries to fill in as many fields as possible from the current context, but you may want to tweak something from time to time. In this case, you can implement a custom decorator like this:
+
public class DecorateElmahIoMessages : IConfigureOptions<ElmahIoOptions>
+{
+ private readonly IHttpContextAccessor httpContextAccessor;
+
+ public DecorateElmahIoMessages(IHttpContextAccessor httpContextAccessor)
+ {
+ this.httpContextAccessor = httpContextAccessor;
+ }
+
+ public void Configure(ElmahIoOptions options)
+ {
+ options.OnMessage = msg =>
+ {
+ var context = httpContextAccessor.HttpContext;
+ msg.User = context?.User?.Identity?.Name;
+ };
+ }
+}
+
+
+
+
+
+
Then register IHttpContextAccessor
and the new class in the ConfigureServices
method in the Startup.cs
file:
+
public void ConfigureServices(IServiceCollection services)
+{
+ services.AddHttpContextAccessor();
+ services.AddSingleton<IConfigureOptions<ElmahIoOptions>, DecorateElmahIoMessages>();
+ // ...
+}
+
+
+
+
Then register IHttpContextAccessor
and the new class in the in the Program.cs
file:
+
builder.Services.AddHttpContextAccessor();
+builder.Services.AddSingleton<IConfigureOptions<ElmahIoOptions>, DecorateElmahIoMessages>();
+
+
+
+
+Decorating messages using IConfigureOptions
requires Elmah.Io.AspNetCore
version 4.1.37
or newer.
+
+
Include source code
+
You can use the OnMessage
action to include source code to log messages. This will require a stack trace in the Detail
property with filenames and line numbers in it.
+
There are multiple ways of including source code to log messages. In short, you will need to install the Elmah.Io.Client.Extensions.SourceCode
NuGet package and call the WithSourceCodeFromPdb
method in the OnMessage
action:
+
builder.Services.AddElmahIo(options =>
+{
+ // ...
+ options.OnMessage = msg =>
+ {
+ msg.WithSourceCodeFromPdb();
+ };
+});
+
+
Check out How to include source code in log messages for additional requirements to make source code show up on elmah.io.
+
+Including source code on log messages is available in the Elmah.Io.Client
v4 package and forward.
+
+
+
The OnMessage
event can be used to filter sensitive form data as well. In the following example, we remove the server variable named Secret-Key
from all messages, before sending them to elmah.io.
+
builder.Services.AddElmahIo(options =>
+{
+ // ...
+ options.OnMessage = msg =>
+ {
+ var item = msg.ServerVariables.FirstOrDefault(x => x.Key == "Secret-Key");
+ if (item != null)
+ {
+ msg.ServerVariables.Remove(item);
+ }
+ };
+});
+
+
+
A default exception formatter is used to format any exceptions, before sending them off to the elmah.io API. To override the format of the details field in elmah.io, set a new IExceptionFormatter
in the ExceptionFormatter
property on the ElmahIoOptions
object:
+
builder.Services.AddElmahIo(options =>
+{
+ // ...
+ options.ExceptionFormatter = new DefaultExceptionFormatter();
+}
+
+
Besides the default exception formatted (DefaultExceptionFormatter
), Elmah.Io.AspNetCore comes with a formatter called YellowScreenOfDeathExceptionFormatter
. This formatter, outputs an exception and its inner exceptions as a list of exceptions, much like on the ASP.NET yellow screen of death. If you want, implementing your own exception formatter, requires you to implement a single method.
+
Logging responses not throwing an exception
+
As default, uncaught exceptions (500's) and 404's are logged automatically. Let's say you have a controller returning a Bad Request and want to log that as well. Since returning a 400 from a controller doesn't trigger an exception, you will need to configure this status code:
+
builder.Services.AddElmahIo(options =>
+{
+ // ...
+ options.HandledStatusCodesToLog = new List<int> { 400 };
+}
+
+
The list can also be configured through appsettings.json
:
+
{
+ // ...
+ "ElmahIo": {
+ // ...
+ "HandledStatusCodesToLog": [ 400 ],
+ }
+}
+
+
When configuring status codes through the appsettings.json
file, 404
s will always be logged. To avoid this, configure the list in C# as shown above.
+
Logging through a proxy
+
Since ASP.NET Core no longer support proxy configuration through web.config
, you can log to elmah.io by configuring a proxy manually:
+
builder.Services.AddElmahIo(options =>
+{
+ // ...
+ options.WebProxy = new System.Net.WebProxy("localhost", 8888);
+}
+
+
In this example, the elmah.io client routes all traffic through http://localhost:8000
.
+
Logging health check results
+
Check out Logging heartbeats from ASP.NET Core for details.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-aspnet-mvc/index.html b/logging-to-elmah-io-from-aspnet-mvc/index.html
new file mode 100644
index 0000000000..68cd55b884
--- /dev/null
+++ b/logging-to-elmah-io-from-aspnet-mvc/index.html
@@ -0,0 +1,668 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from ASP.NET MVC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from ASP.NET MVC
+
Even though ELMAH works out of the box with ASP.NET MVC, ELMAH and MVC provide some features which interfere with one another. As usual, the great community around ELMAH has done something to fix this, by using the Elmah.Mvc NuGet package. We've built a package for ASP.NET MVC exclusively, which installs all the necessary packages.
+
To start logging exceptions from ASP.NET MVC, install the Elmah.Io.Mvc
NuGet package:
+
Install-Package Elmah.Io.Mvc
+
dotnet add package Elmah.Io.Mvc
+
<PackageReference Include="Elmah.Io.Mvc" Version="5.*" />
+
+
During the installation, you will be asked for your API key (Where is my API key? ) and log ID (Where is my log ID? ). That's it. Every unhandled exception in ASP.NET MVC is logged to elmah.io.
+
As part of the installation, we also installed Elmah.MVC
, which adds some interesting logic around routing and authentication. Take a look in the web.config
for application settings with the elmah.mvc.
prefix. For documentation about these settings, check out the Elmah.MVC project on GitHub.
+
Since Elmah.MVC
configures an URL for accessing the ELMAH UI (just /elmah
and not /elmah.axd
), you can remove the location
element in web.config
, added by the Elmah.Io.Mvc
NuGet package installer.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-aws-beanstalk/index.html b/logging-to-elmah-io-from-aws-beanstalk/index.html
new file mode 100644
index 0000000000..f138786f98
--- /dev/null
+++ b/logging-to-elmah-io-from-aws-beanstalk/index.html
@@ -0,0 +1,692 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from AWS Beanstalk
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from AWS Beanstalk
+
+
Logging to elmah.io from .NET applications deployed on AWS Beanstalk is as easy as with other cloud hosting services. Since Beanstalk runs normal ASP.NET, MVC, Web API, and Core applications, setting up elmah.io almost follows the guides already available in the elmah.io documentation. There are a few things to notice when needing to configure elmah.io, which will be explained in this document.
+
ASP.NET / MVC / Web API
+
To install elmah.io in ASP.NET , MVC , and/or Web API , please follow the guidelines for each framework. You can specify one set of API key and log ID in the Web.config
file and another set in the Web.release.config
file as explained here: Use multiple logs for different environments .
+
If you want to include your production API key and log ID on AWS only (to avoid having sensitive information in source control), you can do this using Environment properties on AWS. Go to your environment on the AWS console and click the Configuration tab. Click the Edit button beneath the Software category and scroll to the bottom. There you will see a section named Environment properties . Input your API key and log ID:
+
+
AWS inserts the properties as application settings in the Web.config
file. To make sure that elmah.io uses API key and log ID from appSettings
, change the <elmah>
element to reference the keys specified on AWS:
+
<errorLog type="Elmah.Io.ErrorLog, Elmah.Io" apiKeyKey="elmahio-apikey" logIdKey="elmahio-logid" />
+
+
The apiKeyKey
and logIdKey
attributes reference the app settings keys.
+
Finally, if you have an API key and/or log ID specified as part of the appSettings>
element in Web.config
, you will need to remove those when running in production. The reason for this is that AWS only insert missing keys. To do so, modify your Web.release.config
file:
+
<appSettings>
+ <add key="elmahio-logid" xdt:Transform="Remove" xdt:Locator="Match(key)" />
+ <add key="elmahio-apikey" xdt:Transform="Remove" xdt:Locator="Match(key)" />
+</appSettings>
+
+
ASP.NET Core
+
To install elmah.io in ASP.NET Core, follow this guide: Logging to elmah.io from ASP.NET Core .
+
If you want to include your production API key and log ID on AWS only (to avoid having sensitive information in source control), you can do this using Environment properties on AWS. Go to your environment on the AWS console and click the Configuration tab. Click the Edit button beneath the Software category and scroll to the bottom. There you will see a section named Environment properties . Input your API key and log ID:
+
+
This example uses the double underscore syntax to set the ApiKey
and LogId
properties in the appsettings.json
file:
+
{
+ "ElmahIo": {
+ "ApiKey": "API_KEY",
+ "LogId": "LOG_ID"
+ }
+}
+
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-aws-lambdas/index.html b/logging-to-elmah-io-from-aws-lambdas/index.html
new file mode 100644
index 0000000000..e34b360834
--- /dev/null
+++ b/logging-to-elmah-io-from-aws-lambdas/index.html
@@ -0,0 +1,705 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from AWS Lambdas
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from AWS Lambdas
+
+
Since AWS now supports .NET Core, logging to elmah.io from a lambda is easy.
+
Logging to elmah.io from AWS Serverless Application
+
AWS Serverless Applications are running on ASP.NET Core. The configuration matches our documentation for ASP.NET Core. Check out Logging from ASP.NET Core for details on how to log all uncaught exceptions from an AWS Serverless Application.
+
The .NET SDK for AWS comes with native support for logging to CloudWatch. We recommend using Microsoft.Extensions.Logging for logging everything to CloudWatch and warnings and errors to elmah.io. The configuration follows that of Logging from Microsoft.Extensions.Logging .
+
AWS Serverless Applications doesn't have a Program.cs
file. To configure logging, you will need to modify either LambdaEntryPoint.cs
, LocalEntryPoint.cs
or both:
+
public class LambdaEntryPoint : Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction
+{
+ protected override void Init(IWebHostBuilder builder)
+ {
+ builder
+ .UseStartup<Startup>()
+ .ConfigureLogging((ctx, logging) =>
+ {
+ logging.AddElmahIo(options =>
+ {
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+ });
+ logging.AddFilter<ElmahIoLoggerProvider>(null, LogLevel.Warning);
+ });
+ }
+}
+
+
The same configuration would go into LocalEntryPoint.cs
, if you want to log from localhost as well.
+
Logging when using Amazon.Lambda.AspNetCoreServer.Hosting
+
AWS supports running ASP.NET Core applications as Lambdas using the Amazon.Lambda.AspNetCoreServer.Hosting
package. This can serve as an easy way for .NET developers like us to create minimal API-based endpoints and deploy them as Lambda functions on AWS. There's a downside to deploying ASP.NET Core this way since AWS will kill the process when it decides that it is no longer needed. The Elmah.Io.Extensions.Logging
package runs an internal message queue and stores log messages asynchronously to better handle a large workload. When AWS kills the process without disposing of configured loggers, log messages queued for processing are left unhandled.
+
To solve this, Elmah.Io.Extensions.Logging
supports a property named Synchronous
that disables the internal message queue and stores log messages in a synchronous way. You may still experience log messages not being stored, but that's a consequence of AWS's choice of killing the process rather than shutting it down nicely (like ASP.NET Core).
+
To log messages synchronously, include the following code in your logging setup:
+
builder.Services.AddLogging(logging =>
+{
+ logging.AddElmahIo(options =>
+ {
+ // ...
+ options.Synchronous = true;
+ });
+});
+
+
Be aware that logging a large number of log messages synchronously, may slow down your application and/or cause thread exhaustion. We recommend only logging errors this way and not debug, information, and similar.
+
Logging from AWS Lambda Project
+
AWS Lambda Project comes with native support for CloudWatch too. In our experience, it's not possible to configure multiple destinations on LambdaLogger
, why you would want to use another framework when logging to elmah.io from an AWS Lambda Project. We recommend using a logging framework like Serilog , Microsoft.Extensions.Logging , NLog , or log4net .
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-azure-functions/index.html b/logging-to-elmah-io-from-azure-functions/index.html
new file mode 100644
index 0000000000..8e471f26be
--- /dev/null
+++ b/logging-to-elmah-io-from-azure-functions/index.html
@@ -0,0 +1,872 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Azure Functions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Azure Functions
+
+
Logging errors from Azure Functions requires only a few lines of code. We've created a client specifically for Azure Functions. If your are looking for logging from Isolated Azure Functions (out of process) check out Logging to elmah.io from Isolated Azure Functions .
+
Install the newest Elmah.Io.Functions
package in your Azure Functions project:
+
Install-Package Elmah.Io.Functions
+
dotnet add package Elmah.Io.Functions
+
<PackageReference Include="Elmah.Io.Functions" Version="5.*" />
+
paket add Elmah.Io.Functions
+
+
The elmah.io integration for Azure Functions uses function filters and dependency injection part of the Microsoft.Azure.Functions.Extensions
package. To configure elmah.io, open the Startup.cs
file or create a new one if not already there. In the Configure
-method, add the elmah.io options and exception filter:
+
using Elmah.Io.Functions;
+using Microsoft.Azure.Functions.Extensions.DependencyInjection;
+using Microsoft.Azure.WebJobs.Host;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+
+[assembly: FunctionsStartup(typeof(MyFunction.Startup))]
+
+namespace MyFunction
+{
+ public class Startup : FunctionsStartup
+ {
+ public override void Configure(IFunctionsHostBuilder builder)
+ {
+ var config = new ConfigurationBuilder()
+ .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
+ .AddEnvironmentVariables()
+ .Build();
+
+ builder.Services.Configure<ElmahIoFunctionOptions>(o =>
+ {
+ o.ApiKey = config["apiKey"];
+ o.LogId = new Guid(config["logId"]);
+ });
+
+ builder.Services.AddSingleton<IFunctionFilter, ElmahIoExceptionFilter>();
+ }
+ }
+}
+
+
Notice how API key and log ID are configured through the ElmahIoFunctionOptions
object. In the last line of the Configure
-method, the ElmahIoExceptionFilter
-filter is configured. This filter will automatically catch any exception caused by your filter and log it to elmah.io.
+
A quick comment about the obsolete warning showed when using the package. Microsoft marked IFunctionFilter
as obsolete. Not because it will be removed, but because they may change the way attributes work in functions in the future. For now, you can suppress this warning with the following code:
+
#pragma warning disable CS0618 // Type or member is obsolete
+builder.Services.AddSingleton<IFunctionFilter, ElmahIoExceptionFilter>();
+#pragma warning restore CS0618 // Type or member is obsolete
+
+
In your settings, add the apiKey
and logId
variables:
+
{
+ // ...
+ "Values": {
+ // ...
+ "apiKey": "API_KEY",
+ "logId": "LOG_ID"
+ }
+}
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
(Where is my log ID? ) with your log ID. When running on Azure or similar, you can overwrite apiKey
and logId
with application settings or environment variables as already thoroughly documented on Microsoft's documentation.
+
Application name
+
To set the application name on all errors, set the Application
property during initialization:
+
builder.Services.Configure<ElmahIoFunctionOptions>(o =>
+{
+ o.ApiKey = config["apiKey"];
+ o.LogId = new Guid(config["logId"]);
+ o.Application = "MyFunction";
+});
+
+
Message hooks
+
Elmah.Io.Functions
provide message hooks similar to the integrations with ASP.NET and ASP.NET Core.
+
Decorating log messages
+
To include additional information on log messages, you can use the OnMessage
event when initializing ElmahIoFunctionOptions
:
+
builder.Services.Configure<ElmahIoFunctionOptions>(o =>
+{
+ o.ApiKey = config["apiKey"];
+ o.LogId = new Guid(config["logId"]);
+ o.OnMessage = msg =>
+ {
+ msg.Version = "1.0.0";
+ };
+});
+
+
The example above includes a version number on all errors.
+
Include source code
+
You can use the OnMessage
action to include source code to log messages. This will require a stack trace in the Detail
property with filenames and line numbers in it.
+
There are multiple ways of including source code to log messages. In short, you will need to install the Elmah.Io.Client.Extensions.SourceCode
NuGet package and call the WithSourceCodeFromPdb
method in the OnMessage
action:
+
builder.Services.Configure<ElmahIoFunctionOptions>(o =>
+{
+ o.OnMessage = msg =>
+ {
+ msg.WithSourceCodeFromPdb();
+ };
+});
+
+
Check out How to include source code in log messages for additional requirements to make source code show up on elmah.io.
+
Handle errors
+
To handle any errors happening while processing a log message, you can use the OnError
event when initializing ElmahIoFunctionOptions
:
+
builder.Services.Configure<ElmahIoFunctionOptions>(o =>
+{
+ o.ApiKey = config["apiKey"];
+ o.LogId = new Guid(config["logId"]);
+ o.OnError = (msg, ex) =>
+ {
+ logger.LogError(ex, ex.Message);
+ };
+});
+
+
The example above logs any errors during communication with elmah.io to a local log.
+
Error filtering
+
To ignore specific errors based on their content, you can use the OnFilter
event when initializing ElmahIoFunctionOptions
:
+
builder.Services.Configure<ElmahIoFunctionOptions>(o =>
+{
+ o.ApiKey = config["apiKey"];
+ o.LogId = new Guid(config["logId"]);
+ o.OnFilter = msg =>
+ {
+ return msg.Method == "GET";
+ };
+});
+
+
The example above ignores any errors generated during an HTTP GET
request.
+
Logging through ILogger
+
Azure Functions can log through Microsoft.Extensions.Logging (MEL) too. By adding the filter, as shown above, all uncaught exceptions are automatically logged. But when configuring your Function app to log through MEL, custom messages can be logged through the ILogger
interface. Furthermore, you will get detailed log messages from within the Function host. To set this up, install the Elmah.Io.Extensions.Logging
NuGet package:
+
Install-Package Elmah.Io.Extensions.Logging
+
dotnet add package Elmah.Io.Extensions.Logging
+
<PackageReference Include="Elmah.Io.Extensions.Logging" Version="5.*" />
+
paket add Elmah.Io.Extensions.Logging
+
+
Then extend your Startup.cs
file like this:
+
builder.Services.AddLogging(logging =>
+{
+ logging.AddElmahIo(o =>
+ {
+ o.ApiKey = config["apiKey"];
+ o.LogId = new Guid(config["logId"]);
+ });
+ logging.AddFilter<ElmahIoLoggerProvider>(null, LogLevel.Warning);
+});
+
+
In the example, only warning messages and above are logged to elmah.io. You can remove the filter or set another log level if you want to log more. Jump to Log filtering to learn how to configure filters from config.
+
Either pass an ILogger
to your function method:
+
public class MyFunction
+{
+ public static void Run([TimerTrigger("...")]TimerInfo myTimer, ILogger log)
+ {
+ log.LogWarning("This is a warning");
+ }
+}
+
+
Or inject an ILoggerFactory
and create a logger as part of the constructor:
+
public class MyFunction
+{
+ private readonly ILogger log;
+
+ public Function1(ILoggerFactory loggerFactory)
+ {
+ this.log = loggerFactory.CreateLogger("MyFunction");
+ }
+
+ public void Run([TimerTrigger("...")]TimerInfo myTimer)
+ {
+ log.LogWarning("This is a warning");
+ }
+}
+
+
Log filtering
+
The code above filters out all log messages with a severity lower than Warning
. You can use all of the log filtering capabilities of Microsoft.Extensions.Logging to enable and disable various log levels from multiple categories. A common requirement is to only log Warning
and more severe originating from the Azure Functions runtime, but log Information
messages from your function code. This can be enabled through a custom category:
+
public class MyFunction
+{
+ private readonly ILogger log;
+
+ public Function1(ILoggerFactory loggerFactory)
+ {
+ this.log = loggerFactory.CreateLogger("MyFunction");
+ }
+
+ public void Run([TimerTrigger("...")]TimerInfo myTimer)
+ {
+ log.LogInformation("This is an information message");
+ }
+}
+
+
The MyFunction
category will need configuration in either C# or in the host.json
file:
+
{
+ // ...
+ "logging": {
+ "logLevel": {
+ "default": "Warning",
+ "MyFunction": "Information"
+ }
+ }
+}
+
+
Azure Functions v1
+
The recent Elmah.Io.Functions
package no longer supports Azure Functions v1. You can still log from Functions v1 using an older version of the package. Check out Logging to elmah.io from Azure WebJobs for details. The guide is for Azure WebJobs but installation for Functions v1 is identical.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-azure-webjobs/index.html b/logging-to-elmah-io-from-azure-webjobs/index.html
new file mode 100644
index 0000000000..d852f979a5
--- /dev/null
+++ b/logging-to-elmah-io-from-azure-webjobs/index.html
@@ -0,0 +1,693 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Azure WebJobs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Azure WebJobs
+
Logging errors from Azure WebJobs requires only a few lines of code. We've created a client specifically for Azure WebJobs.
+
+Support for Azure WebJobs has been stopped on version 3.1.23
of the Elmah.Io.Functions
package. The newer versions only work with Azure Functions.
+
+
Install the Elmah.Io.Functions package:
+
Install-Package Elmah.Io.Functions -Version 3.1.23
+
dotnet add package Elmah.Io.Functions --version 3.1.23
+
<PackageReference Include="Elmah.Io.Functions" Version="3.1.23" />
+
paket add Elmah.Io.Functions --version 3.1.23
+
+
Log all uncaught exceptions using the ElmahIoExceptionFilter
attribute:
+
[ElmahIoExceptionFilter("API_KEY", "LOG_ID")]
+public class Functions
+{
+ public static void ProcessQueueMessage([QueueTrigger("queue")] string msg, TextWriter log)
+ {
+ throw new Exception("Some exception");
+ }
+}
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
(Where is my log ID? ) with your log ID.
+
+If your WebJob method is declared as async, remember to change the return type to Task
. Without it, ElmahIoExceptionFilter
is never invoked.
+
+
The filter also supports config variables:
+
[ElmahIoExceptionFilter("%apiKey%", "%logId%")]
+
+
The variables above, would require you to add your API key and log ID to your App.config
:
+
<configuration>
+ <appSettings>
+ <add key="apiKey" value="API_KEY"/>
+ <add key="logId" value="LOG_ID"/>
+ </appSettings>
+</configuration>
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-blazor/index.html b/logging-to-elmah-io-from-blazor/index.html
new file mode 100644
index 0000000000..df7c3c28eb
--- /dev/null
+++ b/logging-to-elmah-io-from-blazor/index.html
@@ -0,0 +1,794 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Blazor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Blazor
+
+
Blazor Server App
+
To start logging to elmah.io from a Blazor Server App, install the Elmah.Io.Extensions.Logging
NuGet package:
+
Install-Package Elmah.Io.Extensions.Logging
+
dotnet add package Elmah.Io.Extensions.Logging
+
<PackageReference Include="Elmah.Io.Extensions.Logging" Version="5.*" />
+
paket add Elmah.Io.Extensions.Logging
+
+
In the Program.cs
file, add elmah.io logging configuration:
+
builder.Logging.AddElmahIo(options =>
+{
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+});
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the ID of the log you want messages sent to (Where is my log ID? ).
+
All uncaught exceptions are automatically logged to elmah.io. Exceptions can be logged manually, by injecting an ILogger
into your view and adding try/catch
:
+
@using Microsoft.Extensions.Logging
+@inject ILogger<FetchData> logger
+
+<!-- ... -->
+
+@functions {
+ WeatherForecast[] forecasts;
+
+ protected override async Task OnInitAsync()
+ {
+ try
+ {
+ forecasts = await Http
+ .GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts-nonexisting");
+ }
+ catch (Exception e)
+ {
+ logger.LogError(e, e.Message);
+ }
+ }
+}
+
+
Information
and other severities can be logged as well:
+
@using Microsoft.Extensions.Logging
+@inject ILogger<Counter> logger
+
+<!-- ... -->
+
+@functions {
+ int currentCount = 0;
+
+ void IncrementCount()
+ {
+ currentCount++;
+ logger.LogInformation("Incremented count to {currentCount}", currentCount);
+ }
+}
+
+
Include details from the HTTP context
+
Microsoft.Extensions.Logging
doesn't know that it is running inside a web server. That is why Elmah.Io.Extensions.Logging
doesn't include HTTP contextual information like URL and status code as default. To do so, install the Elmah.Io.AspNetCore.ExtensionsLogging
NuGet package:
+
Install-Package Elmah.Io.AspNetCore.ExtensionsLogging
+
dotnet add package Elmah.Io.AspNetCore.ExtensionsLogging
+
<PackageReference Include="Elmah.Io.AspNetCore.ExtensionsLogging" Version="5.*" />
+
paket add Elmah.Io.AspNetCore.ExtensionsLogging
+
+
And add the following code to the Program.cs
file:
+
app.UseElmahIoExtensionsLogging();
+
+
Make sure to call this method just before the call to UseRouting
and UseEndpoints
. This will include some of the information you are looking for.
+
There's a problem when running Blazor Server where you will see some of the URLs logged as part of errors on elmah.io having the value /_blazor
. This is because Blazor doesn't work like traditional websites where the client requests the server and returns an HTML or JSON response. When navigating the UI, parts of the UI are loaded through SignalR, which causes the URL to be /_blazor
. Unfortunately, we haven't found a good way to fix this globally. You can include the current URL on manual log statements by injecting a NavigationManager
in the top of your .razor
file:
+
@inject NavigationManager navigationManager
+
+
Then wrap your logging code in a new scope:
+
Uri.TryCreate(navigationManager.Uri, UriKind.Absolute, out Uri url);
+using (Logger.BeginScope(new Dictionary<string, object>
+{
+ { "url", url.AbsolutePath }
+}))
+{
+ logger.LogError(exception, "An error happened");
+}
+
+
The code uses the current URL from the injected NavigationManager
object.
+
Blazor WebAssembly App (wasm)
+
To start logging to elmah.io from a Blazor Wasm App, install the Elmah.Io.Blazor.Wasm
NuGet package:
+
Install-Package Elmah.Io.Blazor.Wasm
+
dotnet add package Elmah.Io.Blazor.Wasm
+
<PackageReference Include="Elmah.Io.Blazor.Wasm" Version="4.*" />
+
paket add Elmah.Io.Blazor.Wasm
+
+
In the Program.cs
file, add elmah.io logging configuration:
+
builder.Logging.AddElmahIo(options =>
+{
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+});
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the ID of the log you want messages sent to (Where is my log ID? ).
+
All uncaught exceptions are automatically logged to elmah.io after calling AddElmahIo
. Errors and other severities can be logged manually, by injecting an ILogger
into your view and adding try/catch
or by implementing error boundaries:
+
@page "/"
+@inject ILogger<Index> logger
+
+@code {
+ protected override void OnInitialized()
+ {
+ logger.LogInformation("Initializing index view");
+
+ try
+ {
+ object text = "Text";
+ var cast = (int)text;
+ }
+ catch (InvalidCastException e)
+ {
+ logger.LogError(e, "An error happened");
+ }
+ }
+}
+
+
The following may be implemented by the package later:
+
+Additional information about the HTTP context (like cookies, URL, and user).
+Internal message queue and/or batch processing like Microsoft.Extensions.Logging
.
+Support for logging scopes.
+
+
Blazor (United) App
+
.NET 8 introduces a new approach to developing Blazor applications, formerly known as Blazor United. We have started experimenting a bit with Blazor Apps which have the option of rendering both server-side and client-side from within the same Blazor application. As shown in the sections above, using server-side rendering needs Elmah.Io.Extensions.Logging
while client-side rendering needs Elmah.Io.Blazor.Wasm
. You cannot have both packages installed and configured in the same project so you need to stick to one of them for Blazor (United) Apps. Since the Elmah.Io.Extensions.Logging
package doesn't work with Blazor WebAssembly, we recommend installing the Elmah.Io.Blazor.Wasm
package if you want to log from both server-side and client-side. Once the new Blazor App framework matures, we will probably consolidate features from both packages into an Elmah.Io.Blazor
package or similar.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-blogengine-net/index.html b/logging-to-elmah-io-from-blogengine-net/index.html
new file mode 100644
index 0000000000..fe42c23500
--- /dev/null
+++ b/logging-to-elmah-io-from-blogengine-net/index.html
@@ -0,0 +1,675 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from BlogEngine.NET
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from BlogEngine.NET
+
Because BlogEngine.NET is written in ASP.NET, it doesn't need any custom code to use ELMAH and elmah.io. ELMAH works out of the box for most web frameworks by Microsoft. If you are building and deploying the code yourself, installing elmah.io is achieved using our NuGet package:
+
Install-Package Elmah.Io
+
dotnet add package Elmah.Io
+
<PackageReference Include="Elmah.Io" Version="5.*" />
+
+
During the installation, you will be asked for your API key (Where is my API key? ) and log ID (Where is my log ID? ).
+
When installed, BlogEngine.NET starts reporting errors to elmah.io. To check it out, force an internal server error or similar, and visit /elmah.axd or the search area of your log at elmah.io.
+
Some of you may use the BlogEngine.NET binaries or even installed it using a one-click installer. In this case you will need to add elmah.io manually. To do that, use a tool like NuGet Package Explorer to download the most recent versions of ELMAH and elmah.io. Copy Elmah.dll and Elmah.Io.dll to the bin directory of your BlogEngine.NET installation. Also modify your web.config to include the ELMAH config as shown in the config example. Last but not least, remember to add the elmah.io error logger configuration as a child node to the <elmah>
element:
+
<errorLog type="Elmah.Io.ErrorLog, Elmah.Io" apiKey="API_KEY" logId="LOG_ID" />
+
+
Where API_KEY
is your API key and LOG_ID
is your log ID.
+
To wrap this up, you may have noticed that there's a NuGet package to bring ELMAH support into BlogEngine.NET. This package adds the ELMAH assembly and config as well as adds a nice BlogEngine.NET compliant URL for browsing errors. Feel free to use this package, but remember to add it after the elmah.io package. Also, make sure to clean up the dual error log configuration:
+
<elmah>
+ <security allowRemoteAccess="false" />
+ <errorLog type="Elmah.Io.ErrorLog, Elmah.Io" apiKey="APIKEY" logId="LOGID" />
+ <security allowRemoteAccess="true" />
+ <errorLog type="Elmah.SqlServerCompactErrorLog, Elmah" connectionStringName="elmah-sqlservercompact" />
+</elmah>
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-console-application/index.html b/logging-to-elmah-io-from-console-application/index.html
new file mode 100644
index 0000000000..a5facbe261
--- /dev/null
+++ b/logging-to-elmah-io-from-console-application/index.html
@@ -0,0 +1,861 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from C# and console applications
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from C# and console applications
+
+
If you need to log to elmah.io and you cannot use one of the integrations we provide, logging through the Elmah.Io.Client NuGet package is dead simple.
+
To start logging, install the Elmah.Io.Client
package:
+
Install-Package Elmah.Io.Client
+
dotnet add package Elmah.Io.Client
+
<PackageReference Include="Elmah.Io.Client" Version="5.*" />
+
paket add Elmah.Io.Client
+
+
Create a new ElmahioAPI
:
+
var logger = ElmahioAPI.Create("API_KEY");
+
+
Replace API_KEY
with your API key (Where is my API key? ).
+
The elmah.io client supports logging in different log levels much like other logging frameworks for .NET:
+
var logId = new Guid("LOG_ID");
+logger.Messages.Fatal(logId, new ApplicationException("A fatal exception"), "Fatal message");
+logger.Messages.Error(logId, new ApplicationException("An exception"), "Error message");
+logger.Messages.Warning(logId, "A warning");
+logger.Messages.Information(logId, "An info message");
+logger.Messages.Debug(logId, "A debug message");
+logger.Messages.Verbose(logId, "A verbose message");
+
+
Replace LOG_ID
with your log ID from elmah.io (Where is my log ID? ).
+
To have 100% control of how the message is logged to elmah.io, you can use the CreateAndNotify
-method:
+
logger.Messages.CreateAndNotify(logId, new CreateMessage
+{
+ Title = "Hello World",
+ Application = "Elmah.Io.Client sample",
+ Detail = "This is a long description of the error. Maybe even a stack trace",
+ Severity = Severity.Error.ToString(),
+ Data = new List<Item>
+ {
+ new Item {Key = "Username", Value = "Man in black"}
+ },
+ Form = new List<Item>
+ {
+ new Item {Key = "Password", Value = "SecretPassword"},
+ new Item {Key = "pwd", Value = "Other secret value"},
+ new Item {Key = "visible form item", Value = "With a value"}
+ }
+});
+
+
Structured logging
+
Like the integrations for Serilog, NLog and, Microsoft.Extensions.Logging, the elmah.io client supports structured logging:
+
logger.Messages.CreateAndNotify(logId, new CreateMessage
+{
+ Title = "Thomas says Hello",
+ TitleTemplate = "{User} says Hello",
+});
+
+
Breadcrumbs
+
You can log one or more breadcrumbs as part of a log message. Breadcrumbs indicate steps happening just before a log message (typically an error). Breadcrumbs are supported through the Breadcrumbs
property on the CreateMessage
class:
+
logger.Messages.CreateAndNotify(logId, new CreateMessage
+{
+ Title = "Oh no, an error happened",
+ Severity = "Error",
+ Breadcrumbs = new List<Breadcrumb>
+ {
+ new Breadcrumb
+ {
+ DateTime = DateTime.UtcNow.AddSeconds(-10),
+ Action = "Navigation",
+ Message = "Navigate from / to /signin",
+ Severity = "Information"
+ },
+ new Breadcrumb
+ {
+ DateTime = DateTime.UtcNow.AddSeconds(-3),
+ Action = "Click",
+ Message = "#submit",
+ Severity = "Information"
+ },
+ new Breadcrumb
+ {
+ DateTime = DateTime.UtcNow.AddSeconds(-2),
+ Action = "Submit",
+ Message = "#loginform",
+ Severity = "Information"
+ },
+ new Breadcrumb
+ {
+ DateTime = DateTime.UtcNow.AddSeconds(-1),
+ Action = "Request",
+ Message = "/save",
+ Severity = "Information"
+ }
+ }
+});
+
+
Breadcrumbs will be ordered by the DateTime
field on the elmah.io API why the order you add them to the Breadcrumbs
property isn't that important. Be aware that only the 10 most recent breadcrumbs and breadcrumbs with a date less than or equal to the logged message are stored.
+
In the example above, only Information
breadcrumbs are added. The Severity
property accepts the same severities as on the log message itself.
+
Events
+
The elmah.io client supports two different events: OnMessage
and OnMessageFail
.
+
OnMessage
+
To get a callback every time a new message is being logged to elmah.io, you can implement the OnMessage
event. This is a great chance to decorate all log messages with a specific property or similar.
+
logger.Messages.OnMessage += (sender, eventArgs) =>
+{
+ eventArgs.Message.Version = "1.0.0";
+};
+
+
Include source code
+
You can use the OnMessage
event to include source code to log messages. This will require a stack trace in the Detail
property with filenames and line numbers in it.
+
There are multiple ways of including source code to log messages. In short, you will need to install the Elmah.Io.Client.Extensions.SourceCode
NuGet package and call the WithSourceCodeFromPdb
method in the OnMessage
event handler:
+
logger.Messages.OnMessage += (sender, eventArgs) =>
+{
+ eventArgs.Message.WithSourceCodeFromPdb();
+};
+
+
Check out How to include source code in log messages for additional requirements to make source code show up on elmah.io.
+
+Including source code on log messages is available in the Elmah.Io.Client
v4 package and forward.
+
+
OnMessageFail
+
Logging to elmah.io can fail if the network connection is down, if elmah.io experiences downtime, or something third. To make sure you log an error elsewhere if this happens, you can implement the OnMessageFail
event:
+
logger.Messages.OnMessageFail += (sender, eventArgs) =>
+{
+ System.Console.Error.WriteLine("Error when logging to elmah.io");
+};
+
+
Bulk upload
+
If logging many messages to elmah.io, bulk upload can be a way to optimize performance. The elmah.io client supports bulk upload using the CreateBulkAndNotify
-method:
+
logger.Messages.CreateBulkAndNotify(logId, new[]
+{
+ new CreateMessage { Title = "This is a bulk message" },
+ new CreateMessage { Title = "This is another bulk message" },
+}.ToList());
+
+
Options
+
The elmah.io client contains a set of default options that you can override.
+
Proxy
+
To log through a HTTP proxy, set the WebProxy
property:
+
var logger = ElmahioAPI.Create("API_KEY", new ElmahIoOptions
+{
+ WebProxy = new WebProxy("localhost", 8888)
+});
+
+
A proxy needs to be specified as part of the options sent to the ElmahioAPI.Create
method to make sure that the underlying HttpClient
is properly initialized.
+
+
When logging POSTs with form values, you don't want users' passwords and similar logged to elmah.io. The elmah.io client automatically filters form keys named password
and pwd
. Using the FormKeysToObfuscate
you can tell the client to obfuscate additional form entries:
+
var logger = ElmahioAPI.Create("API_KEY");
+logger.Options.FormKeysToObfuscate.Add("secret_key");
+
+
Full example
+
Here's a full example of how to catch all exceptions in a console application and log as many information as possible to elmah.io:
+
class Program
+{
+ private static IElmahioAPI elmahIo;
+
+ static void Main(string[] args)
+ {
+ try
+ {
+ AppDomain.CurrentDomain.UnhandledException +=
+ (sender, e) => LogException(e.ExceptionObject as Exception);
+
+ // Run some code
+ }
+ catch (Exception e)
+ {
+ LogException(e);
+ }
+ }
+
+ private static void LogException(Exception e)
+ {
+ if (elmahIo == null) elmahIo = ElmahioAPI.Create("API_KEY");
+
+ var baseException = e?.GetBaseException();
+
+ elmahIo.Messages.CreateAndNotify(new Guid("LOG_ID"), new CreateMessage
+ {
+ Data = e?.ToDataList(),
+ Detail = e?.ToString(),
+ Hostname = Environment.MachineName,
+ Severity = Severity.Error.ToString(),
+ Source = baseException?.Source,
+ Title = baseException?.Message ?? "An error happened",
+ Type = baseException?.GetType().FullName,
+ User = Environment.UserName,
+ });
+ }
+}
+
+
The code will catch all exceptions both from the catch
block and exceptions reported through the UnhandledException
event.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-corewcf/index.html b/logging-to-elmah-io-from-corewcf/index.html
new file mode 100644
index 0000000000..ee0579091b
--- /dev/null
+++ b/logging-to-elmah-io-from-corewcf/index.html
@@ -0,0 +1,725 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from CoreWCF
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from CoreWCF
+
elmah.io supports CoreWCF using our integration with Microsoft.Extensions.Logging. Start by installing the Elmah.Io.Extensions.Logging
NuGet package:
+
Install-Package Elmah.Io.Extensions.Logging
+
dotnet add package Elmah.Io.Extensions.Logging
+
<PackageReference Include="Elmah.Io.Extensions.Logging" Version="5.*" />
+
paket add Elmah.Io.Extensions.Logging
+
+
Configure logging as part of the configuration (typically in the Program.cs
file):
+
builder.Logging.AddElmahIo(options =>
+{
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+});
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the id of the log (Where is my log ID? ) where you want messages logged.
+
CoreWCF will now send all messages logged from your application to elmah.io. CoreWCF doesn't log uncaught exceptions happening in WCF services to Microsoft.Extensions.Logging
as you'd expect if coming from ASP.NET Core. To do this, you will need to add a custom error logger by including the following class:
+
public class ElmahIoErrorHandler : IErrorHandler
+{
+ private readonly ILogger<ElmahIoErrorHandler> logger;
+
+ public ElmahIoErrorHandler(ILogger<ElmahIoErrorHandler> logger)
+ {
+ this.logger = logger;
+ }
+
+ public bool HandleError(Exception error) => false;
+
+ public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
+ {
+ if (error == null) return;
+
+ logger.LogError(error, error.Message);
+ }
+}
+
+
The ElmahIoErrorHandler
class will be called by CoreWCF when exceptions are thrown and log the to the configure ILogger
. To invoke the error handler, add the following service behavior:
+
public class ElmahIoErrorBehavior : IServiceBehavior
+{
+ private readonly ILogger<ElmahIoErrorHandler> logger;
+
+ public ElmahIoErrorBehavior(ILogger<ElmahIoErrorHandler> logger)
+ {
+ this.logger = logger;
+ }
+
+ public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
+ {
+ }
+
+ public void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
+ {
+ }
+
+ public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
+ {
+ var errorHandler = (IErrorHandler)Activator.CreateInstance(typeof(ElmahIoErrorHandler), logger);
+
+ foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
+ {
+ ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
+ channelDispatcher.ErrorHandlers.Add(errorHandler);
+ }
+ }
+}
+
+
The service behavior will look up the ElmahIoErrorHandler
and register it with CoreWCF. The code above is hardcoded to work with the elmah.io error handler only. If you have multiple error handlers, you will need to register all of them.
+
Finally, register the service behavior in the Program.cs
file:
+
builder.Services.AddSingleton<IServiceBehavior, ElmahIoErrorBehavior>();
+
+
Uncaught errors will now be logged to elmah.io.
+
All of the settings from Elmah.Io.Extensions.Logging
not mentioned on this page work with CoreWCF. Check out Logging to elmah.io from Microsoft.Extensions.Logging for details.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-devexpress/index.html b/logging-to-elmah-io-from-devexpress/index.html
new file mode 100644
index 0000000000..1f1c6560f5
--- /dev/null
+++ b/logging-to-elmah-io-from-devexpress/index.html
@@ -0,0 +1,671 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from DevExpress (eXpressApp Framework)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from DevExpress (eXpressApp Framework)
+
eXpressApp Framework (XAF) is built on top of ASP.NET. Installing elmah.io corresponds to any other ASP.NET site:
+
Install-Package Elmah.Io
+
dotnet add package Elmah.Io
+
<PackageReference Include="Elmah.Io" Version="5.*" />
+
+
During the installation, you will be asked for your API key (Where is my API key? ) and log ID (Where is my log ID? ).
+
To verify the integration, throw a new exception in Default.aspx
or similar:
+
<body class="VerticalTemplate">
+ <% throw new Exception("Test exception"); %>
+ <form id="form2" runat="server">
+ <!-- ... -->
+ </form>
+</body>
+
+
Launch the project and see the test exception flow into elmah.io.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-elmah/index.html b/logging-to-elmah-io-from-elmah/index.html
new file mode 100644
index 0000000000..ba5420ed8d
--- /dev/null
+++ b/logging-to-elmah-io-from-elmah/index.html
@@ -0,0 +1,680 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from ASP.NET / WebForms
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
This article will explain the steps necessary to log errors from your web application into elmah.io. We also offer more specific guides on ASP.NET MVC , Web API , and a lot of other web- and logging-frameworks. Read through this tutorial and head over to a tutorial specific for your choice of framework afterwards.
+
This guide is also available as a short video tutorial here:
+
+
+
+
+
Create a new ASP.NET Web Application in Visual Studio :
+
+
Select a project template of your choice:
+
+
Navigate to elmah.io and login using username/password or your favorite social provider. When logged in, elmah.io redirects you to the dashboard. If you just signed up, you will be guided through the process of creating an organization and a log.
+
When the log has been created, elmah.io shows you the install instructions. If you are currently on the dashboard, click the gears icon on the lower right corner of the log box. Don't pay too much attention to the install steps, because the rest of this tutorial will guide you through the installation. Keep the page open in order to copy your API key and log ID at a later step:
+
+
Navigate back to your web project, right click References and select Manage NuGet Packages :
+
+
In the NuGet dialog, search for elmah.io:
+
+
Select the elmah.io
package and click Install . Input your API key and log ID in the dialog appearing during installation of the NuGet package:
+
+
You're ready to rock and roll. Either add throw new Exception("Test");
somewhere or hit F5 and input a URL you know doesn't exist (like http://localhost:64987/notfound). To verify that the installation of elmah.io is successful, navigate back to the elmah.io dashboard and select the Search tab of your newly created log:
+
+
Congrats! Every error on your application is now logged to elmah.io.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-entity-framework-core/index.html b/logging-to-elmah-io-from-entity-framework-core/index.html
new file mode 100644
index 0000000000..7ee6d6a2ab
--- /dev/null
+++ b/logging-to-elmah-io-from-entity-framework-core/index.html
@@ -0,0 +1,679 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Entity Framework Core
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Entity Framework Core
+
Both elmah.io and Entity Framework Core supports logging through Microsoft.Extensions.Logging. To log all errors happening inside Entity Framework Core, install the Elmah.Io.Extensions.Logging NuGet package:
+
Install-Package Elmah.Io.Extensions.Logging
+
dotnet add package Elmah.Io.Extensions.Logging
+
<PackageReference Include="Elmah.Io.Extensions.Logging" Version="5.*" />
+
paket add Elmah.Io.Extensions.Logging
+
+
Then add elmah.io to a new or existing LoggerFactory
:
+
var loggerFactory = new LoggerFactory()
+ .AddElmahIo("API_KEY", new Guid("LOG_ID"));
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the log ID (Where is my log ID? ) that should receive errors from Entity Framework.
+
+When using Entity Framework Core from ASP.NET Core, you never create a LoggerFactory
. Factories are provided through DI by ASP.NET Core. Check out this sample for details.
+
+
Finally, enable logging in Entity Framework Core:
+
optionsBuilder
+ .UseLoggerFactory(loggerFactory)
+ .UseSqlServer(/*...*/);
+
+
(UseSqlServer
included for illustration purposes only - elmah.io works with any provider)
+
That's it! All errors happening in Entity Framework Core, are now logged in elmah.io.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-express/index.html b/logging-to-elmah-io-from-express/index.html
new file mode 100644
index 0000000000..eef9278fd3
--- /dev/null
+++ b/logging-to-elmah-io-from-express/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Redirecting...
+
+
+
+
+
+
+Redirecting...
+
+
diff --git a/logging-to-elmah-io-from-google-cloud-functions/index.html b/logging-to-elmah-io-from-google-cloud-functions/index.html
new file mode 100644
index 0000000000..397f5d3eb0
--- /dev/null
+++ b/logging-to-elmah-io-from-google-cloud-functions/index.html
@@ -0,0 +1,704 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Google Cloud Functions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Google Cloud Functions
+
Logging to elmah.io from Google Cloud Functions uses our integration with Microsoft.Extensions.Logging . To start logging, install the Elmah.Io.Extensions.Logging
NuGet package through the Cloud Shell or locally:
+
Install-Package Elmah.Io.Extensions.Logging
+
dotnet add package Elmah.Io.Extensions.Logging
+
<PackageReference Include="Elmah.Io.Extensions.Logging" Version="5.*" />
+
paket add Elmah.Io.Extensions.Logging
+
+
In the root of your Function app create a new file named Startup.cs
:
+
public class Startup : FunctionsStartup
+{
+ public override void ConfigureServices(WebHostBuilderContext ctx, IServiceCollection services)
+ {
+ services.AddLogging(logging =>
+ {
+ logging.AddElmahIo(o =>
+ {
+ o.ApiKey = "API_KEY";
+ o.LogId = new Guid("LOG_ID");
+ });
+ logging.AddFilter<ElmahIoLoggerProvider>(null, LogLevel.Warning);
+ });
+ }
+}
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the ID of the log to store messages in (Where is my log ID? ).
+
The filter tells Google Cloud Functions to log warnings and above to elmah.io only. If you want to log detailed information about what goes on inside Google Cloud Functions, you can lower the log level.
+
Decorate your function with a FunctionsStartup
attribute:
+
[FunctionsStartup(typeof(Startup))]
+public class Function : IHttpFunction
+{
+ // ...
+}
+
+
All uncaught exceptions happening in your function as well as log messages sent from Google Cloud Functions are now stored in elmah.io.
+
To log messages manually, you can inject an ILogger
in your function:
+
public class Function : IHttpFunction
+{
+ private ILogger<Function> _logger;
+
+ public Function(ILogger<Function> logger)
+ {
+ _logger = logger;
+ }
+
+ // ...
+}
+
+
Then log messages using the injected logger:
+
_logger.LogWarning("Your log message goes here");
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-isolated-azure-functions/index.html b/logging-to-elmah-io-from-isolated-azure-functions/index.html
new file mode 100644
index 0000000000..2865282ca8
--- /dev/null
+++ b/logging-to-elmah-io-from-isolated-azure-functions/index.html
@@ -0,0 +1,825 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Isolated Azure Functions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Isolated Azure Functions
+
+
Logging errors from Isolated Azure Functions requires only a few lines of code. We've created clients specifically for Isolated Azure Functions. If your are looking for logging from Azure Functions (in process) check out Logging to elmah.io from Azure Functions .
+
Install the Elmah.Io.Functions.Isolated
package in your project to get started:
+
Install-Package Elmah.Io.Functions.Isolated
+
dotnet add package Elmah.Io.Functions.Isolated
+
<PackageReference Include="Elmah.Io.Functions.Isolated" Version="5.*" />
+
paket add Elmah.Io.Functions.Isolated
+
+
Next, call the AddElmahIo
method inside ConfigureFunctionsWorkerDefaults
:
+
.ConfigureFunctionsWorkerDefaults((context, app) =>
+{
+ app.AddElmahIo(options =>
+ {
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+ });
+})
+
+
Also, include a using
of the Elmah.Io.Functions.Isolated
namespace. elmah.io now automatically identifies any uncaught exceptions and logs them to the specified log. Check out the samples for more ways to configure elmah.io.
+
Application name
+
To set the application name on all errors, set the Application
property:
+
app.AddElmahIo(options =>
+{
+ // ...
+ options.Application = "MyFunction";
+});
+
+
Message hooks
+
Elmah.Io.Functions.Isolated
provide message hooks similar to the integrations with ASP.NET and ASP.NET Core.
+
Decorating log messages
+
To include additional information on log messages, you can use the OnMessage
action:
+
app.AddElmahIo(options =>
+{
+ // ...
+ options.OnMessage = msg =>
+ {
+ msg.Version = "1.0.0";
+ };
+});
+
+
The example above includes a version number on all errors.
+
Include source code
+
You can use the OnMessage
action to include source code to log messages. This will require a stack trace in the Detail
property with filenames and line numbers in it.
+
There are multiple ways of including source code to log messages. In short, you will need to install the Elmah.Io.Client.Extensions.SourceCode
NuGet package and call the WithSourceCodeFromPdb
method in the OnMessage
action:
+
app.AddElmahIo(options =>
+{
+ // ...
+ options.OnMessage = msg =>
+ {
+ msg.WithSourceCodeFromPdb();
+ };
+});
+
+
Check out How to include source code in log messages for additional requirements to make source code show up on elmah.io.
+
Handle errors
+
To handle any errors happening while processing a log message, you can use the OnError
action:
+
app.AddElmahIo(options =>
+{
+ // ...
+ options.OnError = (msg, ex) =>
+ {
+ logger.LogError(ex, ex.Message);
+ };
+});
+
+
The example above logs any errors during communication with elmah.io to a local log.
+
Error filtering
+
To ignore specific errors based on their content, you can use the OnFilter
action:
+
app.AddElmahIo(options =>
+{
+ // ...
+ options.OnFilter = msg =>
+ {
+ return msg.Method == "GET";
+ };
+});
+
+
The example above ignores any errors generated during an HTTP GET
request.
+
Logging through ILogger
+
Isolated Azure Functions can log through Microsoft.Extensions.Logging (MEL) too. When configuring your Function app to log through MEL, custom messages can be logged through the ILogger
interface. Furthermore, you will get detailed log messages from within the Function host. To set this up, install the Elmah.Io.Extensions.Logging
NuGet package:
+
Install-Package Elmah.Io.Extensions.Logging
+
dotnet add package Elmah.Io.Extensions.Logging
+
<PackageReference Include="Elmah.Io.Extensions.Logging" Version="5.*" />
+
paket add Elmah.Io.Extensions.Logging
+
+
Then extend your Program.cs
file like this:
+
var host = new HostBuilder()
+ // ...
+ .ConfigureLogging(logging =>
+ {
+ logging.AddElmahIo(options =>
+ {
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+ });
+ logging.AddFilter<ElmahIoLoggerProvider>(null, LogLevel.Warning);
+ })
+ // ...
+ .Build();
+
+
In the example, only warning messages and above are logged to elmah.io. You can remove the filter or set another log level if you want to log more. Jump to Log filtering to learn how to configure filters from config.
+
Either pass an ILogger
to your function method:
+
public class MyFunction
+{
+ public static void Run([TimerTrigger("...")]TimerInfo myTimer, ILogger<MyFunction> logger)
+ {
+ logger.LogWarning("This is a warning");
+ }
+}
+
+
Or inject an ILoggerFactory
and create a logger as part of the constructor:
+
public class MyFunction
+{
+ private readonly ILogger<MyFunction> logger;
+
+ public Function1(ILoggerFactory loggerFactory)
+ {
+ this.logger = loggerFactory.CreateLogger<MyFunction>();
+ }
+
+ public void Run([TimerTrigger("...")]TimerInfo myTimer)
+ {
+ logger.LogWarning("This is a warning");
+ }
+}
+
+
Log filtering
+
The code above filters out all log messages with a severity lower than Warning
. You can use all of the log filtering capabilities of Microsoft.Extensions.Logging to enable and disable various log levels from multiple categories. A common requirement is to only log Warning
and more severe originating from the Azure Functions runtime, but log Information
messages from your function code. This can be enabled through a custom category:
+
public class MyFunction
+{
+ public void Run([TimerTrigger("...")]TimerInfo myTimer, ILogger<MyFunction> logger)
+ {
+ logger.LogInformation("This is an information message");
+ }
+}
+
+
The MyFunction
category will need configuration in either C# or in the host.json
file:
+
{
+ // ...
+ "logging": {
+ "logLevel": {
+ "default": "Warning",
+ "MyFunction": "Information"
+ }
+ }
+}
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-javascript/index.html b/logging-to-elmah-io-from-javascript/index.html
new file mode 100644
index 0000000000..54c5193f2a
--- /dev/null
+++ b/logging-to-elmah-io-from-javascript/index.html
@@ -0,0 +1,974 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from JavaScript
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from JavaScript
+
+
elmah.io doesn't only support server-side .NET logging. We also log JavaScript errors happening on your website. Logging client-side errors require nothing more than installing the elmahio.js
script on your website.
+
+Organizations created end 2023 and forward will have an API key named JavaScript automatically generated. Remember to either use this or generate a new API key with messages_write
permission only. This makes it easy to revoke the API key if someone starts sending messages to your log with your key.
+
+
elmahio.js
supports all modern browsers like Chrome, Edge, Firefox, and Safari. Internet Explorer 10 and 11 are supported too, but because of internal dependencies on the stacktrace-gps
library, nothing older than IE10 is supported.
+
If you want to see elmahio.js
in action before installing it on your site, feel free to play on the Playground .
+
Installation
+
Pick an installation method of your choice:
+
+
+
+
+
Download the latest release as a zip: https://github.com/elmahio/elmah.io.javascript/releases
+
Unpack and copy elmahio.min.js
to the Scripts
folder or whatever folder you use to store JavaScript files.
+
Reference elmahio.min.js
just before the </body>
tag (but before all other JavaScripts) in your shared _Layout.cshtml
or all HTML files, depending on how you've structured your site:
+
<script src="~/Scripts/elmahio.min.js?apiKey=API_KEY&logId=LOG_ID" type="text/javascript"></script>
+
+
+
+
Reference elmahio.min.js
just before the </body>
tag (but before all other JavaScripts) in your shared _Layout.cshtml
or all HTML files, depending on how you've structured your site:
+
<script src="https://cdn.jsdelivr.net/gh/elmahio/elmah.io.javascript@latest/dist/elmahio.min.js?apiKey=API_KEY&logId=LOG_ID" type="text/javascript"></script>
+
+
+
+
Install the elmah.io.javascript npm package:
+
npm install elmah.io.javascript
+
+
Reference elmahio.min.js
just before the </body>
tag (but before all other JavaScripts) in your shared _Layout.cshtml
or all HTML files, depending on how you've structured your site:
+
<script src="~/node_modules/elmah.io.javascript/dist/elmahio.min.js?apiKey=API_KEY&logId=LOG_ID" type="text/javascript"></script>
+
+
+
+
Since Bower is no longer maintained , installing elmah.io.javascript
through Bower, is supported using bower-npm-resolver
. Install the resolver:
+
npm install bower-npm-resolver --save
+
+
Add the resolver in your .bowerrc
file:
+
{
+ "resolvers": [
+ "bower-npm-resolver"
+ ]
+}
+
+
Install the elmah.io.javascript
npm package:
+
bower install npm:elmah.io.javascript --save
+
+
Reference elmahio.min.js
just before the </body>
tag (but before all other JavaScripts) in your shared _Layout.cshtml
or all HTML files, depending on how you've structured your site:
+
<script src="~/bower_components/elmah.io.javascript/dist/elmahio.min.js?apiKey=API_KEY&logId=LOG_ID" type="text/javascript"></script>
+
+
+
+
Add the elmah.io.javascript
library in your libman.json
file:
+
{
+ // ...
+ "libraries": [
+ // ...
+ {
+ "provider": "filesystem",
+ "library": "https://raw.githubusercontent.com/elmahio/elmah.io.javascript/3.7.1/dist/elmahio.min.js",
+ "destination": "wwwroot/lib/elmahio"
+ }
+ ]
+}
+
+
or using the LibMan CLI:
+
libman install https://raw.githubusercontent.com/elmahio/elmah.io.javascript/3.7.1/dist/elmahio.min.js --provider filesystem --destination wwwroot\lib\elmahio
+
+
Reference elmahio.min.js
just before the </body>
tag (but before all other JavaScripts) in your shared _Layout.cshtml
or all HTML files, depending on how you've structured your site:
+
<script src="~/lib/elmahio/dist/elmahio.min.js?apiKey=API_KEY&logId=LOG_ID" type="text/javascript"></script>
+
+
+
+
Install the elmah.io.javascript
NuGet package:
+
Install-Package elmah.io.javascript
+
dotnet add package elmah.io.javascript
+
<PackageReference Include="elmah.io.javascript" Version="4.*" />
+
paket add elmah.io.javascript
+
+
Reference elmahio.min.js
just before the </body>
tag (but before all other JavaScripts) in your shared _Layout.cshtml
or all HTML files, depending on how you've structured your site:
+
<script src="~/Scripts/elmahio.min.js?apiKey=API_KEY&logId=LOG_ID" type="text/javascript"></script>
+
+
+
+
If not already configured, follow the guide installing elmah.io in ASP.NET Core .
+
Install the Elmah.Io.AspNetCore.TagHelpers
NuGet package:
+
Install-Package Elmah.Io.AspNetCore.TagHelpers
+
dotnet add package Elmah.Io.AspNetCore.TagHelpers
+
<PackageReference Include="Elmah.Io.AspNetCore.TagHelpers" Version="5.*" />
+
paket add Elmah.Io.AspNetCore.TagHelpers
+
+
Copy and paste the following line to the top of the _Layout.cshtml
file:
+
@addTagHelper *, Elmah.Io.AspNetCore.TagHelpers
+
+
In the bottom of the file (but before referencing other JavaScript files), add the following tag helper:
+
<elmah-io/>
+
+
If you want to log JavaScript errors from production only, make sure to move the elmah-io
element inside the tag <environment exclude="Development">
.
+
elmah.io automatically pulls your API key and log ID from the options specified as part of the installation for logging serverside errors from ASP.NET Core.
+
+
+
That's it. All uncaught errors on your website, are now logged to elmah.io.
+
Options
+
If you prefer configuring in code (or need to access the options for something else), API key and log ID can be configured by referencing the elmahio.min.js
script without parameters:
+
<script src="~/scripts/elmahio.min.js" type="text/javascript"></script>
+
+
Then initialize the logger in JavaScript:
+
new Elmahio({
+ apiKey: 'API_KEY',
+ logId: 'LOG_ID'
+});
+
+
Application name
+
The application
property on elmah.io can be set on all log messages by setting the application
option:
+
new Elmahio({
+ apiKey: 'API_KEY',
+ logId: 'LOG_ID',
+ application: 'My application name'
+});
+
+
Debug output
+
For debug purposes, debug output from the logger to the console can be enabled using the debug
option:
+
new Elmahio({
+ apiKey: 'API_KEY',
+ logId: 'LOG_ID',
+ debug: true
+});
+
+
Message filtering
+
Log messages can be filtered, by adding a filter
handler in options:
+
new Elmahio({
+ // ...
+ filter: function(msg) {
+ return msg.severity === 'Verbose';
+ }
+});
+
+
In the example, all log messages with a severity of Verbose
, are not logged to elmah.io.
+
Events
+
Enriching log messages
+
Log messages can be enriched by subscribing to the message
event:
+
new Elmahio({
+ // ...
+}).on('message', function(msg) {
+ if (!msg.data) msg.data = [];
+ msg.data.push({key: 'MyCustomKey', value: 'MyCustomValue'});
+});
+
+
In the example, all log messages are enriched with a data variable with the key MyCustomKey
and value MyCustomValue
.
+
Handling errors
+
To react to errors happening in elmah.io.javascript, subscribe to the error
event:
+
new Elmahio({
+ // ...
+}).on('error', function(status, text) {
+ console.log('An error happened in elmah.io.javascript', status, text);
+});
+
+
In the example, all errors are written to the console.
+
Breadcrumbs
+
Breadcrumbs can be used to decorate errors with events or actions happening just before logging the error. Breadcrumbs can be added manually:
+
logger.addBreadcrumb('User clicked button x', 'Information', 'click');
+
+
You would want to enrich your code with a range of different breadcrumbs depending on important user actions in your application.
+
As default, a maximum of 10 breadcrumbs are stored in memory at all times. The list acts as first in first out, where adding a new breadcrumb to a full list will automatically remove the oldest breadcrumb in the list. The allowed number of breadcrumbs in the list can be changed using the breadcrumbsNumber
option:
+
var logger = new Elmahio({
+ // ...
+ breadcrumbsNumber: 15
+});
+
+
This will store a maximum of 15 breadcrumbs. Currently, we allow 25
as the highest possible value.
+
elmah.io.javascript
can also be configured to automatically generate breadcrumbs from important actions like click events and xhr:
+
var logger = new Elmahio({
+ // ...
+ breadcrumbs: true
+});
+
+
We are planning to enable automatic breadcrumbs in the future but for now, it's an opt-in feature. Automatic breadcrumbs will be included in the same list as manually added breadcrumbs why the breadcrumbsNumber
option is still valid.
+
Logging manually
+
You may want to log errors manually or even log information messages from JavaScript. To do so, Elmahio
is a logging framework too:
+
var logger = new Elmahio({
+ apiKey: 'API_KEY',
+ logId: 'LOG_ID'
+});
+
+logger.verbose('This is verbose');
+logger.verbose('This is verbose', new Error('A JavaScript error object'));
+
+logger.debug('This is debug');
+logger.debug('This is debug', new Error('A JavaScript error object'));
+
+logger.information('This is information');
+logger.information('This is information', new Error('A JavaScript error object'));
+
+logger.warning('This is warning');
+logger.warning('This is warning', new Error('A JavaScript error object'));
+
+logger.error('This is error');
+logger.error('This is error', new Error('A JavaScript error object'));
+
+logger.fatal('This is fatal');
+logger.fatal('This is fatal', new Error('A JavaScript error object'));
+
+logger.log({
+ title: 'This is a custom log message',
+ type: 'Of some type',
+ severity: 'Error'
+});
+
+var msg = logger.message(); // Get a prefilled message
+msg.title = "This is a custom log message";
+logger.log(msg);
+
+var msg = logger.message(new Error('A JavaScript error object')); // Get a prefilled message including error details
+// Set custom variables. If not needed, simply use logger.error(...) instead.
+logger.log(msg);
+
+
The Error
object used, should be a JavaScript Error object .
+
As for the log
-function, check out message reference .
+
+Manual logging only works when initializing the elmah.io logger from code.
+
+
Logging from console
+
If you don't like to share the Elmahio
logger or you want to hook elmah.io logging up to existing code, you can capture log messages from console
. To do so, set the captureConsoleMinimumLevel
option:
+
var log = new Elmahio({
+ apiKey: 'API_KEY',
+ logId: 'LOG_ID',
+ captureConsoleMinimumLevel: 'error'
+});
+
+console.error('This is an %s message.', 'error');
+
+
captureConsoleMinimumLevel
can be set to one of the following values:
+
+none
: will disable logging from console (default).
+debug
: will capture logging from console.debug
, console.info
, console.warn
, console.error
.
+info
: will capture logging from console.info
, console.warn
, console.error
.
+warn
: will capture logging from console.warn
, console.error
.
+error
: will capture logging from console.error
.
+
+
+Capturing the console only works when initializing the elmah.io logger from code. Also, console.log
is not captured.
+
+
IntelliSense
+
If installing through npm or similar, Visual Studio should pick up the TypeScript mappings from the elmah.io.javascript package. If not, add the following line at the top of the JavaScript file where you want elmah.io.javascript IntelliSense:
+
/// <reference path="/path/to/elmahio.d.ts" />
+
+
Message reference
+
This is an example of the elmah.io.javascript Message
object that is used in various callbacks, etc.:
+
{
+ title: 'this.remove is not a function',
+ detail: 'TypeError: this.remove is not a function\n at (undefined:12:14)',
+ source: 'index.js',
+ severity: 'Error',
+ type: 'TypeError',
+ url: '/current/page',
+ method: 'GET',
+ application: 'MyApp',
+ hostname: 'WOPR',
+ statusCode: 400,
+ user: 'DavidLightman',
+ version: '1.0.0',
+ cookies: [
+ { key: '_ga', value: 'GA1.3.1580453215.1783132008' }
+ ],
+ queryString: [
+ { key: 'id', value: '42' }
+ ],
+ data: [
+ { key: 'User-Language', value: 'en-US' },
+ { key: 'Color-Depth', value: '24' }
+ ],
+ serverVariables: [
+ { key: 'User-Agent', value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' }
+ ],
+ breadcrumbs: [
+ { severity: 'Information', event: 'click', message: 'A user clicked' }
+ ]
+}
+
+
For a complete definition, check out the Message
interface in the elmah.io.javascript TypeScript mappings .
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-jsnlog/index.html b/logging-to-elmah-io-from-jsnlog/index.html
new file mode 100644
index 0000000000..38d58e9afa
--- /dev/null
+++ b/logging-to-elmah-io-from-jsnlog/index.html
@@ -0,0 +1,695 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from JSNLog
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from JSNLog
+
+While logging through JSNLog still works, we recommend using our native integration with JavaScript: Logging to elmah.io from JavaScript
+
+
Using JSNLog you will be able to log JavaScript errors to elmah.io. In this sample, we will focus on logging JavaScript errors from an ASP.NET MVC web application, but you can use JSNLog to log anything to elmah.io, so please check out their documentation.
+
Start by installing the JSNLog.Elmah
package:
+
Install-Package JSNLog.Elmah
+
dotnet add package JSNLog.Elmah
+
<PackageReference Include="JSNLog.Elmah" Version="2.*" />
+
+
This installs and setup JSNLog into your project, using ELMAH as an appender. Then, install Elmah.Io
:
+
Install-Package Elmah.Io
+
dotnet add package Elmah.Io
+
<PackageReference Include="Elmah.Io" Version="5.*" />
+
+
During the installation, you will be asked for your API key (Where is my API key? ) and log ID (Where is my log ID? ).
+
Add the JSNLog code before any script imports in your _Layout.cshtml file:
+
@Html.Raw(JSNLog.JavascriptLogging.Configure())
+
+
You are ready to log errors from JavaScript to elmah.io. To test that everything is installed correctly, launch your web application and execute the following JavaScript using Chrome Developer Tools or similar:
+
JL().fatal("log message");
+
+
Navigate to your log at elmah.io and observe the new error. As you can see, logging JavaScript errors is now extremely simple and can be built into any try-catch, jQuery fail handlers, and pretty much anywhere else. To log every JavaScript error, add the following to the bottom of the _Layout.cshtml file:
+
<script>
+window.onerror = function (errorMsg, url, lineNumber, column, errorObj) {
+
+ // Send object with all data to server side log, using severity fatal,
+ // from logger "onerrorLogger"
+ JL("onerrorLogger").fatalException({
+ "msg": "Exception!",
+ "errorMsg": errorMsg, "url": url,
+ "line number": lineNumber, "column": column
+ }, errorObj);
+
+ // Tell browser to run its own error handler as well
+ return false;
+}
+</script>
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-log4net/index.html b/logging-to-elmah-io-from-log4net/index.html
new file mode 100644
index 0000000000..a1ce2041d8
--- /dev/null
+++ b/logging-to-elmah-io-from-log4net/index.html
@@ -0,0 +1,889 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from log4net
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from log4net
+
+
In this tutorial we'll add logging to elmah.io from a .NET application with log4net. Install the elmah.io appender:
+
Install-Package Elmah.Io.Log4Net
+
dotnet add package Elmah.Io.Log4Net
+
<PackageReference Include="Elmah.Io.Log4Net" Version="5.*" />
+
paket add Elmah.Io.Log4Net
+
+
Add the following to your AssemblyInfo.cs file:
+
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
+
+
Add the following config section to your web/app.config
file:
+
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
+
+
Finally, add the log4net configuration element to web/app.config
:
+
<log4net>
+ <appender name="ElmahIoAppender" type="elmah.io.log4net.ElmahIoAppender, elmah.io.log4net">
+ <logId value="LOG_ID" />
+ <apiKey value="API_KEY" />
+ </appender>
+ <root>
+ <level value="Info" />
+ <appender-ref ref="ElmahIoAppender" />
+ </root>
+</log4net>
+
+
That’s it! log4net is now configured and log messages to elmah.io. Remember to replace API_KEY
(Where is my API key? ) and LOG_ID
(Where is my log ID? ) with your actual log Id. To start logging, write your usual log4net log statements:
+
var log = log4net.LogManager.GetLogger(typeof(HomeController));
+try
+{
+ log.Info("Trying something");
+ throw new ApplicationException();
+}
+catch (ApplicationException ex)
+{
+ log.Error("Error happening", ex);
+}
+
+
Logging custom properties
+
log4net offers a feature called context properties. With context properties, you can log additional key/value pairs with every log message. The elmah.io appender for log4net, supports context properties as well. Context properties are handled like custom properties in the elmah.io UI.
+
Let's utilize two different hooks in log4net, to add context properties to elmah.io:
+
log4net.GlobalContext.Properties["ApplicationIdentifier"] = "MyCoolApp";
+log4net.ThreadContext.Properties["ThreadId"] = Thread.CurrentThread.ManagedThreadId;
+
+log.Info("This is a message with custom properties");
+
+
Basically, we set two custom properties on contextual classes provided by log4net. To read more about the choices in log4net, check out the log4net manual .
+
When looking up the log message in elmah.io, we see the context properties in the Data tab. Besides the two custom variables that we set through GlobalContext
and ThreadContext
, we see a couple of build-in properties in log4net, both prefixed with log4net:
.
+
In addition, Elmah.Io.Log4Net
provides a range of reserved property names, that can be used to fill in data in the correct fields on the elmah.io UI. Let's say you want to fill the User field:
+
var properties = new PropertiesDictionary();
+properties["User"] = "Arnold Schwarzenegger";
+log.Logger.Log(new LoggingEvent(new LoggingEventData
+{
+ Level = Level.Error,
+ TimeStampUtc = DateTime.UtcNow,
+ Properties = properties,
+ Message = "Hasta la vista, baby",
+}));
+
+
This will fill in the value Arnold Schwarzenegger
in the User
field, as well as add a key/value pair to the Data tab on elmah.io. For a reference of all possible property names, check out the property names on CreateMessage .
+
Setting category
+
elmah.io provide a field named Category to better group log messages by class name, namespace, or similar. Category maps to log4net's LoggerName field automatically when using Elmah.Io.Log4Net
. The category field can be overwritten using one of the context features available in log4net:
+
log4net.ThreadContext.Properties["Category"] = "The category";
+log.Info("This is an information message with custom category");
+
+
Message hooks
+
Decorating log messages
+
In case you want to set one or more core properties on each elmah.io message logged, using message hooks may be a better solution. In that case you will need to add a bit of log4net magic. An example could be setting the Version
property on all log messages. In the following code, we set a hard-coded version number on all log messages, but the value could come from assembly info, a text file, or similar:
+
Hierarchy hier = log4net.LogManager.GetRepository(Assembly.GetEntryAssembly()) as Hierarchy;
+var elmahIoAppender = (ElmahIoAppender)(hier?.GetAppenders())
+ .FirstOrDefault(appender => appender.Name
+ .Equals("ElmahIoAppender", StringComparison.InvariantCultureIgnoreCase));
+
+elmahIoAppender.ActivateOptions();
+elmahIoAppender.Client.Messages.OnMessage += (sender, a) =>
+{
+ a.Message.Version = "1.0.0";
+};
+
+
This rather ugly piece of code would go into an initalization block, depending on the project type. The code starts by getting the configured elmah.io appender (typically set up in web/app.config
or log4net.config
). With the appender, you can access the underlying elmah.io client and subscribe to the OnMessage
event. This let you trigger a small piece of code, just before sending log messages to elmah.io. In this case, we set the Version
property to 1.0.0
. Remember to call the ActiveOptions
method, to make sure that the Client
property is initialized.
+
Include source code
+
You can use the OnMessage
event to include source code to log messages. This will require a stack trace in the Detail
property with filenames and line numbers in it.
+
There are multiple ways of including source code to log messages. In short, you will need to install the Elmah.Io.Client.Extensions.SourceCode
NuGet package and call the WithSourceCodeFromPdb
method in the OnMessage
event handler:
+
elmahIoAppender.Client.Messages.OnMessage += (sender, a) =>
+{
+ a.Message.WithSourceCodeFromPdb();
+};
+
+
Check out How to include source code in log messages for additional requirements to make source code show up on elmah.io.
+
+Including source code on log messages is available in the Elmah.Io.Client
v4 package and forward.
+
+
Specify API key and log ID in appSettings
+
You may prefer storing the API key and log ID in the appSettings
element over having the values embedded into the appender
element. This can be the case for easy config transformation, overwriting values on Azure, or similar. log4net provides a feature named pattern strings to address just that:
+
<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <configSections>
+ <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
+ </configSections>
+ <appSettings>
+ <add key="logId" value="LOG_ID"/>
+ <add key="apiKey" value="API_KEY"/>
+ </appSettings>
+ <log4net>
+ <root>
+ <level value="ALL" />
+ <appender-ref ref="ElmahIoAppender" />
+ </root>
+ <appender name="ElmahIoAppender" type="elmah.io.log4net.ElmahIoAppender, elmah.io.log4net">
+ <logId type="log4net.Util.PatternString" value="%appSetting{logId}" />
+ <apiKey type="log4net.Util.PatternString" value="%appSetting{apiKey}" />
+ </appender>
+ </log4net>
+</configuration>
+
+
The logId
and apiKey
elements underneath the elmah.io appender have been extended to include type="log4net.Util.PatternString"
. This allows for complex patterns in the value
attribute. In this example, I reference an app setting from its name, by adding a value of %appSetting{logId}
where logId
is a reference to the app setting key specified above.
+
ASP.NET Core
+
Like other logging frameworks, logging through log4net from ASP.NET Core is also supported. We have a sample to show you how to set it up. The required NuGet packages and configuration are documented in this section.
+
To start logging to elmah.io from Microsoft.Extensions.Logging (through log4net), install the Elmah.Io.Log4Net
and Microsoft.Extensions.Logging.Log4Net.AspNetCore
NuGet packages:
+
Install-Package Elmah.Io.Log4Net
+Install-Package Microsoft.Extensions.Logging.Log4Net.AspNetCore
+
dotnet add package Elmah.Io.Log4Net
+dotnet add package Microsoft.Extensions.Logging.Log4Net.AspNetCore
+
<PackageReference Include="Elmah.Io.Log4Net" Version="5.*" />
+<PackageReference Include="Microsoft.Extensions.Logging.Log4Net.AspNetCore" Version="6.*" />
+
paket add Elmah.Io.Log4Net
+paket add Microsoft.Extensions.Logging.Log4Net.AspNetCore
+
+
The version of Microsoft.Extensions.Logging.Log4Net.AspNetCore
should match the version of .NET you are targeting.
+
Include a log4net config file to the root of the project:
+
<?xml version="1.0" encoding="utf-8" ?>
+<log4net>
+ <root>
+ <level value="WARN" />
+ <appender-ref ref="ElmahIoAppender" />
+ <appender-ref ref="ConsoleAppender" />
+ </root>
+ <appender name="ElmahIoAppender" type="elmah.io.log4net.ElmahIoAppender, elmah.io.log4net">
+ <logId value="LOG_ID" />
+ <apiKey value="API_KEY" />
+ <!--<application value="My app" />-->
+ </appender>
+ <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
+ <layout type="log4net.Layout.PatternLayout">
+ <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
+ </layout>
+ </appender>
+</log4net>
+
+
In the Program.cs
file, make sure to set up log4net:
+
builder.Logging.AddLog4Net();
+
+
All internal logging from ASP.NET Core, as well as manual logging you create through the ILogger
interface, now goes directly into elmah.io.
+
A common request is to include all of the HTTP contextual information you usually get logged when using a package like Elmah.Io.AspNetCore
. We have developed a specialized NuGet package to include cookies, server variables, etc. when logging through log4net from ASP.NET Core. To set it up, install the Elmah.Io.AspNetCore.Log4Net
NuGet package:
+
Install-Package Elmah.Io.AspNetCore.Log4Net
+
dotnet add package Elmah.Io.AspNetCore.Log4Net
+
<PackageReference Include="Elmah.Io.AspNetCore.Log4Net" Version="5.*" />
+
paket add Elmah.Io.AspNetCore.Log4Net
+
+
Finally, make sure to call the UseElmahIoLog4Net
method in the Program.cs
file:
+
// ... Exception handling middleware
+app.UseElmahIoLog4Net();
+// ... UseMvc etc.
+
+
Troubleshooting
+
Here are some things to try out if logging from log4net to elmah.io doesn't work:
+
+Run the diagnose
command with the elmah.io CLI as shown here: Diagnose potential problems with an elmah.io installation .
+Make sure that you have the newest Elmah.Io.Log4Net
and Elmah.Io.Client
packages installed.
+Make sure to include all of the configuration from the example above. That includes both the <root>
and <appender>
element.
+Make sure that the API key is valid and allow the Messages | Write permission .
+Make sure to include a valid log ID.
+Make sure that you have sufficient log messages in your subscription and that you didn't disable logging to the log or include any ignore filters/rules.
+Enable and inspect log4net's internal debug log by including the following code in your web/app.config
file to reveal any exceptions happening inside the log4net engine room or one of the appenders:
+
+
<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <appSettings>
+ <add key="log4net.Internal.Debug" value="true"/>
+ </appSettings>
+ <system.diagnostics>
+ <trace autoflush="true">
+ <listeners>
+ <add
+ name="textWriterTraceListener"
+ type="System.Diagnostics.TextWriterTraceListener"
+ initializeData="C:\temp\log4net-debug.log" />
+ </listeners>
+ </trace>
+ </system.diagnostics>
+</configuration>
+
+
System.IO.FileLoadException: Could not load file or assembly 'log4net ...
+
In case you get the following exception while trying to log messages to elmah.io:
+
System.IO.FileLoadException: Could not load file or assembly 'log4net, Version=2.0.8.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
+
+
This indicates they either the log4net.dll
file is missing or there's a problem with assembly bindings. You can include a binding redirect to the newest version of log4net by including the following code in your web/app.config
file:
+
<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <dependentAssembly>
+ <assemblyIdentity name="log4net" publicKeyToken="669e0ddf0bb1aa2a" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-2.0.12.0" newVersion="2.0.12.0"/>
+ </dependentAssembly>
+ </assemblyBinding>
+ </runtime>
+</configuration>
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-logary/index.html b/logging-to-elmah-io-from-logary/index.html
new file mode 100644
index 0000000000..fc2934c490
--- /dev/null
+++ b/logging-to-elmah-io-from-logary/index.html
@@ -0,0 +1,695 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Logary
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Logary
+
Logary is a semantic logging framework like Serilog and Microsoft Semantic Logging. Combining semantic logs with elmah.io is a perfect fit since elmah.io has been designed with semantics from the ground up.
+
In this tutorial, we’ll add Logary to a Console application, but the process is almost identical to other project types. Create a new console application and add the elmah.io target for Logary:
+
Install-Package Logary.Targets.ElmahIO
+
dotnet add package Logary.Targets.ElmahIO
+
<PackageReference Include="Logary.Targets.ElmahIO" Version="5.*" />
+
paket add Logary.Targets.ElmahIO
+
+
Configuration in F#
+
Configure elmah.io just like you would any normal target:
+
withTargets [
+ // ...
+ ElmahIO.create { logId = Guid.Parse "LOG_ID"; apiKey = "API_KEY" } "elmah.io"
+] >>
+withRules [
+ // ...
+ Rule.createForTarget "elmah.io"
+]
+
+
where LOG_ID
is the id of your log (Where is my log ID? ).
+
Configuration in C#
+
Configuration in C# is just as easy:
+
.Target<ElmahIO.Builder>(
+ "elmah.io",
+ conf => conf.Target.SendTo(logId: "LOG_ID", apiKey: "API_KEY"))
+
+
where API_KEY
is your API key and LOG_ID
is the ID of your log.
+
Logging
+
To start logging messages to elmah.io, you can use the following F# code:
+
let logger = logary.getLogger (PointName [| "Logary"; "Samples"; "main" |])
+
+Message.event Info "User logged in"
+ |> Message.setField "userName" "haf"
+ |> Logger.logSimple logger
+
+
+
or in C#:
+
var logger = logary.GetLogger("Logary.CSharpExample");
+logger.LogEventFormat(LogLevel.Fatal, "Unhandled {exception}!", e);
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-maui/index.html b/logging-to-elmah-io-from-maui/index.html
new file mode 100644
index 0000000000..a65ff09803
--- /dev/null
+++ b/logging-to-elmah-io-from-maui/index.html
@@ -0,0 +1,693 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from MAUI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from MAUI
+
We just started looking into .NET MAUI. If you want to go for the stable choice, check out the integration with Xamarin (the NuGet package is also in prerelease but more stable than the integration described on this page).
+
A quick way to get started with logging from MAUI to elmah.io is by installing the Elmah.Io.Blazor.Wasm
NuGet package:
+
Install-Package Elmah.Io.Blazor.Wasm -IncludePrerelease
+
dotnet add package Elmah.Io.Blazor.Wasm --prerelease
+
<PackageReference Include="Elmah.Io.Blazor.Wasm" Version="4.*" />
+
paket add Elmah.Io.Blazor.Wasm
+
+
The name is, admittedly, misleading here. The Elmah.Io.Blazor.Wasm
package contains a simplified version of the Elmah.Io.Extensions.Logging
package that doesn't make use of the Elmah.Io.Client
package to communicate with the elmah.io API.
+
Finally, configure the elmah.io logger in the MauiProgram.cs
file:
+
public static MauiApp CreateMauiApp()
+{
+ var builder = MauiApp.CreateBuilder();
+ builder
+ .UseMauiApp<App>()
+ // ...
+ .Logging
+ .AddElmahIo(options =>
+ {
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+ });
+
+ return builder.Build();
+}
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the ID of the log you want messages sent to (Where is my log ID? ).
+
The logging initialization code will log all exceptions logged through ILogger
both manually and by MAUI itself. MAUI supports dependency injection of the ILogger
like most other web and mobile frameworks built on top of .NET.
+
Some exceptions are not logged through Microsoft.Extensions.Logging why you need to handle these manually. If you have worked with Xamarin, you probably know about the various exceptions-related events (like UnhandledException
). Which event you need to subscribe to depends on the current platform. An easy approach is to use Matt Johnson-Pint's MauiExceptions
class available here . When the class is added, logging uncaught exceptions is using a few lines of code:
+
// Replace with an instance of the ILogger
+ILogger logger;
+
+// Subscribe to the UnhandledException event and log it through ILogger
+MauiExceptions.UnhandledException += (sender, args) =>
+{
+ var exception = (Exception)args.ExceptionObject;
+ logger.LogError(exception, exception.GetBaseException().Message);
+}
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-microsoft-extensions-logging/index.html b/logging-to-elmah-io-from-microsoft-extensions-logging/index.html
new file mode 100644
index 0000000000..587e2f4714
--- /dev/null
+++ b/logging-to-elmah-io-from-microsoft-extensions-logging/index.html
@@ -0,0 +1,963 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Microsoft.Extensions.Logging
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Microsoft.Extensions.Logging
+
+
Microsoft.Extensions.Logging is both a logging framework itself and a logging abstraction on top of other logging frameworks like log4net and Serilog.
+
Start by installing the Elmah.Io.Extensions.Logging package:
+
Install-Package Elmah.Io.Extensions.Logging
+
dotnet add package Elmah.Io.Extensions.Logging
+
<PackageReference Include="Elmah.Io.Extensions.Logging" Version="5.*" />
+
paket add Elmah.Io.Extensions.Logging
+
+
Locate your API key (Where is my API key? ) and log ID. The two values will be referenced as API_KEY
and LOG_ID
(Where is my log ID? ) in the following.
+
Logging from ASP.NET Core
+
In the Program.cs
file, add a new using
statement:
+
using Elmah.Io.Extensions.Logging;
+
+
Then call the AddElmahIo
-method as shown here:
+
builder.Logging.AddElmahIo(options =>
+{
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+});
+builder.Logging.AddFilter<ElmahIoLoggerProvider>(null, LogLevel.Warning);
+
+
By calling, the AddFilter
-method, you ensure that only warnings and up are logged to elmah.io.
+
The API key and log ID can also be configured in appsettings.json
:
+
{
+ // ...
+ "ElmahIo": {
+ "ApiKey": "API_KEY",
+ "LogId": "LOG_ID"
+ }
+}
+
+
To tell Microsoft.Extensions.Logging to use configuration from the appsettings.json
file, include the following code in Program.cs
:
+
builder.Services.Configure<ElmahIoProviderOptions>(builder.Configuration.GetSection("ElmahIo"));
+builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging"));
+builder.Logging.AddElmahIo();
+
+
The first line fetches elmah.io configuration from the ElmahIo
object in the appsettings.json
file. The second line configures log levels in Microsoft.Extensions.Logging from the Logging
object in appsettings.json
. The third line adds the elmah.io logger to Microsoft.Extensions.Logging. Notice how the overload without any options object is called since options are already loaded from appsettings.json
.
+
Start logging messages by injecting an ILogger
in your controllers:
+
public class HomeController : Controller
+{
+ private readonly ILogger<HomeController> logger;
+
+ public HomeController(ILogger<HomeController> logger)
+ {
+ this.logger = logger;
+ }
+
+ public IActionResult Index()
+ {
+ logger.LogWarning("Request to index");
+ return View();
+ }
+}
+
+
For an example of configuring elmah.io with ASP.NET Core 3.1, check out this sample .
+
Include HTTP context
+
A common use case for using Microsoft.Extensions.Logging is part of an ASP.NET Core project. When combining the two, you would expect the log messages to contain relevant information from the HTTP context (like URL, status code, cookies, etc.). This is not the case out of the box, since Microsoft.Extensions.Logging doesn't know which project type includes it.
+
+Logging HTTP context requires Elmah.Io.Extensions.Logging
version 3.6.x
or newer.
+
+
To add HTTP context properties to log messages when logging from ASP.NET Core, install the Elmah.Io.AspNetCore.ExtensionsLogging
NuGet package:
+
Install-Package Elmah.Io.AspNetCore.ExtensionsLogging
+
dotnet add package Elmah.Io.AspNetCore.ExtensionsLogging
+
<PackageReference Include="Elmah.Io.AspNetCore.ExtensionsLogging" Version="5.*" />
+
paket add Elmah.Io.AspNetCore.ExtensionsLogging
+
+
Then call the UseElmahIoExtensionsLogging
method in the Program.cs
file:
+
// ... Exception handling middleware
+app.UseElmahIoExtensionsLogging();
+// ... UseMvc etc.
+
+
It's important to call the UseElmahIoExtensionsLogging
method after any calls to UseElmahIo
, UseAuthentication
, and other exception handling middleware but before UseMvc
and UseEndpoints
. If you experience logged errors without the HTTP context, try moving the UseElmahIoExtensionsLogging
method as the first call in the Configure
method.
+
Logging from a console application
+
Choose the right framework version:
+
+
+
+
+
Configure logging to elmah.io using a new or existing ServiceCollection
:
+
var services = new ServiceCollection();
+services.AddLogging(logging => logging.AddElmahIo(options =>
+{
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+}));
+
+
Get a reference to the LoggerFactory
:
+
using var serviceProvider = services.BuildServiceProvider();
+var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
+
+
+
+
Create a new LoggerFactory
and configure it to use elmah.io:
+
using var loggerFactory = LoggerFactory.Create(builder => builder
+ .AddElmahIo(options =>
+ {
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+ }));
+
+
+
+
Adding the using
keyword is important to let elmah.io store messages before exiting the application.
+
Finally, create a new logger and start logging exceptions:
+
var logger = factory.CreateLogger("MyLog");
+logger.LogError(ex, "Unexpected error");
+
+
Logging custom properties
+
Elmah.Io.Extensions.Logging
support Microsoft.Extensions.Logging scopes from version 3.6.x
. In short, scopes are a way to decorate your log messages like enrichers in Serilog and context in NLog and log4net. By including properties in a scope, these properties automatically go into the Data tab on elmah.io.
+
To define a new scope, wrap your logging code in a using
:
+
using (logger.BeginScope(new Dictionary<string, object> { { "UserId", 42 } }))
+{
+ logger.LogInformation("Someone says hello");
+}
+
+
In the example above, the UserId
key will be added on the Data tab with the value of 42
.
+
Like the other logging framework integrations, Elmah.Io.Extensions.Logging
supports a range of known keys that can be used to insert value in the correct fields on the elmah.io UI.
+
using (logger.BeginScope(new Dictionary<string, object>
+ { { "statuscode", 500 }, { "method", "GET" } }))
+{
+ logger.LogError("Request to {url} caused an error", "/profile");
+}
+
+
In this example, a log message with the template Request to {url} caused an error
to be logged. The use of the variable names statuscode
, method
, and url
will fill in those values in the correct fields on elmah.io. For a reference of all possible property names, check out the property names on CreateMessage .
+
An alternative is to use the OnMessage
action. As an example, we'll add a version number to all messages:
+
logging
+ .AddElmahIo(options =>
+ {
+ // ...
+ options.OnMessage = msg =>
+ {
+ msg.Version = "2.0.0";
+ };
+ });
+
+
You can even access the current HTTP context in the OnMessage
action. To do so, start by creating a new class named DecorateElmahIoMessages
:
+
public class DecorateElmahIoMessages : IConfigureOptions<ElmahIoProviderOptions>
+{
+ private readonly IHttpContextAccessor httpContextAccessor;
+
+ public DecorateElmahIoMessages(IHttpContextAccessor httpContextAccessor)
+ {
+ this.httpContextAccessor = httpContextAccessor;
+ }
+
+ public void Configure(ElmahIoProviderOptions options)
+ {
+ options.OnMessage = msg =>
+ {
+ var context = httpContextAccessor.HttpContext;
+ if (context == null) return;
+ msg.User = context.User?.Identity?.Name;
+ };
+ }
+}
+
+
Then register IHttpContextAccessor
and the new class in the Program.cs
file:
+
builder.Services.AddHttpContextAccessor();
+builder.Services.AddSingleton<IConfigureOptions<ElmahIoProviderOptions>, DecorateElmahIoMessages>();
+
+
Setting category
+
elmah.io provide a field named Category to better group log messages by class name, namespace, or similar. Category maps to Microsoft.Extensions.Logging field of the same name. The category field is automatically set when using a typed logger:
+
ILogger<MyType> logger = ...;
+logger.LogInformation("This is an information message with category");
+
+
The category can be overwritten using a property named category
on either the log message or a new scope:
+
using (logger.BeginScope(new Dictionary<string, object> { { "category", "The category" } }))
+{
+ logger.LogInformation("This is an information message with category");
+}
+
+
Include source code
+
You can use the OnMessage
action already described to include source code to log messages. This will require a stack trace in the Detail
property with filenames and line numbers in it.
+
There are multiple ways of including source code to log messages. In short, you will need to install the Elmah.Io.Client.Extensions.SourceCode
NuGet package and call the WithSourceCodeFromPdb
method in the OnMessage
action:
+
logging
+ .AddElmahIo(options =>
+ {
+ // ...
+ options.OnMessage = msg =>
+ {
+ msg.WithSourceCodeFromPdb();
+ };
+ });
+
+
Check out How to include source code in log messages for additional requirements to make source code show up on elmah.io.
+
+Including source code on log messages is available in the Elmah.Io.Client
v4 package and forward.
+
+
Options
+
Setting application name
+
If logging to the same log from multiple applications it is a good idea to set unique application names from each app. This will let you search and filter errors on the elmah.io UI. To set an application name, add the following code to the options:
+
logging.AddElmahIo(options =>
+{
+ // ...
+ options.Application = "MyApp";
+});
+
+
The application name can also be configured through appsettings.json
:
+
{
+ // ...
+ "ElmahIo": {
+ // ...
+ "Application": "MyApp"
+ }
+}
+
+
appsettings.json configuration
+
Some of the configuration for Elmah.Io.Extensions.Logging can be done through the appsettings.json
file when using ASP.NET Core 2.x or above. To configure the minimum log level, add a new logger named ElmahIo
to the settings file:
+
{
+ "Logging": {
+ // ...
+ "ElmahIo": {
+ "LogLevel": {
+ "Default": "Warning"
+ }
+ }
+ }
+}
+
+
Finally, tell the logger to look for this information, by adding a bit of code to Program.cs
:
+
builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging"));
+
+
Filtering log messages
+
As default, the elmah.io logger for Microsoft.Extensions.Logging only logs warnings, errors, and fatals. The rationale behind this is that we build an error management system and doesn't do much to support millions of debug messages from your code. Sometimes you may want to log non-exception messages, though. To do so, use filters in Microsoft.Extensions.Logging.
+
To log everything from log level Information
and up, do the following:
+
Inside Program.cs
change the minimum level:
+
builder.Logging.AddFilter<ElmahIoLoggerProvider>(null, LogLevel.Information);
+
+
In the code sample, every log message with a log level of Information
and up will be logged to elmah.io.
+
Logging through a proxy
+
+Proxy configuration requires 3.5.49
or newer.
+
+
You can log through a proxy using options:
+
logging.AddElmahIo(options =>
+{
+ // ...
+ options.WebProxy = new WebProxy("localhost", 8000);
+});
+
+
In this example, the elmah.io client routes all traffic through http://localhost:8000
.
+
Troubleshooting
+
Here are some things to try out if logging from Microsoft.Extensions.Logging to elmah.io doesn't work:
+
+
x
message(s) dropped because of queue size limit
+
If you see this message in your log, it means that you are logging a large number of messages to elmah.io through Microsoft.Extensions.Logging within a short period of time. Either turn down the volume using filters:
+
logging.AddElmahIo(options => { /*...*/ });
+logging.AddFilter<ElmahIoLoggerProvider>(null, LogLevel.Warning);
+
+
or increase the queue size of Elmah.Io.Extensions.Logging
:
+
logging.AddElmahIo(options =>
+{
+ // ...
+ options.BackgroundQueueSize = 5000;
+});
+
+
Uncaught errors are logged twice
+
If you have both Elmah.Io.Extensions.Logging
and Elmah.Io.AspNetCore
installed, you may see a pattern of uncaught errors being logged twice. This is because a range of middleware from Microsoft (and others) log uncaught exceptions through ILogger
. When you have Elmah.Io.AspNetCore
installed you typically don't want other pieces of middleware to log the same error but with fewer details.
+
To ignore duplicate errors you need to figure out which middleware class that trigger the logging and in which namespace it is located. One or more of the following ignore filters can be added to your Program.cs
file:
+
logging.AddFilter<ElmahIoLoggerProvider>(
+ "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware", LogLevel.None);
+logging.AddFilter<ElmahIoLoggerProvider>(
+ "Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware", LogLevel.None);
+logging.AddFilter<ElmahIoLoggerProvider>(
+ "Microsoft.AspNetCore.Server.IIS.Core", LogLevel.None);
+
+
Be aware that these lines will ignore all messages from the specified class or namespace. To ignore specific errors you can implement the OnFilter
action as shown previously in this document. Ignoring uncaught errors from IIS would look like this:
+
options.OnFilter = msg =>
+{
+ return msg.TitleTemplate == "Connection ID \"{ConnectionId}\", Request ID \"{TraceIdentifier}\": An unhandled exception was thrown by the application.";
+};
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-nancy/index.html b/logging-to-elmah-io-from-nancy/index.html
new file mode 100644
index 0000000000..6706ccf22a
--- /dev/null
+++ b/logging-to-elmah-io-from-nancy/index.html
@@ -0,0 +1,691 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Nancy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Nancy
+
+Nancy is no longer maintained. While the installation steps on this page (probably) still work, we no longer test the Nancy.Elmah
package.
+
+
As with MVC and WebAPI, Nancy already provides ELMAH support out of the box. Start by installing the Elmah.Io
NuGet package:
+
Install-Package Elmah.Io
+
dotnet add package Elmah.Io
+
<PackageReference Include="Elmah.Io" Version="5.*" />
+
+
During the installation, you will be asked for your API key (Where is my API key? ) and log ID (Where is my log ID? ).
+
To integrate Nancy and ELMAH, Christian Westman already did a great job with his Nancy.Elmah
package. Install it using NuGet:
+
Install-Package Nancy.Elmah
+
dotnet add package Nancy.Elmah
+
<PackageReference Include="Nancy.Elmah" Version="0.*" />
+
+
You must install the Elmah.Io
package before Nancy.Elmah
, because both packages like to add the ELMAH configuration to the web.config file. If you install it the other way around, you will need to add the elmah.io ErrorLog element manually.
+
For Nancy to know how to log errors to Elmah, you need to add an override of the DefaultNancyBootstrapper. Create a new class in the root named Bootstrapper:
+
using Nancy;
+using Nancy.Bootstrapper;
+using Nancy.Elmah;
+using Nancy.TinyIoc;
+
+namespace Elmah.Io.NancyExample
+{
+ public class Bootstrapper : DefaultNancyBootstrapper
+ {
+ protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
+ {
+ base.ApplicationStartup(container, pipelines);
+ Elmahlogging.Enable(pipelines, "elmah");
+ }
+ }
+}
+
+
The important thing in the code sample is line 13, where we tell Nancy.Elmah to hook into the pipeline of Nancy for it to catch and log HTTP errors. The second parameter for the Enable-method, lets us define a URL for the ELMAH error page, which can be used as an alternative to elmah.io for quick viewing of errors.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-nlog/index.html b/logging-to-elmah-io-from-nlog/index.html
new file mode 100644
index 0000000000..8c871dd4ac
--- /dev/null
+++ b/logging-to-elmah-io-from-nlog/index.html
@@ -0,0 +1,1022 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from NLog
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from NLog
+
+
NLog is one of the most popular logging frameworks for .NET. With an active history of almost 10 years, the possibilities with NLog are many and it’s easy to find documentation on how to use it.
+
To start logging messages from NLog to elmah.io, you need to install the Elmah.Io.NLog
NuGet package:
+
Install-Package Elmah.Io.NLog
+
dotnet add package Elmah.Io.NLog
+
<PackageReference Include="Elmah.Io.NLog" Version="5.*" />
+
paket add Elmah.Io.NLog
+
+
+Please don't use NLog 4.6.0
since that version contains a bug that causes the elmah.io target to not load correctly. 4.5.11
, 4.6.1
, or newer.
+
+
Configuration in .NET
+
To configure the elmah.io target, add the following configuration to your app.config/web.config/nlog.config depending on what kind of project you’ve created:
+
+
+
+
+
<extensions>
+ <add assembly="Elmah.Io.NLog"/>
+</extensions>
+
+<targets>
+ <target name="elmahio" type="elmah.io" apiKey="API_KEY" logId="LOG_ID"/>
+</targets>
+
+<rules>
+ <logger name="*" minlevel="Info" writeTo="elmahio" />
+</rules>
+
+
+
+
<targets>
+ <target name="elmahio" type="elmah.io, Elmah.Io.NLog" apiKey="API_KEY" logId="LOG_ID"/>
+</targets>
+
+<rules>
+ <logger name="*" minlevel="Info" writeTo="elmahio" />
+</rules>
+
+
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the ID of the log you want messages sent to (Where is my log ID? ).
+
In the example, we specify the level minimum as Info. This tells NLog to log only information, warning, error, and fatal messages. You may adjust this, but be aware that your elmah.io log may run full pretty fast, especially if you log thousands and thousands of trace and debug messages.
+
Log messages to elmah.io, just as with every other target and NLog:
+
log.Warn("This is a warning message");
+log.Error(new Exception(), "This is an error message");
+
+
Specify API key and log ID in appSettings
+
If you are already using elmah.io, you may have your API key and log ID in the appSettings
element already. To use these settings from withing the NLog target configuration you can use an NLog layout formatter:
+
<targets>
+ <target name="elmahio" type="elmah.io" apiKey="${appsetting:item=apiKey}" logId="${appsetting:item=logId}"/>
+</targets>
+
+
By using the layout ${appsetting:item=apiKey}
you tell NLog that the value for this attribute is in an appSettings
element named elmahKey
:
+
<appSettings>
+ <add key="apiKey" value="API_KEY" />
+ <add key="logId" value="LOG_ID" />
+</appSettings>
+
+
+The appSettings
layout formatter only works when targeting .NET Full Framework and requires Elmah.Io.NLog
version 3.3.x or above and NLog
version 4.6.x or above.
+
+
IntelliSense
+
There is support for adding IntelliSense in Visual Studio for the NLog.config
file. Extend the nlog
root element like this:
+
<?xml version="1.0" encoding="utf-8" ?>
+<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:elmahio="http://www.nlog-project.org/schemas/NLog.Targets.Elmah.Io.xsd"
+ xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.Targets.Elmah.Io.xsd http://www.nlog-project.org/schemas/NLog.Targets.Elmah.Io.xsd">
+ <!-- ... -->
+</nlog>
+
+
Then change the type
attribute in the target to xsi:type
:
+
<target xsi:type="elmahio:elmah.io" />
+
+
Configuration in appsettings.json
+
.NET 5 (previously Core) and newer switched from declaring XML configuration in app/web/nlog.config
files to JSON configuration in an appsettings.json
file. To configure elmah.io in JSON, install the NLog.Extensions.Logging
NuGet package:
+
Install-Package NLog.Extensions.Logging
+
dotnet add package NLog.Extensions.Logging
+
<PackageReference Include="NLog.Extensions.Logging" Version="1.*" />
+
paket add NLog.Extensions.Logging
+
+
Extend the appsettings.json
file with a new NLog
section:
+
+
+
+
+
{
+ "NLog": {
+ "throwConfigExceptions": true,
+ "extensions": [
+ { "assembly": "Elmah.Io.NLog" }
+ ],
+ "targets": {
+ "elmahio": {
+ "type": "elmah.io",
+ "apiKey": "API_KEY",
+ "logId": "LOG_ID"
+ }
+ },
+ "rules": [
+ {
+ "logger": "*",
+ "minLevel": "Info",
+ "writeTo": "elmahio"
+ }
+ ]
+ }
+}
+
+
+
+
{
+ "NLog": {
+ "throwConfigExceptions": true,
+ "targets": {
+ "elmahio": {
+ "type": "elmah.io, Elmah.Io.NLog",
+ "apiKey": "API_KEY",
+ "logId": "LOG_ID"
+ }
+ },
+ "rules": [
+ {
+ "logger": "*",
+ "minLevel": "Info",
+ "writeTo": "elmahio"
+ }
+ ]
+ }
+}
+
+
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the ID of the log you want messages sent to (Where is my log ID? ).
+
If you haven't already loaded the configuration in your application, make sure to do so:
+
var config = new ConfigurationBuilder()
+ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
+ .Build();
+
+
Finally, tell NLog how to load the NLog
configuration section:
+
LogManager.Configuration = new NLogLoggingConfiguration(config.GetSection("NLog"));
+
+
elmah.io configuration outside the NLog section
+
You might not want the elmah.io API key and log Id inside the NLog
section or already have an ElmahIo
section defined and want to reuse that. Splitting up configuration like that is supported through NLog layout renderers:
+
{
+ "NLog": {
+ // ...
+ "targets": {
+ "elmahio": {
+ "type": "elmah.io",
+ "apiKey": "${configsetting:item=ElmahIo.ApiKey}",
+ "logId": "${configsetting:item=ElmahIo.LogId}"
+ }
+ },
+ // ...
+ },
+ "ElmahIo": {
+ "ApiKey": "API_KEY",
+ "LogId": "LOG_ID"
+ }
+}
+
+
Notice how the value of the apiKey
and logId
parameters have been replaced with ${configsetting:item=ElmahIo.*}
. At the bottom, the ElmahIo
section wraps the API key and log Id.
+
To make this work, you will need an additional line of C# when setting up NLog logging:
+
var config = new ConfigurationBuilder()
+ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
+ .Build();
+
+ConfigSettingLayoutRenderer.DefaultConfiguration = config; // 👈 add this line
+
+LogManager.Configuration = new NLogLoggingConfiguration(config.GetSection("NLog"));
+
+
IntelliSense
+
There is support for adding IntelliSense in Visual Studio for the NLog
section in the appsettings.json
file. Copy and paste the following link into the Schema textbox above the file content:
+
https://nlog-project.org/schemas/appsettings.schema.json
+
+
Configuration in code
+
The elmah.io target can be configured from C# code if you prefer or need to access the built-in events (see more later). The following adds logging to elmah.io:
+
var config = new LoggingConfiguration();
+var elmahIoTarget = new ElmahIoTarget();
+elmahIoTarget.Name = "elmahio";
+elmahIoTarget.ApiKey = "API_KEY";
+elmahIoTarget.LogId = "LOG_ID";
+config.AddTarget(elmahIoTarget);
+config.AddRuleForAllLevels(elmahIoTarget);
+LogManager.Configuration = config;
+
+
The example will log all log levels to elmah.io. For more information about how to configure individual log levels, check out the NLog documentation on GitHub .
+
Logging custom properties
+
NLog supports logging custom properties in multiple ways. If you want to include a property (like a version number) in all log messages, you might want to look into the OnMessage
feature on Elmah.Io.NLog
.
+
With custom properties, you can log additional key/value pairs with every log message. The elmah.io target for NLog supports custom properties as well. Properties are persisted alongside every log message in elmah.io and searchable if named correctly .
+
One way to log custom properties with NLog and elmah.io is to use the overload of each logging-method that takes a LogEventInfo
object as a parameter:
+
var infoMessage = new LogEventInfo(LogLevel.Info, "", "This is an information message");
+infoMessage.Properties.Add("Some Property Key", "Some Property Value");
+log.Info(infoMessage);
+
+
This saves the information message in elmah.io with a custom property with key Some Property Key
and value Some Property Value
.
+
As of NLog 4.5, structured logging is supported as well. To log a property as part of the log message, use the new syntax as shown here:
+
log.Warn("Property named {FirstName}", "Donald");
+
+
In the example, NLog will log the message Property named "Donald"
, but the key (FirstName
) and value (Donald
), will also be available in the Data tab inside elmah.io.
+
Elmah.Io.NLog
provides a range of reserved property names, that can be used to fill in data in the correct fields on the elmah.io UI. Let's say you want to fill the User
field using structured logging only:
+
log.Info("{Quote} from {User}", "Hasta la vista, baby", "T-800");
+
+
This will fill in the value T-800
in the User
field, as well as add two key/value pairs (Quote
and User
) to the Data tab on elmah.io. For a reference of all possible property names, check out the property names on CreateMessage .
+
NLog also provides a fluent API (available in the NLog.Fluent
namespace) that some might find more readable:
+
logger.Info()
+ .Message("I'll be back")
+ .Property("User", "T-800")
+ .Write();
+
+
If you want to use the normal logging methods like Info
and Error
, you can do so injunction with the MappedDiagnosticsLogicalContext
class, also provided by NLog:
+
using (MappedDiagnosticsLogicalContext.SetScoped("User", "T-800"))
+{
+ logger.Info("I'll be back");
+}
+
+
This will create the same result as the example above.
+
In NLog 5 MappedDiagnosticsLogicalContext
has been deprecated in favor of a scope context:
+
using (logger.PushScopeProperty("User", "T-800"))
+{
+ logger.Info("I'll be back");
+}
+
+
Setting application name
+
The application field on elmah.io can be set globally using NLog's global context:
+
GlobalDiagnosticsContext.Set("Application", "My application name");
+
+
Setting version number
+
The version field on elmah.io can be set globally using NLog's global context:
+
GlobalDiagnosticsContext.Set("Version", "1.2.3");
+
+
Setting category
+
elmah.io provide a field named Category to better group log messages by class name, namespace, or similar. Category maps to NLog's logger field automatically when using Elmah.Io.NLog
. The category field can be overwritten using a scoped property (NLog 5):
+
using (logger.PushScopeProperty("category", "The category"))
+{
+ logger.Info("This is an information message with category");
+}
+
+
Message hooks
+
Elmah.Io.NLog
provides message hooks similar to the integrations with ASP.NET and ASP.NET Core. Message hooks need to be implemented in C#. Either configure the elmah.io target in C# or fetch the target already configured in XML:
+
var elmahIoTarget = (ElmahIoTarget)LogManager.Configuration.FindTargetByName("elmahio");
+
+
You also need to install the most recent version of the Elmah.Io.Client
NuGet package to use message hooks.
+
Decorating log messages
+
To include additional information on log messages, you can use the OnMessage
action:
+
elmahIoTarget.OnMessage = msg =>
+{
+ msg.Version = "1.0.0";
+};
+
+
The example above includes a version number on all errors.
+
Include source code
+
You can use the OnMessage
action to include source code to log messages. This will require a stack trace in the Detail
property with filenames and line numbers in it.
+
There are multiple ways of including source code to log messages. In short, you will need to install the Elmah.Io.Client.Extensions.SourceCode
NuGet package and call the WithSourceCodeFromPdb
method in the OnMessage
action:
+
elmahIoTarget.OnMessage = msg =>
+{
+ msg.WithSourceCodeFromPdb();
+};
+
+
Check out How to include source code in log messages for additional requirements to make source code show up on elmah.io.
+
+Including source code on log messages is available in the Elmah.Io.Client
v4 package and forward.
+
+
Handle errors
+
To handle any errors happening while processing a log message, you can use the OnError event when initializing the elmah.io target:
+
elmahIoTarget.OnError = (msg, err) =>
+{
+ // Do something here
+};
+
+
The example implements a callback if logging to elmah.io fails. How you choose to implement this is entirely up to your application and tech stack.
+
Error filtering
+
To ignore specific errors based on their content, you can use the OnFilter event when initializing the elmah.io target:
+
elmahIoTarget.OnFilter = msg =>
+{
+ return msg.Title.Contains("trace");
+};
+
+
The example above ignores any log messages with the word trace
in the title.
+
Include HTTP context in ASP.NET and ASP.NET Core
+
When logging through NLog from a web application, you may want to include HTTP contextual information like the current URL, status codes, server variables, etc. NLog provides two web-packages to include this information. For ASP.NET, MVC, and Web API you can install the NLog.Web
NuGet package and include the following code in web.config
:
+
<system.webServer>
+ <modules runAllManagedModulesForAllRequests="true">
+ <add name="NLog" type="NLog.Web.NLogHttpModule, NLog.Web" />
+ </modules>
+</system.webServer>
+
+
For ASP.NET Core you can install the NLog.Web.AspNetCore
NuGet package. When installed, the elmah.io NLog target automatically picks up the HTTP context and fills in all possible fields on messages sent to elmah.io.
+
Troubleshooting
+
Here are some things to try out if logging from NLog to elmah.io doesn't work:
+
+Run the diagnose
command with the elmah.io CLI as shown here: Diagnose potential problems with an elmah.io installation .
+Make sure that you have the newest Elmah.Io.NLog
and Elmah.Io.Client
packages installed.
+Make sure to include all of the configuration from the example above. That includes both the <extensions>
(Only needed in Elmah.Io.NLog
3 and 4), <targets>
, and <rules>
element.
+Make sure that the API key is valid and allow the Messages | Write permission .
+Make sure to include a valid log ID.
+Make sure that you have sufficient log messages in your subscription and that you didn't disable logging to the log or include any ignore filters/rules.
+Always make sure to call LogManager.Shutdown()
before exiting the application to make sure that all log messages are flushed.
+Extend the nlog
element with internalLogLevel="Warn" internalLogFile="c:\temp\nlog-internal.log
and inspect that log file for any internal NLog errors.
+
+
System.IO.FileLoadException: Could not load file or assembly 'Newtonsoft.Json'
+
If you see this error in the internal NLog file it means that there's a problem with multiple assemblies referencing different versions of Newtonsoft.Json
(also known as NuGet dependency hell ). This can be fixed by redirecting to the installed version in the App.config
or Web.config
file:
+
<runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <dependentAssembly>
+ <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-13.0.1.0" newVersion="13.0.1.0"/>
+ </dependentAssembly>
+ </assemblyBinding>
+</runtime>
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-orchard/index.html b/logging-to-elmah-io-from-orchard/index.html
new file mode 100644
index 0000000000..8c61fa6514
--- /dev/null
+++ b/logging-to-elmah-io-from-orchard/index.html
@@ -0,0 +1,717 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Orchard CMS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Orchard CMS
+
Orchard CMS is a free, open-source community-focused content management system built on the ASP.NET MVC and ASP.NET Core platforms. This tutorial is written for the ASP.NET Core version of Orchard. If you want to log to elmah.io from the MVC version, you should follow our tutorial for MVC .
+
To start logging to elmah.io, install the Elmah.Io.Client
and Elmah.Io.AspNetCore
NuGet packages:
+
Install-Package Elmah.Io.Client
+Install-Package Elmah.Io.AspNetCore
+
dotnet add package Elmah.Io.Client
+dotnet add package Elmah.Io.AspNetCore
+
<PackageReference Include="Elmah.Io.Client" Version="5.*" />
+<PackageReference Include="Elmah.Io.AspNetCore" Version="5.*" />
+
paket add Elmah.Io.Client
+paket add Elmah.Io.AspNetCore
+
+
Then modify your Startup.cs
file:
+
public class Startup
+{
+ public void ConfigureServices(IServiceCollection services)
+ {
+ // ...
+ services.AddElmahIo(o =>
+ {
+ o.ApiKey = "API_KEY";
+ o.LogId = new Guid("LOG_ID");
+ });
+ }
+
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ // ...
+ app.UseElmahIo();
+ // ...
+ }
+}
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the id of the log (Where is my log ID? ) where you want errors logged.
+
Like with any other ASP.NET Core application, it's important to call the UseElmahIo
-method after setting up other middleware handling exceptions (like UseDeveloperExceptionPage
).
+
Orchard uses NLog as the internal logging framework. Hooking into this pipeline is a great way to log warnings and errors through NLog to elmah.io as well.
+
Install the Elmah.Io.Nlog
NuGet package:
+
Install-Package Elmah.Io.NLog
+
dotnet add package Elmah.Io.NLog
+
<PackageReference Include="Elmah.Io.NLog" Version="5.*" />
+
paket add Elmah.Io.NLog
+
+
Add the elmah.io target to the NLog.config
-file:
+
<?xml version="1.0" encoding="utf-8" ?>
+<nlog>
+
+ <extensions>
+ <!-- ... -->
+ <add assembly="Elmah.Io.NLog"/>
+ </extensions>
+
+ <targets>
+ <!-- ... -->
+ <target name="elmahio" type="elmah.io" apiKey="API_KEY" logId="LOG_ID"/>
+ </targets>
+
+ <rules>
+ <!-- ... -->
+ <logger name="*" minlevel="Warn" writeTo="elmahio" />
+ </rules>
+</nlog>
+
+
Make sure not to log Trace and Debug messages to elmah.io, which will quickly use up the included storage.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-piranha-cms/index.html b/logging-to-elmah-io-from-piranha-cms/index.html
new file mode 100644
index 0000000000..22f577dab2
--- /dev/null
+++ b/logging-to-elmah-io-from-piranha-cms/index.html
@@ -0,0 +1,686 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Piranha CMS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Piranha CMS
+
Piranha CMS is a popular headless CMS written in ASP.NET Core. elmah.io works with Piranha CMS out of the box. This document contains a quick installation guide for setting up elmah.io logging in Piranha CMS. For the full overview of logging from ASP.NET Core, check out Logging to elmah.io from ASP.NET Core .
+
To start logging to elmah.io, install the Elmah.Io.AspNetCore
NuGet package:
+
Install-Package Elmah.Io.AspNetCore
+
dotnet add package Elmah.Io.AspNetCore
+
<PackageReference Include="Elmah.Io.AspNetCore" Version="5.*" />
+
paket add Elmah.Io.AspNetCore
+
+
Then modify your Startup.cs
file:
+
public class Startup
+{
+ public void ConfigureServices(IServiceCollection services)
+ {
+ // ...
+ services.AddElmahIo(o =>
+ {
+ o.ApiKey = "API_KEY";
+ o.LogId = new Guid("LOG_ID");
+ });
+ }
+
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ // ...
+ app.UseElmahIo();
+ // ...
+ }
+}
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the id of the log (Where is my log ID? ) where you want errors logged.
+
Make sure to call the UseElmahIo
-method after setting up other middleware handling exceptions (like UseDeveloperExceptionPage
), but before the call to UsePiranha
.
+
To use structured logging and the ILogger
interface with Piranha CMS and elmah.io, set up Microsoft.Extensions.Logging as explained here: Logging to elmah.io from Microsoft.Extensions.Logging .
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-powershell/index.html b/logging-to-elmah-io-from-powershell/index.html
new file mode 100644
index 0000000000..aafc29c1e0
--- /dev/null
+++ b/logging-to-elmah-io-from-powershell/index.html
@@ -0,0 +1,767 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from PowerShell
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from PowerShell
+
+
There are a couple of options for logging to elmah.io from PowerShell. If you need to log a few messages, using the API is the easiest.
+
Log through the API
+
Logging to elmah.io from PowerShell is easy using built-in cmdlets:
+
$apiKey = "API_KEY"
+$logId = "LOG_ID"
+$url = "https://api.elmah.io/v3/messages/$logId/?api_key=$apiKey"
+
+$body = @{
+ title = "Error from PowerShell"
+ severity = "Error"
+ detail = "This is an error message logged from PowerShell"
+ hostname = hostname
+}
+Invoke-RestMethod -Method Post -Uri $url -Body ($body|ConvertTo-Json) -ContentType "application/json-patch+json"
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the ID of the log you want messages sent to (Where is my log ID? ).
+
Log through PoShLog.Sinks.ElmahIo
+
PoShLog is a PowerShell logging module built on top of Serilog. To log to elmah.io using PoShLog, install the following packages:
+
Install-Module -Name PoShLog
+Install-Module -Name PoShLog.Sinks.ElmahIo
+
+
Logging messages can now be done using Write-*Log
:
+
Import-Module PoShLog
+Import-Module PoShLog.Sinks.ElmahIo
+
+New-Logger |
+ Add-SinkElmahIo -ApiKey 'API_KEY' -LogId 'LOG_ID' |
+ Start-Logger
+
+Write-ErrorLog 'Say My Name'
+
+# Don't forget to close the logger
+Close-Logger
+
+
Log through Elmah.Io.Client
+
If you prefer to use the Elmah.Io.Client
NuGet package, you can do this in PowerShell too. First of all, you will need to include elmah.io.client.dll
. How you do this is entirely up to you. You can include this assembly in your script location or you can download it through NuGet on every execution. To download the package through NuGet, you will need nuget.exe
:
+
$source = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
+$target = ".\nuget.exe"
+Invoke-WebRequest $source -OutFile $target
+Set-Alias nuget $target -Scope Global
+
+
This script will download the latest version of the NuGet command-line tool and make it available through the command nuget
.
+
To install Elmah.Io.Client
run nuget.exe
:
+
nuget install Elmah.Io.Client
+
+
This will create an Elmah.Io.Client-version
folder containing the latest stable version of the Elmah.Io.Client
package.
+
You now have Elmah.Io.Client.dll
loaded into your shell and everything is set up to log to elmah.io. To do so, add try-catch around critical code:
+
$logger = [Elmah.Io.Client.ElmahioAPI]::Create("API_KEY")
+Try {
+ # some code that may throw exceptions
+}
+Catch {
+ $logger.Messages.Error([guid]::new("LOG_ID"), $_.Exception, "Oh no")
+}
+
+
In the first line, we create a new logger object. Then, in the Catch
block, the catched exception is shipped off to the elmah.io log specified in LOG_ID
together with a custom message.
+
Examples
+
For inspiration, here's a list of examples of common scenarios where you'd want to log to elmah.io from PowerShell.
+
Log error on low remaining disk
+
You can monitor when a server is running low on disk space like this:
+
$cdrive = Get-Volume -DriveLetter C
+$sizeremainingingb = $cdrive.SizeRemaining/1024/1024/1024
+if ($sizeremainingingb -lt 10) {
+ $apiKey = "API_KEY"
+ $logId = "LOG_ID"
+ $url = "https://api.elmah.io/v3/messages/$logId/?api_key=$apiKey"
+
+ $body = @{
+ title = "Disk storage less than 10 gb"
+ severity = "Error"
+ detail = "Remaining storage in gb: $sizeremainingingb"
+ hostname = hostname
+ }
+ Invoke-RestMethod -Method Post -Uri $url -Body ($body|ConvertTo-Json) -ContentType "application/json-patch+json"
+}
+
+
Troubleshooting
+
Elmah.Io.Client.ElmahioAPI cannot be loaded
+
If PowerShell complains about Elmah.Io.Client.ElmahioAPI
not being found, try adding the following lines to the script after installing the Elmah.Io.Client
NuGet package:
+
$elmahIoClientPath = Get-ChildItem -Path . -Filter Elmah.Io.Client.dll -Recurse `
+ | Where-Object {$_.FullName -match "net45"}
+[Reflection.Assembly]::LoadFile($elmahIoClientPath.FullName)
+
+$jsonNetPath = Get-ChildItem -Path . -Filter Newtonsoft.Json.dll -Recurse `
+ | Where-Object {$_.FullName -match "net45" -and $_.FullName -notmatch "portable"}
+[Reflection.Assembly]::LoadFile($jsonNetPath.FullName)
+
+
You may need to include additional assemblies if PowerShell keeps complaining.
+
The catch block is not invoked even though a cmdlet failed
+
Most errors in PowerShell are non-terminating meaning that they are handled internally in the cmdlet. To force a cmdlet to use terminating errors use the -ErrorAction
parameter:
+
Try {
+ My-Cmdlet -ErrorAction Stop
+}
+Catch {
+ // Log to elmah.io
+}
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-react/index.html b/logging-to-elmah-io-from-react/index.html
new file mode 100644
index 0000000000..b7de7563bb
--- /dev/null
+++ b/logging-to-elmah-io-from-react/index.html
@@ -0,0 +1,674 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from React
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from React
+
To log all errors from a React application, install the elmah.io.javascript
npm package as described in Logging from JavaScript . Then modify the index.js
or index.tsx
file:
+
// ...
+import Elmahio from 'elmah.io.javascript';
+
+new Elmahio({
+ apiKey: 'API_KEY',
+ logId: 'LOG_ID'
+});
+
+// After this the ReactDOM.render etc. will be included
+
+
When launching your React app, elmah.io is configured and all errors happening in the application are logged.
+
Check out the elmahio-react and elmahio-react-typescript samples for some real working code.
+
+React have a known bug/feature in DEV mode where errors are submitted twice. For better error handling in React, you should look into Error Boundaries .
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-serilog/index.html b/logging-to-elmah-io-from-serilog/index.html
new file mode 100644
index 0000000000..bdaed6379e
--- /dev/null
+++ b/logging-to-elmah-io-from-serilog/index.html
@@ -0,0 +1,992 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Serilog
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Serilog
+
+
Serilog is a great addition to the flowering .NET logging community, described as “A no-nonsense logging library for the NoSQL era” on their project page. Serilog works just like other logging frameworks such as log4net and NLog but offers a great fluent API and the concept of sinks (a bit like appenders in log4net). Sinks are superior to appenders because they threat errors as objects rather than strings, a perfect fit for elmah.io which itself is built on NoSQL. Serilog already comes with native support for elmah.io, which makes it easy to integrate with any application using Serilog.
+
Adding this type of logging to your windows and console applications is just as easy. Add the Serilog.Sinks.ElmahIo
NuGet package to your project:
+
Install-Package Serilog.Sinks.ElmahIo
+
dotnet add package Serilog.Sinks.ElmahIo
+
<PackageReference Include="Serilog.Sinks.ElmahIo" Version="5.*" />
+
paket add Serilog.Sinks.ElmahIo
+
+
To configure Serilog, add the following code to the Application_Start method in global.asax.cs:
+
var log =
+ new LoggerConfiguration()
+ .WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID")))
+ .CreateLogger();
+Log.Logger = log;
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the ID of the log you want messages sent to (Where is my log ID? ).
+
First, we create a new LoggerConfiguration and tell it to write to elmah.io. The log object can be used to log errors and you should register this in your IoC container. In this case, we don't use IoC, that is why the log object is set as the public static Logger property, which makes it accessible from everywhere.
+
To log exceptions to elmah.io through Serilog use the Log
class provided by Serilog:
+
try
+{
+ // Do some stuff that may cause an exception
+}
+catch (Exception e)
+{
+ Log.Error(e, "The actual error message");
+}
+
+
The Error method tells Serilog to log the error in the configured sinks, which in our case logs to elmah.io. Simple and beautiful.
+
+Always call Log.CloseAndFlush();
before your program terminates.
+
+
Logging custom properties
+
Serilog supports logging custom properties in three ways: As part of the log message, through enrichers, and using LogContext
. All three types of properties are implemented in the elmah.io sink as part of the Data dictionary to elmah.io.
+
The following example shows how to log all three types of properties:
+
var logger =
+ new LoggerConfiguration()
+ .Enrich.WithProperty("ApplicationIdentifier", "MyCoolApp")
+ .Enrich.FromLogContext()
+ .WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID")))
+ .CreateLogger();
+
+using (LogContext.PushProperty("ThreadId", Thread.CurrentThread.ManagedThreadId))
+{
+ logger.Error("This is a message with {Type} properties", "custom");
+}
+
+
Beneath the Data tab on the logged message details, the ApplicationIdentifier
, ThreadId
, and Type
properties can be found.
+
Serilog.Sinks.ElmahIo
provides a range of reserved property names, that can be used to fill in data in the correct fields on the elmah.io UI. Let's say you want to fill the User field using structured logging only:
+
logger.Information("{Quote} from {User}", "Hasta la vista, baby", "Arnold Schwarzenegger");
+
+
This will fill in the value Arnold Schwarzenegger
in the User
field, as well as add two key/value pairs (Quote and User) to the Data tab on elmah.io. For a reference of all possible property names, check out the property names on CreateMessage .
+
Message hooks
+
Serilog.Sinks.ElmahIo
provides message hooks similar to the integrations with ASP.NET and ASP.NET Core.
+
+Message hooks require Serilog.Sinks.ElmahIo
version 3.3.0
or newer.
+
+
Decorating log messages
+
To include additional information on log messages, you can use the OnMessage event when initializing the elmah.io target:
+
Log.Logger =
+ new LoggerConfiguration()
+ .WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID"))
+ {
+ OnMessage = msg =>
+ {
+ msg.Version = "1.0.0";
+ }
+ })
+ .CreateLogger();
+
+
The example above includes a version number on all errors. Since the elmah.io sink also picks up enrichers specified with Serilog, this example could be implemented by enriching all log messages with a field named version
.
+
Include source code
+
You can use the OnMessage
action to include source code to log messages. This will require a stack trace in the Detail
property with filenames and line numbers in it.
+
There are multiple ways of including source code to log messages. In short, you will need to install the Elmah.Io.Client.Extensions.SourceCode
NuGet package and call the WithSourceCodeFromPdb
method in the OnMessage
action:
+
Log.Logger =
+ new LoggerConfiguration()
+ .WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID"))
+ {
+ OnMessage = msg =>
+ {
+ msg.WithSourceCodeFromPdb();
+ }
+ })
+ .CreateLogger();
+
+
Check out How to include source code in log messages for additional requirements to make source code show up on elmah.io.
+
+Including source code on log messages is available in the Elmah.Io.Client
v4 package and forward.
+
+
Handle errors
+
To handle any errors happening while processing a log message, you can use the OnError event when initializing the elmah.io sink:
+
Log.Logger =
+ new LoggerConfiguration()
+ .WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID"))
+ {
+ OnError = (msg, ex) =>
+ {
+ Console.Error.WriteLine(ex.Message);
+ }
+ })
+ .CreateLogger();
+
+
The example implements a callback if logging to elmah.io fails. How you choose to implement this is entirely up to your application and tech stack.
+
Error filtering
+
To ignore specific messages based on their content, you can use the OnFilter event when initializing the elmah.io sink:
+
Log.Logger =
+ new LoggerConfiguration()
+ .WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID"))
+ {
+ OnFilter = msg =>
+ {
+ return msg.Title.Contains("trace");
+ }
+ })
+ .CreateLogger();
+
+
The example above ignores any log message with the word trace
in the title.
+
ASP.NET Core
+
Serilog provides a package for ASP.NET Core, that routes log messages from inside the framework through Serilog. We recommend using this package together with the elmah.io sink, in order to capture warnings and errors happening inside ASP.NET Core.
+
To use this, install the following packages:
+
Install-Package Serilog.AspNetCore
+Install-Package Serilog.Sinks.ElmahIo
+
dotnet add package Serilog.AspNetCore
+dotnet add package Serilog.Sinks.ElmahIo
+
<PackageReference Include="Serilog.AspNetCore" Version="6.*" />
+<PackageReference Include="Serilog.Sinks.ElmahIo" Version="5.*" />
+
paket add Serilog.AspNetCore
+paket add Serilog.Sinks.ElmahIo
+
+
Configure Serilog using the UseSerilog
method in the Program.cs
file:
+
builder.Host.UseSerilog((ctx, lc) => lc
+ .WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID"))
+ {
+ MinimumLogEventLevel = LogEventLevel.Information,
+ }));
+}
+
+
Now, all warnings, errors, and fatals happening inside ASP.NET Core are logged to elmah.io.
+
A common request is to include all of the HTTP contextual information you usually get logged when using a package like Elmah.Io.AspNetCore
. We have developed a specialized NuGet package to include cookies, server variables, etc. when logging through Serilog from ASP.NET Core. To set it up, install the Elmah.Io.AspNetCore.Serilog
NuGet package:
+
Install-Package Elmah.Io.AspNetCore.Serilog
+
dotnet add package Elmah.Io.AspNetCore.Serilog
+
<PackageReference Include="Elmah.Io.AspNetCore.Serilog" Version="5.*" />
+
paket add Elmah.Io.AspNetCore.Serilog
+
+
Then, call the UseElmahIoSerilog
method in Program.cs
file:
+
// ... Exception handling middleware
+app.UseElmahIoSerilog();
+// ... UseMvc etc.
+
+
The middleware uses Serilog's LogContext
feature to enrich each log message with additional properties. To turn on the log context, extend your Serilog config:
+
builder.Host.UseSerilog((ctx, lc) => lc
+ .WriteTo.ElmahIo(/*...*/)
+ .Enrich.FromLogContext() // <-- add this line
+);
+
+
There's a problem with this approach when an endpoint throws an uncaught exception. Microsoft.Extensions.Logging logs all uncaught exceptions as errors, but the LogContext
is already popped when doing so. The recommended approach is to ignore these errors in the elmah.io sink and install the Elmah.Io.AspNetCore
package to log uncaught errors to elmah.io (as explained in Logging from ASP.NET Core ). The specific error message can be ignored in the sink by providing the following filter during initialization of Serilog:
+
.WriteTo.ElmahIo(/*...*/)
+{
+ // ...
+ OnFilter = msg =>
+ {
+ return
+ msg != null
+ && msg.TitleTemplate != null
+ && msg.TitleTemplate.Equals(
+ "An unhandled exception has occurred while executing the request.",
+ StringComparison.InvariantCultureIgnoreCase);
+ }
+})
+
+
ASP.NET
+
Messages logged through Serilog in an ASP.NET WebForms, MVC, or Web API application can be enriched with a range of HTTP contextual information using the SerilogWeb.Classic
NuGet package. Start by installing the package:
+
Install-Package SerilogWeb.Classic
+
dotnet add package SerilogWeb.Classic
+
<PackageReference Include="SerilogWeb.Classic" Version="5.*" />
+
paket add SerilogWeb.Classic
+
+
The package includes automatic HTTP request and response logging as well as some Serilog enrichers. Unless you are trying to debug a specific problem with your website, we recommend disabling HTTP logging since that will produce a lot of messages (depending on the traffic on your website). HTTP logging can be disabled by including the following code in the Global.asax.cs
file:
+
protected void Application_Start()
+{
+ SerilogWebClassic.Configure(cfg => cfg
+ .Disable()
+ );
+
+ // ...
+}
+
+
To enrich log messages with HTTP contextual information you can configure one or more enrichers in the same place as you configure the elmah.io sink:
+
Log.Logger = new LoggerConfiguration()
+ .WriteTo.ElmahIo(new ElmahIoSinkOptions("API_KEY", new Guid("LOG_ID")))
+ .Enrich.WithHttpRequestClientHostIP()
+ .Enrich.WithHttpRequestRawUrl()
+ .Enrich.WithHttpRequestType()
+ .Enrich.WithHttpRequestUrl()
+ .Enrich.WithHttpRequestUserAgent()
+ .Enrich.WithUserName(anonymousUsername:null)
+ .CreateLogger();
+
+
This will automatically fill in fields on elmah.io like URL, method, client IP, and UserAgent.
+
Check out this full sample for more details.
+
Config using appsettings.json
+
While Serilog provides a great fluent C# API, some prefer to configure Serilog using an appsettings.json
file. To configure the elmah.io sink this way, you will need to install the Serilog.Settings.Configuration
NuGet package. Then configure elmah.io in your appsettings.json
file:
+
{
+ // ...
+ "Serilog":{
+ "Using":[
+ "Serilog.Sinks.ElmahIo"
+ ],
+ "MinimumLevel": "Warning",
+ "WriteTo":[
+ {
+ "Name": "ElmahIo",
+ "Args":{
+ "apiKey": "API_KEY",
+ "logId": "LOG_ID"
+ }
+ }
+ ]
+ }
+}
+
+
+Make sure to specify the apiKey
and logId
arguments with the first character in lowercase.
+
+
Finally, tell Serilog to read the configuration from the appsettings.json
file:
+
var configuration = new ConfigurationBuilder()
+ .AddJsonFile("appsettings.json")
+ .Build();
+
+var logger = new LoggerConfiguration()
+ .ReadFrom.Configuration(configuration)
+ .CreateLogger();
+
+
Extended exception details with Serilog.Exceptions
+
The more information you have on an error, the easier it is to find out what went wrong. Muhammad Rehan Saeed made a nice enrichment package for Serilog named Serilog.Exceptions
. The package uses reflection on a logged exception to log additional information depending on the concrete exception type. You can install the package through NuGet:
+
Install-Package Serilog.Exceptions
+
dotnet add package Serilog.Exceptions
+
<PackageReference Include="Serilog.Exceptions" Version="5.*" />
+
paket add Serilog.Exceptions
+
+
And configure it in C# code:
+
var logger = new LoggerConfiguration()
+ .Enrich.WithExceptionDetails()
+ .WriteTo.ElmahIo(/*...*/)
+ .CreateLogger();
+
+
The elmah.io sink will automatically pick up the additional information and show them in the extended message details overlay. To navigate to this view, click an error on the search view. Then click the button in the upper right corner to open extended message details. The information logged by Serilog.Exceptions
are available beneath the Data tab.
+
Remove sensitive data
+
Structured logging with Serilog is a great way to store a lot of contextual information about a log message. In some cases, it may result in sensitive data being stored in your log, though. We recommend you remove any sensitive data from your log messages before storing them on elmah.io and anywhere else. To implement this, you can use the OnMessage
event as already shown previously in the document:
+
OnMessage = msg =>
+{
+ foreach (var d in msg.Data)
+ {
+ if (d.Key.Equals("Password"))
+ {
+ d.Value = "****";
+ }
+ }
+}
+
+
An alternative to replacing sensitive values manually is to use a custom destructuring package for Serilog. The following example shows how to achieve this using the Destructurama.Attributed
package:
+
Install-Package Destructurama.Attributed
+
dotnet add package Destructurama.Attributed
+
<PackageReference Include="Destructurama.Attributed" Version="2.*" />
+
paket add Destructurama.Attributed
+
+
Set up destructuring from attributes:
+
Log.Logger = new LoggerConfiguration()
+ .Destructure.UsingAttributes()
+ .WriteTo.ElmahIo(/*...*/)
+ .CreateLogger();
+
+
Make sure to decorate any properties including sensitive data with the NotLogged
attribute:
+
public class LoginModel
+{
+ public string Username { get; set; }
+
+ [NotLogged]
+ public string Password { get; set; }
+}
+
+
Setting a category
+
elmah.io provide a field named Category to better group log messages by class name, namespace, or similar. Category maps to Serilog's SourceContext automatically when using Serilog.Sinks.ElmahIo
. To make sure that the category is correctly populated, either use the Forcontext
method:
+
Log.ForContext<Program>().Information("This is an information message with context");
+Log.ForContext("The context").Information("This is another information message with context");
+
+
Or set a SourceContext
or Category
property using LogContext
:
+
using (LogContext.PushProperty("SourceContext", "The context"))
+ Log.Information("This is an information message with context");
+
+using (LogContext.PushProperty("Category", "The context"))
+ Log.Information("This is another information message with context");
+
+
When using Serilog through Microsoft.Extensions.Logging's ILogger<T>
interface, the source context will automatically be set by Serilog.
+
Troubleshooting
+
Here are some things to try out if logging from Serilog to elmah.io doesn't work:
+
+Run the diagnose
command with the elmah.io CLI as shown here: Diagnose potential problems with an elmah.io installation .
+Make sure that you have the newest Serilog.Sinks.ElmahIo
and Elmah.Io.Client
packages installed.
+Make sure to include all of the configuration from the example above.
+Make sure that the API key is valid and allow the Messages | Write permission .
+Make sure to include a valid log ID.
+Make sure that you have sufficient log messages in your subscription and that you didn't disable logging to the log or include any ignore filters/rules.
+Always make sure to call Log.CloseAndFlush()
before exiting the application to make sure that all log messages are flushed.
+Set up Serilog's SelfLog to inspect any errors happening inside Serilog or the elmah.io sink: Serilog.Debugging.SelfLog.Enable(msg => Debug.WriteLine(msg));
.
+Implement the OnError
action and put a breakpoint in the handler to inspect if any errors are thrown while logging to the elmah.io API.
+
+
PeriodicBatchingSink is marked as sealed
+
If you get a runtime error stating that the PeriodicBatchingSink
class is sealed and cannot be extended, make sure to manually install version 3.1.0
or newer of the Serilog.Sinks.PeriodicBatching
NuGet package. The bug has also been fixed in the Serilog.Sinks.ElmahIo
NuGet package from version 4.3.29
and forward.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-servicestack/index.html b/logging-to-elmah-io-from-servicestack/index.html
new file mode 100644
index 0000000000..d284a29e74
--- /dev/null
+++ b/logging-to-elmah-io-from-servicestack/index.html
@@ -0,0 +1,671 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from ServiceStack
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from ServiceStack
+
Logging errors to elmah.io from ServiceStack is almost as easy as installing in MVC and Web API. The folks over at ServiceStack provide you with a NuGet package named ServiceStack.Logging.Elmah. Like Web API you need to tell ServiceStack to use ELMAH as the logging framework for errors, besides adding the standard ELMAH configuration in web.config. Start by installing both ServiceStack.Logging.Elmah
and Elmah.Io
into your ServiceStack web project:
+
Install-Package ServiceStack.Logging.Elmah
+Install-Package Elmah.Io
+
dotnet add package ServiceStack.Logging.Elmah
+dotnet add package Elmah.Io
+
<PackageReference Include="ServiceStack.Logging.Elmah" Version="5.*" />
+<PackageReference Include="Elmah.Io" Version="5.*" />
+
paket add ServiceStack.Logging.Elmah
+paket add Elmah.Io
+
+
During the installation, you will be asked for your API key (Where is my API key? ) and log ID (Where is my log ID? ).
+
Once installed, add the following line to your AppHost:
+
LogManager.LogFactory = new ElmahLogFactory(new NLogFactory());
+
+
The above example assumes that you are already using NLog as the existing framework for logging. Wrapping different logger factories in each other and makes it possible to log errors through ELMAH and other types of messages like warnings and info messages through another logging framework. If you don’t need anything other than ELMAH logging, use the NullLogFactory instead of NLogFactory.
+
That’s it! By installing both the ServiceStack.Logging.Elmah and elmah.io packages, you should have sufficient configuration in your web.config to start logging errors.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-signalr/index.html b/logging-to-elmah-io-from-signalr/index.html
new file mode 100644
index 0000000000..cffde379de
--- /dev/null
+++ b/logging-to-elmah-io-from-signalr/index.html
@@ -0,0 +1,682 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from SignalR
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from SignalR
+
Logging from SignalR is supported through our Elmah.Io.Extensions.Logging
package. For details not included in this article, check out Logging from Microsoft.Extensions.Logging .
+
Start by installing the Elmah.Io.Extensions.Logging package:
+
Install-Package Elmah.Io.Extensions.Logging
+
dotnet add package Elmah.Io.Extensions.Logging
+
<PackageReference Include="Elmah.Io.Extensions.Logging" Version="5.*" />
+
paket add Elmah.Io.Extensions.Logging
+
+
In the Program.cs
file, add a new using
statement:
+
using Elmah.Io.Extensions.Logging;
+
+
Then configure elmah.io like shown here:
+
builder.Logging.AddElmahIo(options =>
+{
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+});
+builder.Logging.AddFilter<ElmahIoLoggerProvider>(null, LogLevel.Warning);
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
(Where is my log ID? ) with the log Id of the log you want to log to.
+
The code only logs Warning
, Error
, and Fatal
messages. To change that you can change the LogLevel
filter specified in the line calling the AddFilter
method. You may also need to change log levels for SignalR itself:
+
logging.AddFilter("Microsoft.AspNetCore.SignalR", LogLevel.Debug);
+logging.AddFilter("Microsoft.AspNetCore.Http.Connections", LogLevel.Debug);
+
+
Be aware that changing log levels to Debug
or lower will cause a lot of messages to be stored in elmah.io. Log levels can be specified through the appsettings.json
file as with any other ASP.NET Core application. Check out appsettings.json configuration for more details.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-sitefinity/index.html b/logging-to-elmah-io-from-sitefinity/index.html
new file mode 100644
index 0000000000..a1a062b543
--- /dev/null
+++ b/logging-to-elmah-io-from-sitefinity/index.html
@@ -0,0 +1,671 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Sitefinity
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Sitefinity
+
Sitefinity is a CMS from Telerik, implemented on top of ASP.NET. Like other content management systems build on top of ASP.NET, ELMAH is supported out of the box.
+
To install elmah.io in a Sitefinity website, start by opening the website in Visual Studio by selecting File | Open Web Site... and navigate to the Sitefinity projects folder (something similar to this: C:\Program Files (x86)\Telerik\Sitefinity\Projects\Default
).
+
Right-click the website and install the Elmah.Io
NuGet package:
+
Install-Package Elmah.Io
+
dotnet add package Elmah.Io
+
<PackageReference Include="Elmah.Io" Version="5.*" />
+
+
During installation, you will be prompted for your API key (Where is my API key? ) and log ID (Where is my log ID? ).
+
That's it! Uncaught errors in Sitefinity are logged to your elmah.io log. To test that the integration works, right-click the website and add a new Web Form named ELMAH.aspx. In the code-behind file add the following code:
+
protected void Page_Load(object sender, EventArgs e)
+{
+ throw new ApplicationException();
+}
+
+
Start the website and navigate to the ELMAH.aspx page. If everything works as intended, you will see the yellow screen of death, and a new error will pop up on elmah.io.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-sveltekit/index.html b/logging-to-elmah-io-from-sveltekit/index.html
new file mode 100644
index 0000000000..30cd5fe0a2
--- /dev/null
+++ b/logging-to-elmah-io-from-sveltekit/index.html
@@ -0,0 +1,676 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from SvelteKit
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from SvelteKit
+
To log all errors from a SvelteKit application, install the elmah.io.javascript
npm package as described in Logging from JavaScript . Then add the following code to the hooks.client.js
file. If the file does not exist, make sure to create it in the src
folder since SvelteKit will automatically load it from there.
+
import Elmahio from 'elmah.io.javascript';
+var logger = new Elmahio({
+ apiKey: 'API_KEY',
+ logId: 'LOG_ID'
+});
+
+/** @type {import('@sveltejs/kit').HandleClientError} */
+export function handleError({ error, event }) {
+ if (error && error.message) {
+ logger.error(error.message, error);
+ } else {
+ logger.error('Error in application', error);
+ }
+}
+
+
When launching your SvelteKit app, elmah.io is configured and all errors happening in the application are logged. For now, elmah.io.javascript
only supports SvelteKit apps running inside the browser, why implementing the HandleServerError
is not supported.
+
Check out the elmahio-sveltekit sample for some real working code.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-system-diagnostics/index.html b/logging-to-elmah-io-from-system-diagnostics/index.html
new file mode 100644
index 0000000000..b730ab115a
--- /dev/null
+++ b/logging-to-elmah-io-from-system-diagnostics/index.html
@@ -0,0 +1,682 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from System.Diagnostics
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from System.Diagnostics
+
+Logging through System.Diagnostics
have been deprecated. Please use the Elmah.Io.Client
package to log trace messages to elmah.io.
+
+
.NET comes with its own tracing/logging feature located in the System.Diagnostics
namespaces. A core part of System.Diagnostics
is the Trace
class, but that namespace contains utilities for performance counters, working with the event log, and a lot of other features. In this article, we will focus on logging to elmah.io from System.Diagnostics.Trace
.
+
To start logging, install the Elmah.Io.Trace
package:
+
Install-Package Elmah.Io.Trace
+
dotnet add package Elmah.Io.Trace
+
<PackageReference Include="Elmah.Io.Trace" Version="3.*" />
+
paket add Elmah.Io.Trace
+
+
As default, Trace
logs to the Win32 OutputDebugString function, but it is possible to log to multiple targets (like appenders in log4net). To do so, tell Trace
about elmah.io:
+
System.Diagnostics.Trace.Listeners.Add(
+ new ElmahIoTraceListener("API_KEY", new Guid("LOG_ID")));
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with your log id (Where is my log ID? ).
+
To start logging, call the Trace
API:
+
try
+{
+ System.Diagnostics.Trace.Write("Starting something dangerous");
+ // ...
+}
+catch (Exception e)
+{
+ System.Diagnostics.Trace.Fail(e.Message, e.ToString());
+}
+
+
In the example, we write an information message with the message Starting something dangerous
and log any thrown exception to elmah.io.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-umbraco/index.html b/logging-to-elmah-io-from-umbraco/index.html
new file mode 100644
index 0000000000..f1591fe523
--- /dev/null
+++ b/logging-to-elmah-io-from-umbraco/index.html
@@ -0,0 +1,791 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Umbraco
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Umbraco
+
+
elmah.io offer great support for all newer Umbraco versions. Umbraco has been in rapid development in the last few years, so the installation instructions are very different depending on which major version you are using. Make sure to select the right version below since newer versions of the Elmah.Io.Umbraco
package don't work with older versions of Umbraco and vice versa.
+
+
+
+
To learn more about the elmah.io integration with Umbraco and an overall introduction to the included features, make sure to check out the
elmah.io and Umbraco page.
+
+
+
+
During the installation steps described below, you will need your API key (Where is my API key? ) and log ID (Where is my log ID? ).
+
elmah.io integrates with Umbraco's Health Checks feature too. To learn more about how to set it up, visit Logging heartbeats from Umbraco .
+
Umbraco >= 9
+
+Umbraco 9 is targeting .NET 5.0 which is no longer supported by Microsoft. This is why we have chosen to support Umbraco 10 and up only.
+
+
To install elmah.io in your Umbraco >= v10 site, install the Elmah.Io.Umbraco
NuGet package:
+
Install-Package Elmah.Io.Umbraco
+
dotnet add package Elmah.Io.Umbraco
+
<PackageReference Include="Elmah.Io.Umbraco" Version="5.*" />
+
paket add Elmah.Io.Umbraco
+
+
After installing the NuGet package add the following to the Startup.cs
file:
+
public class Startup
+{
+ // ...
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+ services.AddElmahIo(options =>
+ {
+ options.ApiKey = "API_KEY";
+ options.LogId = new Guid("LOG_ID");
+ });
+ // ...
+ }
+
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ // ...
+ app.UseElmahIo();
+ // ...
+ }
+}
+
+
+Make sure to call the UseElmahIo
-method after installation of other pieces of middleware handling exceptions and auth (like UseDeveloperExceptionPage
, UseExceptionHandler
, UseAuthentication
, and UseAuthorization
), but before the call to UseUmbraco
.
+
+
This will log all uncaught errors to elmah.io. If you want to hook into Umbraco's logging through Serilog, extend the configuration in the appsettings.json
file with the following JSON:
+
{
+ "Serilog": {
+ ...
+ "WriteTo": [
+ {
+ "Name": "ElmahIo",
+ "Args": {
+ "apiKey": "API_KEY",
+ "logId": "LOG_ID"
+ }
+ }
+ ]
+ },
+ ...
+}
+
+
This will configure elmah.io's Serilog sink in Umbraco. You may experience logging not coming through when running locally. In this case, it might help to remove the WriteTo
action from the appsettings.Development.json
file.
+
Umbraco 8
+
To install elmah.io in your Umbraco v8 site, install the Elmah.Io.Umbraco
v4 package:
+
Install-Package Elmah.Io.Umbraco -Version 4.2.21
+
dotnet add package Elmah.Io.Umbraco --version 4.2.21
+
<PackageReference Include="Elmah.Io.Umbraco" Version="4.2.21" />
+
paket add Elmah.Io.Umbraco --version 4.2.21
+
+
During the installation, you will be presented with a dialog asking for your API key and log ID. Hit F5 and watch messages start flowing into elmah.io.
+
+Unless serious security issues in the Elmah.Io.Umbraco
v4 package are found, new features will be added to the v5 package only (supporting Umbraco 10 and newer).
+
+
Configuration
+
If you are running on the default Umbraco template, all necessary configuration is added during the installation of the Elmah.Io.Umbraco
NuGet package. If your web.config
file for some reason isn't updated during installation, you can configure elmah.io manually: Configure elmah.io manually . Likewise, the installer configures the elmah.io sink for Serilog in your config\serilog.user.config
file.
+
Different environments
+
You may have different environments like Staging and Production . At least you have two: Localhost and Production . If you want to log to different error logs depending on the current environment, check out Use multiple logs for different environments . Web.config transformations work on the Web.config
file only but you may have other config files that need transformation as well. In terms of elmah.io, the serilog.user.config
file also includes elmah.io configuration that you may want to disable on localhost and include on production. If you are running on Umbraco Cloud this is natively supported as explained here: Config Transforms . Even in self-hosted environments, you can achieve something similar using the SlowCheetah extension. Check out this question on Our for details: Deploying different umbracoSettings.config for different environments .
+
Umbraco 7
+
We still support Umbraco 7 through the Elmah.Io.Umbraco
package version 3.2.35
:
+
Install-Package Elmah.Io.Umbraco -Version 3.2.35
+
dotnet add package Elmah.Io.Umbraco --version 3.2.35
+
<PackageReference Include="Elmah.Io.Umbraco" Version="3.2.35" />
+
paket add Elmah.Io.Umbraco --version 3.2.35
+
+
+New features will be added to the updated package for Umbraco 10 and newer only.
+
+
Umbraco Cloud
+
When using Umbraco Cloud, you may not have a local clone of the source code. To install elmah.io on Umbraco Cloud, follow these steps:
+
+
+Clone your Umbraco Cloud project to a local folder as explained here: Working with a Local Clone .
+
+
+Where you need to install and configure the Elmah.Io.Umbraco
package depends on the Umbraco major version you are running on Umbraco Cloud. For Umbraco 7-8, all changes should be made in the *.Web
project only and all commits from within that folder as well. Don't commit and push anything in the root folder. For Umbraco versions above 8, all changes should be made in the src\UmbracoProject
folder.
+
+
+Follow the installation steps for your Umbraco version as specified in the beginning of this document.
+
+
+Commit and push all changes to the git repository. This will add elmah.io logging to your remote Umbraco Cloud project.
+
+
+
In case you want logging to different elmah.io logs from each Umbraco Cloud environment, please check out Umbraco's support for config transformations here: Config transforms .
+
Umbraco Uno
+
+Umbraco Uno has been discontinued.
+
+
Installing elmah.io in Umbraco Uno follows the process of installing it onto Umbraco Cloud. To modify code and configuration in Uno you will need a Umbraco Uno Standard plan or higher. Also, you need to enable Custom Code to clone the code locally. This can be done from Uno by clicking the Enable custom code button:
+
+
After enabling Custom Code you can create a Development environment and follow the steps in the Umbraco Cloud documentation.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-uno/index.html b/logging-to-elmah-io-from-uno/index.html
new file mode 100644
index 0000000000..ce75b38baa
--- /dev/null
+++ b/logging-to-elmah-io-from-uno/index.html
@@ -0,0 +1,684 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Uno
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Uno
+
+The Uno integration for elmah.io is currently in prerelease.
+
+
Integrating Uno with elmah.io is done by installing the Elmah.Io.Uno
NuGet package:
+
Install-Package Elmah.Io.Uno -IncludePrerelease
+
dotnet add package Elmah.Io.Uno --prerelease
+
<PackageReference Include="Elmah.Io.Uno" Version="4.0.19-pre" />
+
+
While configured in the shared project, the NuGet package will need to be installed in all platform projects.
+
Elmah.Io.Uno
comes with a logger for Microsoft.Extensions.Logging. To configure the logger, open the App.xaml.cs
file and locate the InitializeLogging
method. Here you will see the logging configuration for your application. Include logging to elmah.io by calling the AddElmahIo
method:
+
var factory = LoggerFactory.Create(builder =>
+{
+ // ...
+ builder.AddElmahIo("API_KEY", new Guid("LOG_ID"));
+ // ...
+});
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
(Where is my log ID? ) with the log Id of the log you want to log to.
+
elmah.io will now automatically log all warning, error, and fatal messages to elmah.io. Uno log messages internally, but you can also do manual logging like this:
+
this.Log().LogWarning("Oh no");
+
+
Logging with Uno's log helper will require you to add the following usings:
+
using Microsoft.Extensions.Logging;
+using Uno.Extensions;
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-vue/index.html b/logging-to-elmah-io-from-vue/index.html
new file mode 100644
index 0000000000..66109c4df3
--- /dev/null
+++ b/logging-to-elmah-io-from-vue/index.html
@@ -0,0 +1,675 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Vue
+
To log all errors from a Vue.js application, install the elmah.io.javascript
npm package as described in Logging from JavaScript or include it with a direct <script>
include:
+
<script src="https://cdn.jsdelivr.net/gh/elmahio/elmah.io.javascript@latest/dist/elmahio.min.js" type="text/javascript"></script>
+
+
Before initializing the application, include the following code:
+
var logger = new Elmahio({
+ apiKey: "API_KEY",
+ logId: "LOG_ID"
+});
+Vue.config.errorHandler = function (err, vm, info) {
+ logger.error(err.message, err);
+};
+Vue.config.warnHandler = function (msg, vm, trace) {
+ logger.warning(msg);
+};
+
+
elmah.io.javascript
will automatically log all errors raised through window.onerror
and log additional errors and warnings from Vue.js through the errorHandler
and warnHandler
functions. If you want to exclude warnings, simply remove the warnHandler
function.
+
Check out the Elmah.Io.JavaScript.VueJs sample for some real working code.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-wcf/index.html b/logging-to-elmah-io-from-wcf/index.html
new file mode 100644
index 0000000000..3a8cf96ee1
--- /dev/null
+++ b/logging-to-elmah-io-from-wcf/index.html
@@ -0,0 +1,720 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from WCF
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from WCF
+
ELMAH (the open-source project) and WCF aren't exactly known to go hand in hand. But, with a bit of custom code, logging exceptions from WCF to elmah.io is possible.
+
Let's get started. Install elmah.io into your WCF project using NuGet:
+
Install-Package Elmah.Io
+
dotnet add package Elmah.Io
+
<PackageReference Include="Elmah.Io" Version="5.*" />
+
+
During the installation, you will be asked for your API key (Where is my API key? ) and log ID (Where is my log ID? ).
+
Add a new class named HttpErrorHandler
:
+
public class HttpErrorHandler : IErrorHandler
+{
+ public bool HandleError(Exception error)
+ {
+ return false;
+ }
+
+ public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
+ {
+ if (error != null)
+ {
+ Elmah.ErrorSignal.FromCurrentContext().Raise(error);
+ }
+ }
+}
+
+
This is an implementation of WCF's IErrorHandler
that instructs WCF to log any errors to ELMAH, using the Raise
-method on ErrorSignal
.
+
Then create an attribute named ServiceErrorBehaviourAttribute
:
+
public class ServiceErrorBehaviourAttribute : Attribute, IServiceBehavior
+{
+ Type errorHandlerType;
+
+ public ServiceErrorBehaviourAttribute(Type errorHandlerType)
+ {
+ this.errorHandlerType = errorHandlerType;
+ }
+
+ public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
+ {
+ }
+
+ public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
+ BindingParameterCollection bindingParameters)
+ {
+ }
+
+ public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
+ {
+ IErrorHandler errorHandler;
+ errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
+ foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
+ {
+ ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
+ channelDispatcher.ErrorHandlers.Add(errorHandler);
+ }
+ }
+}
+
+
We'll use the ServiceErrorBehaviourAttribute
class for decorating endpoints which we want logging uncaught errors to ELMAH. Add the new attribute to your service implementation like this:
+
[ServiceErrorBehaviour(typeof(HttpErrorHandler))]
+public class Service1 : IService1
+{
+ // ...
+}
+
+
That's it. Services decorated with the ServiceErrorBehaviourAttribute
now logs exceptions to ELMAH.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-web-api/index.html b/logging-to-elmah-io-from-web-api/index.html
new file mode 100644
index 0000000000..f377332ded
--- /dev/null
+++ b/logging-to-elmah-io-from-web-api/index.html
@@ -0,0 +1,716 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from ASP.NET Web API
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Web API
+
Web API provides its own mechanism for handling errors, why ELMAH’s modules and handlers don't work there. Luckily, Richard Dingwall created the Elmah.Contrib.WebApi NuGet package to fix this. We've built a package for ASP.NET Web API exclusively, which installs all the necessary packages.
+
To start logging exceptions from Web API, install the Elmah.Io.WebApi
NuGet package:
+
Install-Package Elmah.Io.WebApi
+
dotnet add package Elmah.Io.WebApi
+
<PackageReference Include="Elmah.Io.WebApi" Version="5.*" />
+
paket add Elmah.Io.WebApi
+
+
During the installation, you will be asked for your API key (Where is my API key? ) and log ID (Where is my log ID? ).
+
+
+
+
+
Add the following code to your WebApiConfig.cs
file:
+
public static class WebApiConfig
+{
+ public static void Register(HttpConfiguration config)
+ {
+ // ...
+ config.Services.Add(typeof(IExceptionLogger), new ElmahExceptionLogger());
+ // ...
+ }
+}
+
+
The registered IExceptionLogger
intercepts all thrown exceptions, even errors in controller constructors and routing errors.
+
+
+
Add the following code to your Global.asax.cs
file:
+
protected void Application_Start()
+{
+ // ...
+ GlobalConfiguration.Configuration.Filters.Add(new ElmahHandleErrorApiAttribute());
+ // ...
+}
+
+
In this case you register a new global filter with Web API. The downside of this approach is, that only errors thrown in controller actions are logged.
+
+
+
All uncaught exceptions in ASP.NET Web API are now logged to elmah.io
+
Logging from exception/action filters
+
It's a widely used Web API approach, to handle all exceptions in a global exception/action filter and return a nicely formatted JSON/XML error response to the client. This is a nice approach to avoid throwing internal server errors, but it also puts ELMAH out of the game. When catching any exception manually and converting it to a response message, errors won't be logged in elmah.io.
+
To overcome this, errors should be logged manually from your global exception/action filter:
+
public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute
+{
+ public override void OnException(HttpActionExecutedContext context)
+ {
+ ErrorSignal.FromCurrentContext().Raise(context.Exception);
+
+ // Now generate the result to the client
+ }
+}
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-web-pages/index.html b/logging-to-elmah-io-from-web-pages/index.html
new file mode 100644
index 0000000000..2fbc27d546
--- /dev/null
+++ b/logging-to-elmah-io-from-web-pages/index.html
@@ -0,0 +1,660 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Web Pages
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Web Pages
+
Logging from ASP.NET Web Pages uses our integration with ELMAH (the open-source project). This means that the installation is identical to installing elmah.io in ASP.NET Web Forms.
+
For a full guide to installing the Elmah.Io
NuGet package in your Web Pages project go to: Logging to elmah.io from ASP.NET / WebForms .
+
If the dialog requesting you to input an API key and a Log ID is not shown during the installation of the NuGet package, ELMAH configuration needs to be checked manually as explained here: Configure elmah.io manually .
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-winforms/index.html b/logging-to-elmah-io-from-winforms/index.html
new file mode 100644
index 0000000000..45e64e52cf
--- /dev/null
+++ b/logging-to-elmah-io-from-winforms/index.html
@@ -0,0 +1,705 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Windows Forms
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
elmah.io logging can be easily added to Windows Forms applications. We don't provide a package specific for WinForms, but the Elmah.Io.Client
package, combined with a bit of code, will achieve just the same.
+
To start logging to elmah.io, install the Elmah.Io.Client
NuGet package:
+
Install-Package Elmah.Io.Client
+
dotnet add package Elmah.Io.Client
+
<PackageReference Include="Elmah.Io.Client" Version="5.*" />
+
paket add Elmah.Io.Client
+
+
Add the following usings to the Program.cs
file:
+
using Elmah.Io.Client;
+using System.Security.Principal;
+using System.Threading;
+
+
Add an event handler to the ThreadException
event in the Main
method:
+
Application.ThreadException += Application_ThreadException;
+
+
Finally, add the Application_ThreadException
method:
+
static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
+{
+ var logger = ElmahioAPI.Create("API_KEY");
+ var exception = e.Exception;
+ var baseException = exception.GetBaseException();
+ logger.Messages.Create("LOG_ID", new CreateMessage
+ {
+ DateTime = DateTime.UtcNow,
+ Detail = exception?.ToString(),
+ Type = baseException?.GetType().FullName,
+ Title = baseException?.Message ?? "An error occurred",
+ Data = exception.ToDataList(),
+ Severity = "Error",
+ Source = baseException?.Source,
+ User = WindowsIdentity.GetCurrent().Name,
+ });
+
+ Application.Exit();
+}
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the id of the log (Where is my log ID? ) where you want errors logged.
+
This example closes the application when an error occurs. If you only want to log the error, make sure to re-use the logger
object:
+
private static IElmahioAPI logger;
+
+static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
+{
+ if (logger == null)
+ {
+ logger = ElmahioAPI.Create("API_KEY");
+ }
+
+ // ...
+}
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-wpf/index.html b/logging-to-elmah-io-from-wpf/index.html
new file mode 100644
index 0000000000..0a92e21f55
--- /dev/null
+++ b/logging-to-elmah-io-from-wpf/index.html
@@ -0,0 +1,793 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from WPF
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from WPF
+
+
elmah.io logging can be easily added to WPF applications. To start logging to elmah.io, install the Elmah.Io.Wpf
NuGet package:
+
Install-Package Elmah.Io.Wpf
+
dotnet add package Elmah.Io.Wpf
+
<PackageReference Include="Elmah.Io.Wpf" Version="5.*" />
+
+
Next, initialize elmah.io in the App.xaml.cs
file:
+
public partial class App : Application
+{
+ public App()
+ {
+ ElmahIoWpf.Init(new ElmahIoWpfOptions
+ {
+ ApiKey = "API_KEY",
+ LogId = new Guid("LOG_ID")
+ });
+ }
+}
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the id of the log (Where is my log ID? ) where you want errors logged.
+
+Remember to generate a new API key with messages_write
permission only. This makes it easy to revoke the API key if someone starts sending messages to your log with your key.
+
+
That's it. All uncaught exceptions are now logged to elmah.io.
+
Logging exceptions manually
+
Once initialized using the Init
call, exceptions can be logged manually:
+
ElmahIoWpf.Log(new Exception());
+
+
Breadcrumbs
+
The Elmah.Io.Wpf
package automatically records breadcrumbs when clicking buttons and opening/closing windows. To manually include a breadcrumb you can include the following code:
+
ElmahIoWpf.AddBreadcrumb(new Client.Breadcrumb(DateTime.UtcNow, severity:"Information", action:"Save", message:"Record save"));
+
+
severity
can be set to Verbose
, Debug
, Information
, Warning
, Error
, or Fatal
. The value of action
is a string of your choice. If using one of the following values, the action will get a special icon in the elmah.io UI: click
, submit
, navigation
, request
, error
, warning
, fatal
. The message
field can be used to describe the breadcrumb in more detail and/or include IDs or similar related to the breadcrumb.
+
The number of breadcrumbs to store in memory is 10 as a default. If you want to lower or increase this number, set the MaximumBreadcrumbs
property during initialization:
+
ElmahIoWpf.Init(new ElmahIoWpfOptions
+{
+ // ...
+ MaximumBreadcrumbs = 20,
+});
+
+
Additional options
+
Setting application name
+
The application name can be set on all logged messages by setting the Application
property on ElmahIoWpfOptions
during initialization:
+
ElmahIoWpf.Init(new ElmahIoWpfOptions
+{
+ // ...
+ Application = "WPF on .NET 6",
+});
+
+
Hooks
+
The ElmahIoWpfOptions
class also supports a range of actions to hook into various stages of logging errors. Hooks are registered as actions when installing Elmah.Io.Wpf
:
+
ElmahIoWpf.Init(new ElmahIoWpfOptions
+{
+ // ...
+ OnFilter = msg =>
+ {
+ return msg.Type.Equals("System.NullReferenceException");
+ },
+ OnMessage = msg =>
+ {
+ msg.Version = "42";
+ },
+ OnError = (msg, ex) =>
+ {
+ // Log somewhere else
+ }
+});
+
+
The OnFilter
action can be used to ignore/filter specific errors. In this example, all errors of type System.NullReferenceException
is ignored. The OnMessage
action can be used to decorate/enrich all errors with different information. In this example, all errors get a version number of 42
. The OnError
action can be used to handle if the elmah.io API is down. While this doesn't happen frequently, you might want to log errors elsewhere.
+
Legacy
+
Before the Elmah.Io.Wpf
package was developed, this was the recommended way of installing elmah.io in WPF.
+
To start logging to elmah.io, install the Elmah.Io.Client
NuGet package:
+
Install-Package Elmah.Io.Client
+
dotnet add package Elmah.Io.Client
+
<PackageReference Include="Elmah.Io.Client" Version="5.*" />
+
paket add Elmah.Io.Client
+
+
Add the following usings to the App.xaml.cs
file:
+
using Elmah.Io.Client;
+using System.Diagnostics;
+using System.Security.Principal;
+using System.Threading.Tasks;
+
+
Add the following code:
+
private IElmahioAPI logger;
+
+public App()
+{
+ logger = ElmahioAPI.Create("API_KEY");
+
+ AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
+ LogException(args.ExceptionObject as Exception);
+
+ TaskScheduler.UnobservedTaskException += (sender, args) =>
+ LogException(args.Exception);
+
+ Dispatcher.UnhandledException += (sender, args) =>
+ {
+ if (!Debugger.IsAttached)
+ LogException(args.Exception);
+ };
+}
+
+private void LogException(Exception exception)
+{
+ var baseException = exception.GetBaseException();
+ logger.Messages.Create("LOG_ID", new CreateMessage
+ {
+ DateTime = DateTime.UtcNow,
+ Detail = exception?.ToString(),
+ Type = baseException?.GetType().FullName,
+ Title = baseException?.Message ?? "An error occurred",
+ Data = exception.ToDataList(),
+ Severity = "Error",
+ Source = baseException?.Source,
+ User = WindowsIdentity.GetCurrent().Name,
+ });
+}
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with the id of the log (Where is my log ID? ) where you want errors logged.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-elmah-io-from-xamarin/index.html b/logging-to-elmah-io-from-xamarin/index.html
new file mode 100644
index 0000000000..037d0cdf44
--- /dev/null
+++ b/logging-to-elmah-io-from-xamarin/index.html
@@ -0,0 +1,972 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to elmah.io from Xamarin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to elmah.io from Xamarin
+
+
+The Xamarin integration for elmah.io is currently in prerelease.
+
+
Integrating Xamarin with elmah.io is done by installing the Elmah.Io.Xamarin
NuGet package:
+
Install-Package Elmah.Io.Xamarin -IncludePrerelease
+
dotnet add package Elmah.Io.Xamarin --prerelease
+
<PackageReference Include="Elmah.Io.Xamarin" Version="4.0.30-pre" />
+
paket add Elmah.Io.Xamarin
+
+
For each platform (Android and iOS) you will need to set up elmah.io as illustrated in the following sections. The code is the same for both Xamarin and Xamarin.Forms.
+
+
+
+
+
Open the MainActivity.cs
file and add the following using
statements:
+
using System;
+using Elmah.Io.Xamarin;
+
+
Locate the OnCreate
method and add the following code before all other lines:
+
ElmahIoXamarin.Init(new ElmahIoXamarinOptions
+{
+ ApiKey = "API_KEY",
+ LogId = new Guid("LOG_ID"),
+});
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
(Where is my log ID? ) with the log Id of the log you want to log to.
+
Calling the Init
method will initialize elmah.io. For more configuration options see the Additional configuration section.
+
+
+
Open the Main.cs
file and add the following using
statements:
+
using System;
+using Elmah.Io.Xamarin;
+
+
Locate the Main
method and add the following code after the call to UIApplication.Main
:
+
ElmahIoXamarin.Init(new ElmahIoXamarinOptions
+{
+ ApiKey = "API_KEY",
+ LogId = new Guid("LOG_ID"),
+});
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
(Where is my log ID? ) with the log Id of the log you want to log to.
+
Calling the Init
method will initialize elmah.io. For more configuration options see the Additional configuration section.
+
+
+
Log exceptions manually
+
Once the ElmahIoXamarin.Init
method has been configured during initialization of the app, any exception can be logged manually using the Log
methods available in the Elmah.Io.Xamarin
namespace:
+
try
+{
+ // Code that may break
+}
+catch (Exception e)
+{
+ // Log the exception with the Log extension method:
+
+ e.Log();
+
+ // or use the Log method on ElmahIoXamarin:
+
+ ElmahIoXamarin.Log(e);
+}
+
+
Breadcrumbs
+
Breadcrumbs can be a great help when needing to figure out how a user ended up with an error. To log breadcrumbs, you can use the AddBreadcrumb
method on ElmahIoXamarin
. The following is a sample for Android which will log breadcrumbs on interesting events:
+
public class MainActivity : AppCompatActivity, BottomNavigationView.IOnNavigationItemSelectedListener
+{
+ public override void OnBackPressed()
+ {
+ ElmahIoXamarin.AddBreadcrumb("OnBackPressed", DateTime.UtcNow, action: "Navigation");
+ base.OnBackPressed();
+ }
+
+ protected override void OnPause()
+ {
+ ElmahIoXamarin.AddBreadcrumb("OnPause", DateTime.UtcNow);
+ base.OnPause();
+ }
+
+ // ...
+
+ public bool OnNavigationItemSelected(IMenuItem item)
+ {
+ switch (item.ItemId)
+ {
+ case Resource.Id.navigation_home:
+ ElmahIoXamarin.AddBreadcrumb("Navigate to Home", DateTime.UtcNow, action: "Navigation");
+ textMessage.SetText(Resource.String.title_home);
+ return true;
+ case Resource.Id.navigation_dashboard:
+ ElmahIoXamarin.AddBreadcrumb("Navigate to Dashboard", DateTime.UtcNow, action: "Navigation");
+ textMessage.SetText(Resource.String.title_dashboard);
+ return true;
+ case Resource.Id.navigation_notifications:
+ ElmahIoXamarin.AddBreadcrumb("Navigate to Notifications", DateTime.UtcNow, action: "Navigation");
+ textMessage.SetText(Resource.String.title_notifications);
+ return true;
+ }
+ return false;
+ }
+}
+
+
Additional configuration
+
Besides the mandatory properties ApiKey
and LogId
the ElmahIoXamarinOptions
provide a range of other configuration parameters.
+
Application
+
The elmah.io integration for Xamarin will automatically use the package name for the Application
field. To override this you can set the application name in settings:
+
ElmahIoXamarin.Init(new ElmahIoXamarinOptions
+{
+ // ...
+ Application = "MyApp"
+});
+
+
Version
+
The elmah.io integration for Xamarin will automatically use the package version for the Version
field. To override this you can set the version string in settings:
+
ElmahIoXamarin.Init(new ElmahIoXamarinOptions
+{
+ // ...
+ Version = "1.0.2"
+});
+
+
Decorating all messages
+
All log messages logged through this integration can be decorated with the OnMessage
action:
+
ElmahIoXamarin.Init(new ElmahIoXamarinOptions
+{
+ // ...
+ OnMessage = msg =>
+ {
+ msg.Source = "Custom source";
+ }
+});
+
+
Filtering log messages
+
Log messages can be filtered directly on the device to avoid specific log messages from being sent to elmah.io with the OnFilter
function:
+
ElmahIoXamarin.Init(new ElmahIoXamarinOptions
+{
+ // ...
+ OnFilter = msg => msg.Title.Contains("foo")
+});
+
+
This code will automatically ignore all log messages with the text foo
in the title.
+
Handling errors
+
You may want to handle the scenario where the device cannot communicate with the elmah.io API. You can use the OnError
action:
+
ElmahIoXamarin.Init(new ElmahIoXamarinOptions
+{
+ // ...
+ OnError = (msg, ex) =>
+ {
+ // Do something with ex
+ }
+});
+
+
Legacy integration
+
If you prefer you can configure elmah.io manually without the use of the Elmah.Io.Xamarin
package. This is not the recommended way to integrate with elmah.io from Xamarin and this approach will be discontinued.
+
Start by installing the Elmah.Io.Client
NuGet package:
+
Install-Package Elmah.Io.Client
+
dotnet add package Elmah.Io.Client
+
<PackageReference Include="Elmah.Io.Client" Version="5.*" />
+
paket add Elmah.Io.Client
+
+
If you are targeting a single platform, you can install the package directly in the startup project. If you are targeting multiple platforms, you can either install the package in all platform-specific projects or a shared project.
+
Additional steps will vary from platform to platform.
+
+
+
+
+
Locate your main activity class and look for the OnCreate
method. Here, you'd want to set up event handlers for when uncaught exceptions happen:
+
protected override void OnCreate(Bundle savedInstanceState)
+{
+ // ...
+
+ AndroidEnvironment.UnhandledExceptionRaiser += AndroidEnvironment_UnhandledExceptionRaiser;
+ AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
+ TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
+
+ // ... LoadApplication(new App()); etc.
+}
+
+
Next, implement a method that can log an exception to elmah.io:
+
private void LogExceptionToElmahIo(Exception exception)
+{
+ if (exception == null) return;
+
+ if (elmahIoClient == null)
+ {
+ elmahIoClient = ElmahioAPI.Create("API_KEY");
+ }
+
+ var packageInfo = PackageManager.GetPackageInfo(PackageName, PackageInfoFlags.MetaData);
+
+ var baseException = exception?.GetBaseException();
+ var errorMessage = baseException?.Message ?? "Unhandled exception";
+ try
+ {
+ elmahIoClient.Messages.Create("LOG_ID", new CreateMessage
+ {
+ Data = exception?.ToDataList(),
+ DateTime = DateTime.UtcNow,
+ Detail = exception?.ToString(),
+ Severity = "Error",
+ Source = baseException?.Source,
+ Title = errorMessage,
+ Type = baseException?.GetType().FullName,
+ Version = packageInfo.VersionName,
+ Application = packageInfo.PackageName,
+ });
+ }
+ catch (Exception inner)
+ {
+ Android.Util.Log.Error("elmahio", inner.Message);
+ }
+
+ // Log to Android Device Logging.
+ Android.Util.Log.Error("crash", errorMessage);
+}
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
(Where is my log ID? ) with the log Id of the log you want to log to.
+
Finally, implement the three event handlers that we added in the first step:
+
private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
+{
+ LogExceptionToElmahIo(e.Exception);
+ e.SetObserved();
+}
+
+private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+{
+ LogExceptionToElmahIo(e.ExceptionObject as Exception);
+}
+
+private void AndroidEnvironment_UnhandledExceptionRaiser(object sender, RaiseThrowableEventArgs e)
+{
+ LogExceptionToElmahIo(e.Exception);
+ e.Handled = true;
+}
+
+
+
+
Locate your main application class and look for the Main
method. Here, you'd want to set up event handlers for when uncaught exceptions happen:
+
static void Main(string[] args)
+{
+ // ...
+
+ AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
+ TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
+}
+
+
Next, implement a method that can log an exception to elmah.io:
+
private static void LogExceptionToElmahIo(Exception exception)
+{
+ if (exception == null) return;
+
+ if (elmahIoClient == null)
+ {
+ elmahIoClient = ElmahioAPI.Create("API_KEY");
+ }
+
+ var baseException = exception?.GetBaseException();
+ elmahIoClient.Messages.Create("LOG_ID", new CreateMessage
+ {
+ Data = exception?.ToDataList(),
+ DateTime = DateTime.UtcNow,
+ Detail = exception?.ToString(),
+ Severity = "Error",
+ Source = baseException?.Source,
+ Title = baseException?.Message ?? "Unhandled exception",
+ Type = baseException?.GetType().FullName,
+ });
+}
+
+
Replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
(Where is my log ID? ) with the log Id of the log you want to log to.
+
Finally, implement the two event handlers that we added in the first step:
+
private static void TaskScheduler_UnobservedTaskException(
+ object sender, UnobservedTaskExceptionEventArgs e)
+{
+ LogExceptionToElmahIo(e.Exception);
+ e.SetObserved();
+}
+
+private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+{
+ LogExceptionToElmahIo(e.ExceptionObject as Exception);
+}
+
+
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/logging-to-multiple-elmah-logs/index.html b/logging-to-multiple-elmah-logs/index.html
new file mode 100644
index 0000000000..7f339c5fd4
--- /dev/null
+++ b/logging-to-multiple-elmah-logs/index.html
@@ -0,0 +1,679 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Logging to multiple ELMAH logs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Logging to multiple ELMAH logs
+
Unfortunately, ELMAH (the open source project) doesn't support multiple log targets like other logging frameworks like Serilog. This makes logging to multiple logs a bit tricky but in no way impossible. Let's say that you're using ELMAH in your web application and configured it to log everything in SQL Server. If you look through your web.config
file, you will have code looking like this somewhere:
+
<elmah>
+ <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="elmah"/>
+</elmah>
+
+
As you probably know, this tells ELMAH to log all unhandled errors in SQL Server with the connection string “elmah”. You cannot add more <errorLog>
elements, why logging into a second log seems impossible. Meet ELMAH's Logged
event, which is a great hook to log to multiple targets. Install the Elmah.Io NuGet package and add the following code to your global.asax.cs
file:
+
void ErrorLog_Logged(object sender, Elmah.ErrorLoggedEventArgs args)
+{
+ var elmahIoLog = new Elmah.Io.ErrorLog(ElmahioAPI.Create("API_KEY"), new Guid("LOG_ID"));
+ elmahIoLog.Log(args.Entry.Error);
+}
+
+
In the above code, we listen for the Logged
event by simply declaring a method named ErrorLog_Logged
. When called, we create a new (Elmah.Io.)ErrorLog
instance with an IElmahioAPI
object and the log ID. Remember to replace API_KEY
with your API key (Where is my API key? ) and LOG_ID
with your log ID (Where is my log ID? ). You may want to share the ElmahioAPI
object between requests by declaring it as a private member. Next, we simply call the Log
method with a new Error
object. Bam! The error is logged both in SQL Server and in elmah.io.
+
If you only want to log certain types of errors in elmah.io, but everything to your normal log, you can extend your code like this:
+
void ErrorLog_Logged(object sender, Elmah.ErrorLoggedEventArgs args)
+{
+ if (args.Entry.Error.StatusCode == 500)
+ {
+ var elmahIoLog = new Elmah.Io.ErrorLog(/*...*/);
+ elmahIoLog.Log(args.Entry.Error);
+ }
+}
+
+
This time we only begin logging to elmah.io, if the thrown exception is of type HttpException
and contains an HTTP status code of 500
. This example only logs errors with status code 500 in elmah.io and all errors in your normal error log. If you want to create this filter on all logs, you should use the ErrorLog_Filtering
method instead. This method is called before ErrorLog_Logged
and before actually logging the error to your normal error log.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/managing-environments/index.html b/managing-environments/index.html
new file mode 100644
index 0000000000..0640feff5e
--- /dev/null
+++ b/managing-environments/index.html
@@ -0,0 +1,668 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Managing Environments
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Managing Environments
+
Environments is a grouping feature on elmah.io that will help you group similar logs. By default you will have four environments in your organization: Development, Test, Staging, and Production. While this is the default view, additional environments can be added or environments named differently. The names don't need to match environments if you prefer a different naming scheme. Other ways to use environments would be to group logs by customers, departments, or servers.
+
When creating a new log from the elmah.io Dashboard, you can pick an environment in the dropdown:
+
+
Once logs are added to environments, they are nicely grouped in the left menu and on the list of log boxes on the dashboard:
+
+
Logs can be ordered using the drag-and-drop button at the top but only inside the same environment. If you need to move a log to another environment, click Edit (the pencil icon on the log box) and select the new environment from the dropdown.
+
If you want to show logs within one or more specific environments on the dashboard, you can use the Environments dropdown:
+
+
The list of environments can be managed from your organization settings page. To open organization settings, click the gears icon next to your organization name on either the left menu or through the dashboard:
+
+
On the organization settings page, select the Environments tab:
+
+
The order of environments can be changed using drag and drop. New environments can be created and existing ones deleted.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/managing-organisations-and-users/index.html b/managing-organisations-and-users/index.html
new file mode 100644
index 0000000000..d8fcbd9fa9
--- /dev/null
+++ b/managing-organisations-and-users/index.html
@@ -0,0 +1,707 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Managing Organizations and Users
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Managing Organizations and Users
+
+
Chances are that you are not the only one needing to access your logs. Luckily, elmah.io offers great features to manage the users in your organization and to specify who should be allowed access to what.
+
This guide is also available as a short video tutorial here:
+
+
+
+
+
To manage access, you will need to know about the concepts of users and organizations .
+
A user represents a person wanting to access one or more logs. Each user logs in using a username/password or a social provider of choice. A user can be added to one or more organizations. Each user has an access level within the organization as well as an access level on each log. The access level on the organization and the logs doesn't need to be the same.
+
An organization is a collection of users and their roles inside the organization. You will typically only need a single organization, representing all of the users in your company needing to access one or more logs on elmah.io. Your elmah.io subscription is attached to your organization and everyone with administrator access to the organization will be able to manage the subscription.
+
Adding existing users to an organization
+
To assign users to a log, you will need to add them to the organization first. When hovering the organization name in either the left menu or on the dashboard, you will see a small gear icon. When clicking the icon, you will be taken to the organization settings page:
+
+
At first, the user creating the organization will be the only one on the list. To add a new user to the list, click the Add user button and input the user's email or name in the textbox. The dropdown will show a list of users on elmah.io matching your query.
+
+Each user needs to sign up on elmah.io before being visible through Add user . Jump to Invite new users to an organization to learn how to invite new users.
+
+
When the new user is visible in the dropdown, click the user and select an access level. The chosen access level decides what the new user is allowed to do inside the organization. Read users are only allowed to view the organization, while Administrator users are allowed to add new users and delete the entire organization and all logs beneath it. The access level set for the user in the organization will become the user's access level on all new logs inside that organization as well.
+
To change the access level on an added user, click one of the grouped buttons to the right of the user's name. Changing a user's access level on the organization won't change the user's access level on each log. To delete a user from the organization, click the red delete button to the far right.
+
When a user is added to an organization, the user will automatically have access to all new logs created in that organization. For security reasons, a new user added to the organization, will not have access to existing logs in the organization. To assign the new user to existing logs, assign an access level on each log by clicking the settings button to the right of the user:
+
+
+Awarding a user Administrator on a log doesn't give them Administrator rights to the organization.
+
+
To assign a user to all logs, click the None , Read , Write , or Administrator buttons in the table header above the list of logs.
+
Likewise, organization administrators can assign users to different emails for each log in the organization:
+
+
This view is similar to the one the user has on the profile when signing into elmah.io. Make sure to notify users when you assign emails to them to avoid them marking emails as spam.
+
Invite new users to an organization
+
If someone not already created as a user on elmah.io needs access to your organization, you can use the Invite feature. Inviting users will send them an email telling them to sign up for elmah.io and automatically add them to your organization.
+
To invite a user click the Invite user button and input the new user's email. Select an organization access level and click the green Invite user button. This will add the new user to the organization and display it as "Invited" until the user signs up.
+
Control security
+
You may have requirements of using two-factor authentication or against using social sign-ins in your company. These requirements can be configured on elmah.io as well. Click the Security button above the user's list to set it up:
+
+
Using this view you can allow or disallow sign-ins using:
+
+An elmah.io username and password
+Twitter
+Facebook
+Microsoft
+Google
+
+
Notice that disallowing different sign-in types will still allow users in your organization to sign into elmah.io. As soon as a disallowed user type is trying to access pages inside the organization and/or logs a page telling them which sign-in type or required settings is shown.
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/missing-server-side-information-on-uptime-errors/index.html b/missing-server-side-information-on-uptime-errors/index.html
new file mode 100644
index 0000000000..b750f4440f
--- /dev/null
+++ b/missing-server-side-information-on-uptime-errors/index.html
@@ -0,0 +1,661 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Missing server- side information on uptime errors
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
To decorate uptime errors with server-side error information, you will need a few things:
+
+The monitored website should be configured to log errors to elmah.io.
+The uptime check needs to be created on the same log as the monitored website.
+When installed in a previous version, errors generated by Uptime Monitoring are ignored by the BotBuster app (since Uptime Monitoring is also a bot). Uninstall the BotBuster app and enable the Filter Crawlers filter instead.
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/query-messages-using-full-text-search/index.html b/query-messages-using-full-text-search/index.html
new file mode 100644
index 0000000000..ca57723334
--- /dev/null
+++ b/query-messages-using-full-text-search/index.html
@@ -0,0 +1,941 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Query messages using full-text search
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Query messages using full-text search
+
All messages sent to elmah.io, are indexed in Elasticsearch. Storing messages in a database like Elasticsearch opens up a world of possibilities. This article explains how to query your log messages using full-text search, Search Filters, and Lucene Query Syntax.
+
+
Full-text search
+
The easiest approach to start searching your log messages is by inputting search terms in the Search field on elmah.io:
+
+
We don't want to get into too much detail on how full-text works in Elasticsearch. In short, Elasticsearch breaks the query into the terms nominavi and voluptatibus and tries to match all log messages including those terms. Full-text search work on analyzed fields in Elasticsearch, which means that wildcards and other constructs are fully supported.
+
Full-text queries work great. when you want to do a quick search for some keywords like part of an exception message or stack trace. Remember that the entire log message is search, why a search for 500 would hit both log messages with status code 500 and the term 500 in the stack trace.
+
Search Filters
+
Search filters are built exclusively for elmah.io. They are built on top of Lucene Query Syntax (which we'll discuss in a minute), but much easier to write. Search filters are available through either the Add filter button below the search field or using various links and icons on the elmah.io UI.
+
Let's say we want to find all errors with a status code of 500:
+
+
Adding the two filters is possible using a few clicks.
+
As mentioned previously, search filters are available throughout the UI too. In this example, a filter is used to find messages not matching a specified URL:
+
+
Search filters can be used in combination with full-text queries for greater flexibility.
+
Lucene Query Syntax
+
Elasticsearch is implemented on top of Lucene; a high-performance search engine, written entirely in Java. While Elasticsearch supports a lot of nice abstractions on top of Lucene, sometimes you just want close to the metal. This is when we need to introduce you to Lucene Query Syntax. The query syntax is a query language similar to the WHERE part of a SQL statement. Unlike SQL, the query syntax supports both filters (similar to SQL) and full-text queries.
+
All Lucene queries are made up of strings containing full-text search strings and/or terms combined with operators. A simple full-text query simply looks like this:
+
values to search for
+
+
This will search log messages for 'values', 'to', 'search', and 'for'. For exact searches you can use quotes:
+
"values to search for"
+
+
This will only find log messages where that exact string is present somewhere.
+
Queries can also search inside specific fields:
+
field:value
+
+
This is similar to the WHERE part in SQL and will, in this example, search for the term 'value' inside the field named 'field'.
+
Both full-text queries and field-based queries can be combined with other queries using AND
, OR
, and NOT
:
+
field1:value1 AND field2:value2 AND NOT field3:value3
+
+
You can use operators known from C# if you prefer that syntax:
+
field1:value1 && field2:value2 && !field3:value3
+
+
Full-text and field-based queries can be combined into complex queries:
+
field1:value1 && field2:"exact value" || (field1:value2 && "a full text query")
+
+
Examples are worth a thousand words, why the rest of this document is examples of frequently used queries. If you think that examples are missing or have a problem with custom queries, let us know. We will extend this tutorial with the examples you need.
+
Find messages with type
+
type:"System.Web.HttpException"
+
+
Find messages with status codes
+
statusCode:[500 TO 599]
+
+
Find messages with URL and method
+
url:"/tester/" AND method:get
+
+
Find messages with URL starting with
+
url:\/.well-known*
+
+
The forward slash, in the beginning, needs to be escaped, since Lucene will understand it as the start of a regex otherwise.
+
Find messages by IP
+
remoteAddr:192.168.0.1
+
+
Find messages by IP's
+
remoteAddr:192.68.0.*
+
+
The examples above can be achieved using Search Filters as well. We recommend using Search Filters where possible and falling back to Lucene Query Syntax when something isn't supported through filters. An example is using OR which currently isn't possible using filters.
+
Field specification
+
As already illustrated through multiple examples in this document, all log messages consist of a range of fields. Here's a full overview of all fields, data types, etc. The .raw column requires a bit of explanation if you are not familiar with Elasticsearch. Fields that are marked as having .raw are indexed for full-text queries. This means that the values are tokenized and optimized for full-text search and not querying by exact values. In this case, a special raw field is created for supporting SQL like WHERE
queries. To illustrate, searching for a part of a user agent would look like this:
+
userAgent:chrome
+
+
And searching for a specific user agent would look like this:
+
userAgent.raw:"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36"
+
+
Here is the full set of fields:
+
+
+
+Name
+Type
+.raw
+Description
+
+
+
+
+applicationName
+string
+✅
+Used to identify which application logged this message. You can use this if you have multiple applications and services logging into the same log.
+
+
+assignedTo
+string
+
+The id of the user assigned to the log message. elmah.io user IDs are not something that is published on the UI anywhere why this field is intended for the My errors dashboard only.
+
+
+browser
+string
+✅
+A short string classifying each log message to a browser. The value can be one of the following: chrome, safari, edge, firefox, opera, ie, other.
+
+
+category
+string
+✅
+The log message category. Category can be a string of choice but typically contain a logging category set by a logging framework like NLog or Serilog. When logging through a logging framework, this field will be provided by the framework and not something that needs to be set manually.
+
+
+correlationId
+string
+
+CorrelationId can be used to group similar log messages into a single discoverable batch. A correlation ID could be a session ID from ASP.NET Core, a unique string spanning multiple microservices handling the same request, or similar.
+
+
+country
+string
+
+An ISO 3166 two-letter country code in case we could resolve a country from the log message.
+
+
+detail
+string
+
+A longer description of the message. For errors, this could be a stack trace, but it's really up to you what to log in there.
+
+
+domain
+string
+
+The domain name if it could be resolved from the server variables.
+
+
+hash
+string
+
+A unique hash for a log message. The hash is used for multiple things on elmah.io like the new detection.
+
+
+hidden
+boolean
+
+A boolean indicating if a log message has been hidden through the UI, the API, or a hide rule.
+
+
+hostName
+string
+✅
+The hostname of the server logging the message.
+
+
+isBot
+boolean
+
+A boolean indicating is a log message is generated by an automated bot or crawler. This flag is set manually through the UI.
+
+
+isBotSuggestion
+boolean
+
+A boolean indicating if the log message looks to be generated by an automated bot or crawler. Unlike the isBot
field, this field is automatically set using machine learning and is available on the Enterprise plan only.
+
+
+isBurst
+boolean
+
+A boolean indicating if the log message is a burst. Log messages are automatically marked as burst if we have seen it more than 50 times during the retention period of the purchased plan.
+
+
+isFixed
+boolean
+
+A boolean indicating if the log message has been marked as fixed. Log messages can be marked as fixed from the UI.
+
+
+isHeartbeat
+boolean
+
+A boolean indicating if the log message is logged from the elmah.io Heartbeats feature.
+
+
+isNew
+boolean
+
+A boolean indicating if we have seen this unique log message before.
+
+
+isSpike
+boolean
+
+A boolean indicating if the log message is logged from the elmah.io Spike feature.
+
+
+isUptime
+boolean
+
+A boolean indicating if the log message is logged from the elmah.io Uptime Monitoring feature.
+
+
+message
+string
+✅
+The textual title or headline of the message to log.
+
+
+messageTemplate
+string
+
+The title template of the message to log. This property can be used from logging frameworks that support structured logging like: "{user} says {quote}". In the example, titleTemplate will be this string and the title will be "Gilfoyle says It's not magic. It's talent and sweat".
+
+
+method
+string
+✅
+If the log message relates to an HTTP request, you may send the HTTP method of that request. If you don't provide us with a method, we will try to find a key named REQUEST_METHOD in serverVariables.
+
+
+os
+string
+✅
+A short string classifying each log message to an operating system. The value can be one of the following: ios, windows, android, macos, linux, other.
+
+
+remoteAddr
+string
+✅
+The IP address of the user generating this log message if it can be resolved from server variables.
+
+
+severity
+string
+
+An enum value representing the severity of this message. The following values are allowed: Verbose, Debug, Information, Warning, Error, Fatal.
+
+
+source
+string
+✅
+The source of the code logging the message. This could be the assembly name.
+
+
+statusCode
+number
+
+If the message logged relates to an HTTP status code, you can put the code in this property. This would probably only be relevant for errors but could be used for logging successful status codes as well.
+
+
+time
+date
+
+The date and time in UTC of the message. If you don't provide us with a value, this will be set the current date and time in UTC.
+
+
+type
+string
+✅
+The type of message. If logging an error, the type of the exception would go into type but you can put anything in there, that makes sense for your domain.
+
+
+url
+string
+
+If the log message relates to an HTTP request, you may send the URL of that request. If you don't provide us with an URL, we will try to find a key named URL in serverVariables.
+
+
+user
+string
+✅
+An identification of the user triggering this message. You can put the user's email address or your user key into this property.
+
+
+userAgent
+string
+✅
+The user agent of the user causing the log message if it can be resolved from server variables.
+
+
+version
+string
+✅
+Versions can be used to distinguish messages from different versions of your software. The value of the version can be a SemVer compliant string or any other syntax that you are using as your version numbering scheme.
+
+
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/remove-sensitive-form-data/index.html b/remove-sensitive-form-data/index.html
new file mode 100644
index 0000000000..d23768552b
--- /dev/null
+++ b/remove-sensitive-form-data/index.html
@@ -0,0 +1,668 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Remove sensitive form data from log messages
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
You may have something like usernames and passwords in form posts on your website. Since elmah.io automatically logs the content of a failing form POST, sensitive data potentially ends up in your log. No one else but you and your company should get to look inside your log, but remember that everyone connected to the Internet, is a potential hacking victim.
+
In this example, we hide the value of a form value named SomeSecretFormField
. Add the following code in the Application_Start
method in the global.asax.cs
file:
+
Elmah.ErrorLog.GetDefault(null); // Forces creation of logger client
+var logger = Elmah.Io.ErrorLog.Client;
+logger.OnMessage += (sender, args) =>
+{
+ var form = args.Message.Form.FirstOrDefault(f => f.Key == "SomeSecretFormField");
+ if (form != null)
+ {
+ form.Value = "***hidden***";
+ }
+};
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/robots.txt b/robots.txt
new file mode 100644
index 0000000000..5975a417a1
--- /dev/null
+++ b/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /cdn-cgi/
\ No newline at end of file
diff --git a/roslyn-analyzers-for-elmah-io-and-aspnet-core/index.html b/roslyn-analyzers-for-elmah-io-and-aspnet-core/index.html
new file mode 100644
index 0000000000..24a439b69e
--- /dev/null
+++ b/roslyn-analyzers-for-elmah-io-and-aspnet-core/index.html
@@ -0,0 +1,718 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Roslyn analyzers for elmah.io and ASP.NET Core
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Roslyn analyzers for elmah.io and ASP.NET Core
+
+
+The Roslyn analyzers for elmah.io and ASP.NET Core has reached end of life. The analyzers are no longer updated and won't work for top-level statements or when configuring Elmah.Io.AspNetCore
in the Program.cs
file. To validate the installation we recommend running the diagnose
command as explained here: Diagnose potential problems with an elmah.io installation .
+
+
To help to install elmah.io in ASP.NET Core (by using the Elmah.Io.AspNetCore
NuGet package) we have developed a range of Roslyn analyzers. Analyzers run inside Visual Studio and make it possible to validate your Startup.cs
file during development.
+
Installation and usage
+
The analyzers can be installed in two ways. As a NuGet package or a Visual Studio extension. To install it from NuGet:
+
Install-Package Elmah.Io.AspNetCore.Analyzers
+
dotnet add package Elmah.Io.AspNetCore.Analyzers
+
<PackageReference Include="Elmah.Io.AspNetCore.Analyzers" Version="0.*" />
+
paket add Elmah.Io.AspNetCore.Analyzers
+
+
The package is installed as a private asset, which means that it is not distributed as part of your build. You can keep the package installed after you have used it to inspect any warnings generated or uninstall it.
+
To install it as a Visual Studio extension, navigate to Extensions | Manage extensions | Online and search for Elmah.Io.AspNetCore.Analyzers
. Then click the Download button and restart Visual Studio. As an alternative, you can download the extension directly from the Visual Studio Marketplace.
+
Once installed, analyzers will help you add or move elmah.io-related setup code:
+
+
All issues are listed as warnings in the Error list as well. The following is an explanation of possible warnings.
+
+
AddElmahIo
needs to be added as part of the ConfigureServices
method:
+
public void ConfigureServices(IServiceCollection services)
+{
+ services.AddElmahIo(/*...*/); //👈
+}
+
+
+
UseElmahIo
needs to be added as part of the Configure
method:
+
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+{
+ app.UseElmahIo(); //👈
+}
+
+
EIO1002 UseElmahIo must be called before/after Use*
+
UseElmahIo
needs to be called after any calls to UseDeveloperExceptionPage
, UseExceptionHandler
, UseAuthorization
, and UseAuthentication
but before any calls to UseEndpoints
and UseMvc
:
+
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+{
+ if (env.IsDevelopment())
+ {
+ app.UseDeveloperExceptionPage();
+ }
+ else
+ {
+ app.UseExceptionHandler(/*...*/);
+ }
+
+ app.UseAuthentication();
+ app.UseAuthorization();
+
+ app.UseElmahIo(); //👈
+
+ app.UseEndpoints();
+ app.UseMvc(/*...*/);
+}
+
+
+
+
This article was brought to you by the elmah.io team. elmah.io is the best error management system for .NET web applications. We monitor your website, alert you when errors start happening, and help you fix errors fast.
+
+ See how we can help you monitor your website for crashes
+ Monitor your website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/search/lunr.js b/search/lunr.js
new file mode 100644
index 0000000000..aca0a167f3
--- /dev/null
+++ b/search/lunr.js
@@ -0,0 +1,3475 @@
+/**
+ * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9
+ * Copyright (C) 2020 Oliver Nightingale
+ * @license MIT
+ */
+
+;(function(){
+
+/**
+ * A convenience function for configuring and constructing
+ * a new lunr Index.
+ *
+ * A lunr.Builder instance is created and the pipeline setup
+ * with a trimmer, stop word filter and stemmer.
+ *
+ * This builder object is yielded to the configuration function
+ * that is passed as a parameter, allowing the list of fields
+ * and other builder parameters to be customised.
+ *
+ * All documents _must_ be added within the passed config function.
+ *
+ * @example
+ * var idx = lunr(function () {
+ * this.field('title')
+ * this.field('body')
+ * this.ref('id')
+ *
+ * documents.forEach(function (doc) {
+ * this.add(doc)
+ * }, this)
+ * })
+ *
+ * @see {@link lunr.Builder}
+ * @see {@link lunr.Pipeline}
+ * @see {@link lunr.trimmer}
+ * @see {@link lunr.stopWordFilter}
+ * @see {@link lunr.stemmer}
+ * @namespace {function} lunr
+ */
+var lunr = function (config) {
+ var builder = new lunr.Builder
+
+ builder.pipeline.add(
+ lunr.trimmer,
+ lunr.stopWordFilter,
+ lunr.stemmer
+ )
+
+ builder.searchPipeline.add(
+ lunr.stemmer
+ )
+
+ config.call(builder, builder)
+ return builder.build()
+}
+
+lunr.version = "2.3.9"
+/*!
+ * lunr.utils
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+
+/**
+ * A namespace containing utils for the rest of the lunr library
+ * @namespace lunr.utils
+ */
+lunr.utils = {}
+
+/**
+ * Print a warning message to the console.
+ *
+ * @param {String} message The message to be printed.
+ * @memberOf lunr.utils
+ * @function
+ */
+lunr.utils.warn = (function (global) {
+ /* eslint-disable no-console */
+ return function (message) {
+ if (global.console && console.warn) {
+ console.warn(message)
+ }
+ }
+ /* eslint-enable no-console */
+})(this)
+
+/**
+ * Convert an object to a string.
+ *
+ * In the case of `null` and `undefined` the function returns
+ * the empty string, in all other cases the result of calling
+ * `toString` on the passed object is returned.
+ *
+ * @param {Any} obj The object to convert to a string.
+ * @return {String} string representation of the passed object.
+ * @memberOf lunr.utils
+ */
+lunr.utils.asString = function (obj) {
+ if (obj === void 0 || obj === null) {
+ return ""
+ } else {
+ return obj.toString()
+ }
+}
+
+/**
+ * Clones an object.
+ *
+ * Will create a copy of an existing object such that any mutations
+ * on the copy cannot affect the original.
+ *
+ * Only shallow objects are supported, passing a nested object to this
+ * function will cause a TypeError.
+ *
+ * Objects with primitives, and arrays of primitives are supported.
+ *
+ * @param {Object} obj The object to clone.
+ * @return {Object} a clone of the passed object.
+ * @throws {TypeError} when a nested object is passed.
+ * @memberOf Utils
+ */
+lunr.utils.clone = function (obj) {
+ if (obj === null || obj === undefined) {
+ return obj
+ }
+
+ var clone = Object.create(null),
+ keys = Object.keys(obj)
+
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i],
+ val = obj[key]
+
+ if (Array.isArray(val)) {
+ clone[key] = val.slice()
+ continue
+ }
+
+ if (typeof val === 'string' ||
+ typeof val === 'number' ||
+ typeof val === 'boolean') {
+ clone[key] = val
+ continue
+ }
+
+ throw new TypeError("clone is not deep and does not support nested objects")
+ }
+
+ return clone
+}
+lunr.FieldRef = function (docRef, fieldName, stringValue) {
+ this.docRef = docRef
+ this.fieldName = fieldName
+ this._stringValue = stringValue
+}
+
+lunr.FieldRef.joiner = "/"
+
+lunr.FieldRef.fromString = function (s) {
+ var n = s.indexOf(lunr.FieldRef.joiner)
+
+ if (n === -1) {
+ throw "malformed field ref string"
+ }
+
+ var fieldRef = s.slice(0, n),
+ docRef = s.slice(n + 1)
+
+ return new lunr.FieldRef (docRef, fieldRef, s)
+}
+
+lunr.FieldRef.prototype.toString = function () {
+ if (this._stringValue == undefined) {
+ this._stringValue = this.fieldName + lunr.FieldRef.joiner + this.docRef
+ }
+
+ return this._stringValue
+}
+/*!
+ * lunr.Set
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+
+/**
+ * A lunr set.
+ *
+ * @constructor
+ */
+lunr.Set = function (elements) {
+ this.elements = Object.create(null)
+
+ if (elements) {
+ this.length = elements.length
+
+ for (var i = 0; i < this.length; i++) {
+ this.elements[elements[i]] = true
+ }
+ } else {
+ this.length = 0
+ }
+}
+
+/**
+ * A complete set that contains all elements.
+ *
+ * @static
+ * @readonly
+ * @type {lunr.Set}
+ */
+lunr.Set.complete = {
+ intersect: function (other) {
+ return other
+ },
+
+ union: function () {
+ return this
+ },
+
+ contains: function () {
+ return true
+ }
+}
+
+/**
+ * An empty set that contains no elements.
+ *
+ * @static
+ * @readonly
+ * @type {lunr.Set}
+ */
+lunr.Set.empty = {
+ intersect: function () {
+ return this
+ },
+
+ union: function (other) {
+ return other
+ },
+
+ contains: function () {
+ return false
+ }
+}
+
+/**
+ * Returns true if this set contains the specified object.
+ *
+ * @param {object} object - Object whose presence in this set is to be tested.
+ * @returns {boolean} - True if this set contains the specified object.
+ */
+lunr.Set.prototype.contains = function (object) {
+ return !!this.elements[object]
+}
+
+/**
+ * Returns a new set containing only the elements that are present in both
+ * this set and the specified set.
+ *
+ * @param {lunr.Set} other - set to intersect with this set.
+ * @returns {lunr.Set} a new set that is the intersection of this and the specified set.
+ */
+
+lunr.Set.prototype.intersect = function (other) {
+ var a, b, elements, intersection = []
+
+ if (other === lunr.Set.complete) {
+ return this
+ }
+
+ if (other === lunr.Set.empty) {
+ return other
+ }
+
+ if (this.length < other.length) {
+ a = this
+ b = other
+ } else {
+ a = other
+ b = this
+ }
+
+ elements = Object.keys(a.elements)
+
+ for (var i = 0; i < elements.length; i++) {
+ var element = elements[i]
+ if (element in b.elements) {
+ intersection.push(element)
+ }
+ }
+
+ return new lunr.Set (intersection)
+}
+
+/**
+ * Returns a new set combining the elements of this and the specified set.
+ *
+ * @param {lunr.Set} other - set to union with this set.
+ * @return {lunr.Set} a new set that is the union of this and the specified set.
+ */
+
+lunr.Set.prototype.union = function (other) {
+ if (other === lunr.Set.complete) {
+ return lunr.Set.complete
+ }
+
+ if (other === lunr.Set.empty) {
+ return this
+ }
+
+ return new lunr.Set(Object.keys(this.elements).concat(Object.keys(other.elements)))
+}
+/**
+ * A function to calculate the inverse document frequency for
+ * a posting. This is shared between the builder and the index
+ *
+ * @private
+ * @param {object} posting - The posting for a given term
+ * @param {number} documentCount - The total number of documents.
+ */
+lunr.idf = function (posting, documentCount) {
+ var documentsWithTerm = 0
+
+ for (var fieldName in posting) {
+ if (fieldName == '_index') continue // Ignore the term index, its not a field
+ documentsWithTerm += Object.keys(posting[fieldName]).length
+ }
+
+ var x = (documentCount - documentsWithTerm + 0.5) / (documentsWithTerm + 0.5)
+
+ return Math.log(1 + Math.abs(x))
+}
+
+/**
+ * A token wraps a string representation of a token
+ * as it is passed through the text processing pipeline.
+ *
+ * @constructor
+ * @param {string} [str=''] - The string token being wrapped.
+ * @param {object} [metadata={}] - Metadata associated with this token.
+ */
+lunr.Token = function (str, metadata) {
+ this.str = str || ""
+ this.metadata = metadata || {}
+}
+
+/**
+ * Returns the token string that is being wrapped by this object.
+ *
+ * @returns {string}
+ */
+lunr.Token.prototype.toString = function () {
+ return this.str
+}
+
+/**
+ * A token update function is used when updating or optionally
+ * when cloning a token.
+ *
+ * @callback lunr.Token~updateFunction
+ * @param {string} str - The string representation of the token.
+ * @param {Object} metadata - All metadata associated with this token.
+ */
+
+/**
+ * Applies the given function to the wrapped string token.
+ *
+ * @example
+ * token.update(function (str, metadata) {
+ * return str.toUpperCase()
+ * })
+ *
+ * @param {lunr.Token~updateFunction} fn - A function to apply to the token string.
+ * @returns {lunr.Token}
+ */
+lunr.Token.prototype.update = function (fn) {
+ this.str = fn(this.str, this.metadata)
+ return this
+}
+
+/**
+ * Creates a clone of this token. Optionally a function can be
+ * applied to the cloned token.
+ *
+ * @param {lunr.Token~updateFunction} [fn] - An optional function to apply to the cloned token.
+ * @returns {lunr.Token}
+ */
+lunr.Token.prototype.clone = function (fn) {
+ fn = fn || function (s) { return s }
+ return new lunr.Token (fn(this.str, this.metadata), this.metadata)
+}
+/*!
+ * lunr.tokenizer
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+
+/**
+ * A function for splitting a string into tokens ready to be inserted into
+ * the search index. Uses `lunr.tokenizer.separator` to split strings, change
+ * the value of this property to change how strings are split into tokens.
+ *
+ * This tokenizer will convert its parameter to a string by calling `toString` and
+ * then will split this string on the character in `lunr.tokenizer.separator`.
+ * Arrays will have their elements converted to strings and wrapped in a lunr.Token.
+ *
+ * Optional metadata can be passed to the tokenizer, this metadata will be cloned and
+ * added as metadata to every token that is created from the object to be tokenized.
+ *
+ * @static
+ * @param {?(string|object|object[])} obj - The object to convert into tokens
+ * @param {?object} metadata - Optional metadata to associate with every token
+ * @returns {lunr.Token[]}
+ * @see {@link lunr.Pipeline}
+ */
+lunr.tokenizer = function (obj, metadata) {
+ if (obj == null || obj == undefined) {
+ return []
+ }
+
+ if (Array.isArray(obj)) {
+ return obj.map(function (t) {
+ return new lunr.Token(
+ lunr.utils.asString(t).toLowerCase(),
+ lunr.utils.clone(metadata)
+ )
+ })
+ }
+
+ var str = obj.toString().toLowerCase(),
+ len = str.length,
+ tokens = []
+
+ for (var sliceEnd = 0, sliceStart = 0; sliceEnd <= len; sliceEnd++) {
+ var char = str.charAt(sliceEnd),
+ sliceLength = sliceEnd - sliceStart
+
+ if ((char.match(lunr.tokenizer.separator) || sliceEnd == len)) {
+
+ if (sliceLength > 0) {
+ var tokenMetadata = lunr.utils.clone(metadata) || {}
+ tokenMetadata["position"] = [sliceStart, sliceLength]
+ tokenMetadata["index"] = tokens.length
+
+ tokens.push(
+ new lunr.Token (
+ str.slice(sliceStart, sliceEnd),
+ tokenMetadata
+ )
+ )
+ }
+
+ sliceStart = sliceEnd + 1
+ }
+
+ }
+
+ return tokens
+}
+
+/**
+ * The separator used to split a string into tokens. Override this property to change the behaviour of
+ * `lunr.tokenizer` behaviour when tokenizing strings. By default this splits on whitespace and hyphens.
+ *
+ * @static
+ * @see lunr.tokenizer
+ */
+lunr.tokenizer.separator = /[\s\-]+/
+/*!
+ * lunr.Pipeline
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+
+/**
+ * lunr.Pipelines maintain an ordered list of functions to be applied to all
+ * tokens in documents entering the search index and queries being ran against
+ * the index.
+ *
+ * An instance of lunr.Index created with the lunr shortcut will contain a
+ * pipeline with a stop word filter and an English language stemmer. Extra
+ * functions can be added before or after either of these functions or these
+ * default functions can be removed.
+ *
+ * When run the pipeline will call each function in turn, passing a token, the
+ * index of that token in the original list of all tokens and finally a list of
+ * all the original tokens.
+ *
+ * The output of functions in the pipeline will be passed to the next function
+ * in the pipeline. To exclude a token from entering the index the function
+ * should return undefined, the rest of the pipeline will not be called with
+ * this token.
+ *
+ * For serialisation of pipelines to work, all functions used in an instance of
+ * a pipeline should be registered with lunr.Pipeline. Registered functions can
+ * then be loaded. If trying to load a serialised pipeline that uses functions
+ * that are not registered an error will be thrown.
+ *
+ * If not planning on serialising the pipeline then registering pipeline functions
+ * is not necessary.
+ *
+ * @constructor
+ */
+lunr.Pipeline = function () {
+ this._stack = []
+}
+
+lunr.Pipeline.registeredFunctions = Object.create(null)
+
+/**
+ * A pipeline function maps lunr.Token to lunr.Token. A lunr.Token contains the token
+ * string as well as all known metadata. A pipeline function can mutate the token string
+ * or mutate (or add) metadata for a given token.
+ *
+ * A pipeline function can indicate that the passed token should be discarded by returning
+ * null, undefined or an empty string. This token will not be passed to any downstream pipeline
+ * functions and will not be added to the index.
+ *
+ * Multiple tokens can be returned by returning an array of tokens. Each token will be passed
+ * to any downstream pipeline functions and all will returned tokens will be added to the index.
+ *
+ * Any number of pipeline functions may be chained together using a lunr.Pipeline.
+ *
+ * @interface lunr.PipelineFunction
+ * @param {lunr.Token} token - A token from the document being processed.
+ * @param {number} i - The index of this token in the complete list of tokens for this document/field.
+ * @param {lunr.Token[]} tokens - All tokens for this document/field.
+ * @returns {(?lunr.Token|lunr.Token[])}
+ */
+
+/**
+ * Register a function with the pipeline.
+ *
+ * Functions that are used in the pipeline should be registered if the pipeline
+ * needs to be serialised, or a serialised pipeline needs to be loaded.
+ *
+ * Registering a function does not add it to a pipeline, functions must still be
+ * added to instances of the pipeline for them to be used when running a pipeline.
+ *
+ * @param {lunr.PipelineFunction} fn - The function to check for.
+ * @param {String} label - The label to register this function with
+ */
+lunr.Pipeline.registerFunction = function (fn, label) {
+ if (label in this.registeredFunctions) {
+ lunr.utils.warn('Overwriting existing registered function: ' + label)
+ }
+
+ fn.label = label
+ lunr.Pipeline.registeredFunctions[fn.label] = fn
+}
+
+/**
+ * Warns if the function is not registered as a Pipeline function.
+ *
+ * @param {lunr.PipelineFunction} fn - The function to check for.
+ * @private
+ */
+lunr.Pipeline.warnIfFunctionNotRegistered = function (fn) {
+ var isRegistered = fn.label && (fn.label in this.registeredFunctions)
+
+ if (!isRegistered) {
+ lunr.utils.warn('Function is not registered with pipeline. This may cause problems when serialising the index.\n', fn)
+ }
+}
+
+/**
+ * Loads a previously serialised pipeline.
+ *
+ * All functions to be loaded must already be registered with lunr.Pipeline.
+ * If any function from the serialised data has not been registered then an
+ * error will be thrown.
+ *
+ * @param {Object} serialised - The serialised pipeline to load.
+ * @returns {lunr.Pipeline}
+ */
+lunr.Pipeline.load = function (serialised) {
+ var pipeline = new lunr.Pipeline
+
+ serialised.forEach(function (fnName) {
+ var fn = lunr.Pipeline.registeredFunctions[fnName]
+
+ if (fn) {
+ pipeline.add(fn)
+ } else {
+ throw new Error('Cannot load unregistered function: ' + fnName)
+ }
+ })
+
+ return pipeline
+}
+
+/**
+ * Adds new functions to the end of the pipeline.
+ *
+ * Logs a warning if the function has not been registered.
+ *
+ * @param {lunr.PipelineFunction[]} functions - Any number of functions to add to the pipeline.
+ */
+lunr.Pipeline.prototype.add = function () {
+ var fns = Array.prototype.slice.call(arguments)
+
+ fns.forEach(function (fn) {
+ lunr.Pipeline.warnIfFunctionNotRegistered(fn)
+ this._stack.push(fn)
+ }, this)
+}
+
+/**
+ * Adds a single function after a function that already exists in the
+ * pipeline.
+ *
+ * Logs a warning if the function has not been registered.
+ *
+ * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.
+ * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.
+ */
+lunr.Pipeline.prototype.after = function (existingFn, newFn) {
+ lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
+
+ var pos = this._stack.indexOf(existingFn)
+ if (pos == -1) {
+ throw new Error('Cannot find existingFn')
+ }
+
+ pos = pos + 1
+ this._stack.splice(pos, 0, newFn)
+}
+
+/**
+ * Adds a single function before a function that already exists in the
+ * pipeline.
+ *
+ * Logs a warning if the function has not been registered.
+ *
+ * @param {lunr.PipelineFunction} existingFn - A function that already exists in the pipeline.
+ * @param {lunr.PipelineFunction} newFn - The new function to add to the pipeline.
+ */
+lunr.Pipeline.prototype.before = function (existingFn, newFn) {
+ lunr.Pipeline.warnIfFunctionNotRegistered(newFn)
+
+ var pos = this._stack.indexOf(existingFn)
+ if (pos == -1) {
+ throw new Error('Cannot find existingFn')
+ }
+
+ this._stack.splice(pos, 0, newFn)
+}
+
+/**
+ * Removes a function from the pipeline.
+ *
+ * @param {lunr.PipelineFunction} fn The function to remove from the pipeline.
+ */
+lunr.Pipeline.prototype.remove = function (fn) {
+ var pos = this._stack.indexOf(fn)
+ if (pos == -1) {
+ return
+ }
+
+ this._stack.splice(pos, 1)
+}
+
+/**
+ * Runs the current list of functions that make up the pipeline against the
+ * passed tokens.
+ *
+ * @param {Array} tokens The tokens to run through the pipeline.
+ * @returns {Array}
+ */
+lunr.Pipeline.prototype.run = function (tokens) {
+ var stackLength = this._stack.length
+
+ for (var i = 0; i < stackLength; i++) {
+ var fn = this._stack[i]
+ var memo = []
+
+ for (var j = 0; j < tokens.length; j++) {
+ var result = fn(tokens[j], j, tokens)
+
+ if (result === null || result === void 0 || result === '') continue
+
+ if (Array.isArray(result)) {
+ for (var k = 0; k < result.length; k++) {
+ memo.push(result[k])
+ }
+ } else {
+ memo.push(result)
+ }
+ }
+
+ tokens = memo
+ }
+
+ return tokens
+}
+
+/**
+ * Convenience method for passing a string through a pipeline and getting
+ * strings out. This method takes care of wrapping the passed string in a
+ * token and mapping the resulting tokens back to strings.
+ *
+ * @param {string} str - The string to pass through the pipeline.
+ * @param {?object} metadata - Optional metadata to associate with the token
+ * passed to the pipeline.
+ * @returns {string[]}
+ */
+lunr.Pipeline.prototype.runString = function (str, metadata) {
+ var token = new lunr.Token (str, metadata)
+
+ return this.run([token]).map(function (t) {
+ return t.toString()
+ })
+}
+
+/**
+ * Resets the pipeline by removing any existing processors.
+ *
+ */
+lunr.Pipeline.prototype.reset = function () {
+ this._stack = []
+}
+
+/**
+ * Returns a representation of the pipeline ready for serialisation.
+ *
+ * Logs a warning if the function has not been registered.
+ *
+ * @returns {Array}
+ */
+lunr.Pipeline.prototype.toJSON = function () {
+ return this._stack.map(function (fn) {
+ lunr.Pipeline.warnIfFunctionNotRegistered(fn)
+
+ return fn.label
+ })
+}
+/*!
+ * lunr.Vector
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+
+/**
+ * A vector is used to construct the vector space of documents and queries. These
+ * vectors support operations to determine the similarity between two documents or
+ * a document and a query.
+ *
+ * Normally no parameters are required for initializing a vector, but in the case of
+ * loading a previously dumped vector the raw elements can be provided to the constructor.
+ *
+ * For performance reasons vectors are implemented with a flat array, where an elements
+ * index is immediately followed by its value. E.g. [index, value, index, value]. This
+ * allows the underlying array to be as sparse as possible and still offer decent
+ * performance when being used for vector calculations.
+ *
+ * @constructor
+ * @param {Number[]} [elements] - The flat list of element index and element value pairs.
+ */
+lunr.Vector = function (elements) {
+ this._magnitude = 0
+ this.elements = elements || []
+}
+
+
+/**
+ * Calculates the position within the vector to insert a given index.
+ *
+ * This is used internally by insert and upsert. If there are duplicate indexes then
+ * the position is returned as if the value for that index were to be updated, but it
+ * is the callers responsibility to check whether there is a duplicate at that index
+ *
+ * @param {Number} insertIdx - The index at which the element should be inserted.
+ * @returns {Number}
+ */
+lunr.Vector.prototype.positionForIndex = function (index) {
+ // For an empty vector the tuple can be inserted at the beginning
+ if (this.elements.length == 0) {
+ return 0
+ }
+
+ var start = 0,
+ end = this.elements.length / 2,
+ sliceLength = end - start,
+ pivotPoint = Math.floor(sliceLength / 2),
+ pivotIndex = this.elements[pivotPoint * 2]
+
+ while (sliceLength > 1) {
+ if (pivotIndex < index) {
+ start = pivotPoint
+ }
+
+ if (pivotIndex > index) {
+ end = pivotPoint
+ }
+
+ if (pivotIndex == index) {
+ break
+ }
+
+ sliceLength = end - start
+ pivotPoint = start + Math.floor(sliceLength / 2)
+ pivotIndex = this.elements[pivotPoint * 2]
+ }
+
+ if (pivotIndex == index) {
+ return pivotPoint * 2
+ }
+
+ if (pivotIndex > index) {
+ return pivotPoint * 2
+ }
+
+ if (pivotIndex < index) {
+ return (pivotPoint + 1) * 2
+ }
+}
+
+/**
+ * Inserts an element at an index within the vector.
+ *
+ * Does not allow duplicates, will throw an error if there is already an entry
+ * for this index.
+ *
+ * @param {Number} insertIdx - The index at which the element should be inserted.
+ * @param {Number} val - The value to be inserted into the vector.
+ */
+lunr.Vector.prototype.insert = function (insertIdx, val) {
+ this.upsert(insertIdx, val, function () {
+ throw "duplicate index"
+ })
+}
+
+/**
+ * Inserts or updates an existing index within the vector.
+ *
+ * @param {Number} insertIdx - The index at which the element should be inserted.
+ * @param {Number} val - The value to be inserted into the vector.
+ * @param {function} fn - A function that is called for updates, the existing value and the
+ * requested value are passed as arguments
+ */
+lunr.Vector.prototype.upsert = function (insertIdx, val, fn) {
+ this._magnitude = 0
+ var position = this.positionForIndex(insertIdx)
+
+ if (this.elements[position] == insertIdx) {
+ this.elements[position + 1] = fn(this.elements[position + 1], val)
+ } else {
+ this.elements.splice(position, 0, insertIdx, val)
+ }
+}
+
+/**
+ * Calculates the magnitude of this vector.
+ *
+ * @returns {Number}
+ */
+lunr.Vector.prototype.magnitude = function () {
+ if (this._magnitude) return this._magnitude
+
+ var sumOfSquares = 0,
+ elementsLength = this.elements.length
+
+ for (var i = 1; i < elementsLength; i += 2) {
+ var val = this.elements[i]
+ sumOfSquares += val * val
+ }
+
+ return this._magnitude = Math.sqrt(sumOfSquares)
+}
+
+/**
+ * Calculates the dot product of this vector and another vector.
+ *
+ * @param {lunr.Vector} otherVector - The vector to compute the dot product with.
+ * @returns {Number}
+ */
+lunr.Vector.prototype.dot = function (otherVector) {
+ var dotProduct = 0,
+ a = this.elements, b = otherVector.elements,
+ aLen = a.length, bLen = b.length,
+ aVal = 0, bVal = 0,
+ i = 0, j = 0
+
+ while (i < aLen && j < bLen) {
+ aVal = a[i], bVal = b[j]
+ if (aVal < bVal) {
+ i += 2
+ } else if (aVal > bVal) {
+ j += 2
+ } else if (aVal == bVal) {
+ dotProduct += a[i + 1] * b[j + 1]
+ i += 2
+ j += 2
+ }
+ }
+
+ return dotProduct
+}
+
+/**
+ * Calculates the similarity between this vector and another vector.
+ *
+ * @param {lunr.Vector} otherVector - The other vector to calculate the
+ * similarity with.
+ * @returns {Number}
+ */
+lunr.Vector.prototype.similarity = function (otherVector) {
+ return this.dot(otherVector) / this.magnitude() || 0
+}
+
+/**
+ * Converts the vector to an array of the elements within the vector.
+ *
+ * @returns {Number[]}
+ */
+lunr.Vector.prototype.toArray = function () {
+ var output = new Array (this.elements.length / 2)
+
+ for (var i = 1, j = 0; i < this.elements.length; i += 2, j++) {
+ output[j] = this.elements[i]
+ }
+
+ return output
+}
+
+/**
+ * A JSON serializable representation of the vector.
+ *
+ * @returns {Number[]}
+ */
+lunr.Vector.prototype.toJSON = function () {
+ return this.elements
+}
+/* eslint-disable */
+/*!
+ * lunr.stemmer
+ * Copyright (C) 2020 Oliver Nightingale
+ * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt
+ */
+
+/**
+ * lunr.stemmer is an english language stemmer, this is a JavaScript
+ * implementation of the PorterStemmer taken from http://tartarus.org/~martin
+ *
+ * @static
+ * @implements {lunr.PipelineFunction}
+ * @param {lunr.Token} token - The string to stem
+ * @returns {lunr.Token}
+ * @see {@link lunr.Pipeline}
+ * @function
+ */
+lunr.stemmer = (function(){
+ var step2list = {
+ "ational" : "ate",
+ "tional" : "tion",
+ "enci" : "ence",
+ "anci" : "ance",
+ "izer" : "ize",
+ "bli" : "ble",
+ "alli" : "al",
+ "entli" : "ent",
+ "eli" : "e",
+ "ousli" : "ous",
+ "ization" : "ize",
+ "ation" : "ate",
+ "ator" : "ate",
+ "alism" : "al",
+ "iveness" : "ive",
+ "fulness" : "ful",
+ "ousness" : "ous",
+ "aliti" : "al",
+ "iviti" : "ive",
+ "biliti" : "ble",
+ "logi" : "log"
+ },
+
+ step3list = {
+ "icate" : "ic",
+ "ative" : "",
+ "alize" : "al",
+ "iciti" : "ic",
+ "ical" : "ic",
+ "ful" : "",
+ "ness" : ""
+ },
+
+ c = "[^aeiou]", // consonant
+ v = "[aeiouy]", // vowel
+ C = c + "[^aeiouy]*", // consonant sequence
+ V = v + "[aeiou]*", // vowel sequence
+
+ mgr0 = "^(" + C + ")?" + V + C, // [C]VC... is m>0
+ meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$", // [C]VC[V] is m=1
+ mgr1 = "^(" + C + ")?" + V + C + V + C, // [C]VCVC... is m>1
+ s_v = "^(" + C + ")?" + v; // vowel in stem
+
+ var re_mgr0 = new RegExp(mgr0);
+ var re_mgr1 = new RegExp(mgr1);
+ var re_meq1 = new RegExp(meq1);
+ var re_s_v = new RegExp(s_v);
+
+ var re_1a = /^(.+?)(ss|i)es$/;
+ var re2_1a = /^(.+?)([^s])s$/;
+ var re_1b = /^(.+?)eed$/;
+ var re2_1b = /^(.+?)(ed|ing)$/;
+ var re_1b_2 = /.$/;
+ var re2_1b_2 = /(at|bl|iz)$/;
+ var re3_1b_2 = new RegExp("([^aeiouylsz])\\1$");
+ var re4_1b_2 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+
+ var re_1c = /^(.+?[^aeiou])y$/;
+ var re_2 = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
+
+ var re_3 = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
+
+ var re_4 = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
+ var re2_4 = /^(.+?)(s|t)(ion)$/;
+
+ var re_5 = /^(.+?)e$/;
+ var re_5_1 = /ll$/;
+ var re3_5 = new RegExp("^" + C + v + "[^aeiouwxy]$");
+
+ var porterStemmer = function porterStemmer(w) {
+ var stem,
+ suffix,
+ firstch,
+ re,
+ re2,
+ re3,
+ re4;
+
+ if (w.length < 3) { return w; }
+
+ firstch = w.substr(0,1);
+ if (firstch == "y") {
+ w = firstch.toUpperCase() + w.substr(1);
+ }
+
+ // Step 1a
+ re = re_1a
+ re2 = re2_1a;
+
+ if (re.test(w)) { w = w.replace(re,"$1$2"); }
+ else if (re2.test(w)) { w = w.replace(re2,"$1$2"); }
+
+ // Step 1b
+ re = re_1b;
+ re2 = re2_1b;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ re = re_mgr0;
+ if (re.test(fp[1])) {
+ re = re_1b_2;
+ w = w.replace(re,"");
+ }
+ } else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1];
+ re2 = re_s_v;
+ if (re2.test(stem)) {
+ w = stem;
+ re2 = re2_1b_2;
+ re3 = re3_1b_2;
+ re4 = re4_1b_2;
+ if (re2.test(w)) { w = w + "e"; }
+ else if (re3.test(w)) { re = re_1b_2; w = w.replace(re,""); }
+ else if (re4.test(w)) { w = w + "e"; }
+ }
+ }
+
+ // Step 1c - replace suffix y or Y by i if preceded by a non-vowel which is not the first letter of the word (so cry -> cri, by -> by, say -> say)
+ re = re_1c;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ w = stem + "i";
+ }
+
+ // Step 2
+ re = re_2;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = re_mgr0;
+ if (re.test(stem)) {
+ w = stem + step2list[suffix];
+ }
+ }
+
+ // Step 3
+ re = re_3;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ suffix = fp[2];
+ re = re_mgr0;
+ if (re.test(stem)) {
+ w = stem + step3list[suffix];
+ }
+ }
+
+ // Step 4
+ re = re_4;
+ re2 = re2_4;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = re_mgr1;
+ if (re.test(stem)) {
+ w = stem;
+ }
+ } else if (re2.test(w)) {
+ var fp = re2.exec(w);
+ stem = fp[1] + fp[2];
+ re2 = re_mgr1;
+ if (re2.test(stem)) {
+ w = stem;
+ }
+ }
+
+ // Step 5
+ re = re_5;
+ if (re.test(w)) {
+ var fp = re.exec(w);
+ stem = fp[1];
+ re = re_mgr1;
+ re2 = re_meq1;
+ re3 = re3_5;
+ if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) {
+ w = stem;
+ }
+ }
+
+ re = re_5_1;
+ re2 = re_mgr1;
+ if (re.test(w) && re2.test(w)) {
+ re = re_1b_2;
+ w = w.replace(re,"");
+ }
+
+ // and turn initial Y back to y
+
+ if (firstch == "y") {
+ w = firstch.toLowerCase() + w.substr(1);
+ }
+
+ return w;
+ };
+
+ return function (token) {
+ return token.update(porterStemmer);
+ }
+})();
+
+lunr.Pipeline.registerFunction(lunr.stemmer, 'stemmer')
+/*!
+ * lunr.stopWordFilter
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+
+/**
+ * lunr.generateStopWordFilter builds a stopWordFilter function from the provided
+ * list of stop words.
+ *
+ * The built in lunr.stopWordFilter is built using this generator and can be used
+ * to generate custom stopWordFilters for applications or non English languages.
+ *
+ * @function
+ * @param {Array} token The token to pass through the filter
+ * @returns {lunr.PipelineFunction}
+ * @see lunr.Pipeline
+ * @see lunr.stopWordFilter
+ */
+lunr.generateStopWordFilter = function (stopWords) {
+ var words = stopWords.reduce(function (memo, stopWord) {
+ memo[stopWord] = stopWord
+ return memo
+ }, {})
+
+ return function (token) {
+ if (token && words[token.toString()] !== token.toString()) return token
+ }
+}
+
+/**
+ * lunr.stopWordFilter is an English language stop word list filter, any words
+ * contained in the list will not be passed through the filter.
+ *
+ * This is intended to be used in the Pipeline. If the token does not pass the
+ * filter then undefined will be returned.
+ *
+ * @function
+ * @implements {lunr.PipelineFunction}
+ * @params {lunr.Token} token - A token to check for being a stop word.
+ * @returns {lunr.Token}
+ * @see {@link lunr.Pipeline}
+ */
+lunr.stopWordFilter = lunr.generateStopWordFilter([
+ 'a',
+ 'able',
+ 'about',
+ 'across',
+ 'after',
+ 'all',
+ 'almost',
+ 'also',
+ 'am',
+ 'among',
+ 'an',
+ 'and',
+ 'any',
+ 'are',
+ 'as',
+ 'at',
+ 'be',
+ 'because',
+ 'been',
+ 'but',
+ 'by',
+ 'can',
+ 'cannot',
+ 'could',
+ 'dear',
+ 'did',
+ 'do',
+ 'does',
+ 'either',
+ 'else',
+ 'ever',
+ 'every',
+ 'for',
+ 'from',
+ 'get',
+ 'got',
+ 'had',
+ 'has',
+ 'have',
+ 'he',
+ 'her',
+ 'hers',
+ 'him',
+ 'his',
+ 'how',
+ 'however',
+ 'i',
+ 'if',
+ 'in',
+ 'into',
+ 'is',
+ 'it',
+ 'its',
+ 'just',
+ 'least',
+ 'let',
+ 'like',
+ 'likely',
+ 'may',
+ 'me',
+ 'might',
+ 'most',
+ 'must',
+ 'my',
+ 'neither',
+ 'no',
+ 'nor',
+ 'not',
+ 'of',
+ 'off',
+ 'often',
+ 'on',
+ 'only',
+ 'or',
+ 'other',
+ 'our',
+ 'own',
+ 'rather',
+ 'said',
+ 'say',
+ 'says',
+ 'she',
+ 'should',
+ 'since',
+ 'so',
+ 'some',
+ 'than',
+ 'that',
+ 'the',
+ 'their',
+ 'them',
+ 'then',
+ 'there',
+ 'these',
+ 'they',
+ 'this',
+ 'tis',
+ 'to',
+ 'too',
+ 'twas',
+ 'us',
+ 'wants',
+ 'was',
+ 'we',
+ 'were',
+ 'what',
+ 'when',
+ 'where',
+ 'which',
+ 'while',
+ 'who',
+ 'whom',
+ 'why',
+ 'will',
+ 'with',
+ 'would',
+ 'yet',
+ 'you',
+ 'your'
+])
+
+lunr.Pipeline.registerFunction(lunr.stopWordFilter, 'stopWordFilter')
+/*!
+ * lunr.trimmer
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+
+/**
+ * lunr.trimmer is a pipeline function for trimming non word
+ * characters from the beginning and end of tokens before they
+ * enter the index.
+ *
+ * This implementation may not work correctly for non latin
+ * characters and should either be removed or adapted for use
+ * with languages with non-latin characters.
+ *
+ * @static
+ * @implements {lunr.PipelineFunction}
+ * @param {lunr.Token} token The token to pass through the filter
+ * @returns {lunr.Token}
+ * @see lunr.Pipeline
+ */
+lunr.trimmer = function (token) {
+ return token.update(function (s) {
+ return s.replace(/^\W+/, '').replace(/\W+$/, '')
+ })
+}
+
+lunr.Pipeline.registerFunction(lunr.trimmer, 'trimmer')
+/*!
+ * lunr.TokenSet
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+
+/**
+ * A token set is used to store the unique list of all tokens
+ * within an index. Token sets are also used to represent an
+ * incoming query to the index, this query token set and index
+ * token set are then intersected to find which tokens to look
+ * up in the inverted index.
+ *
+ * A token set can hold multiple tokens, as in the case of the
+ * index token set, or it can hold a single token as in the
+ * case of a simple query token set.
+ *
+ * Additionally token sets are used to perform wildcard matching.
+ * Leading, contained and trailing wildcards are supported, and
+ * from this edit distance matching can also be provided.
+ *
+ * Token sets are implemented as a minimal finite state automata,
+ * where both common prefixes and suffixes are shared between tokens.
+ * This helps to reduce the space used for storing the token set.
+ *
+ * @constructor
+ */
+lunr.TokenSet = function () {
+ this.final = false
+ this.edges = {}
+ this.id = lunr.TokenSet._nextId
+ lunr.TokenSet._nextId += 1
+}
+
+/**
+ * Keeps track of the next, auto increment, identifier to assign
+ * to a new tokenSet.
+ *
+ * TokenSets require a unique identifier to be correctly minimised.
+ *
+ * @private
+ */
+lunr.TokenSet._nextId = 1
+
+/**
+ * Creates a TokenSet instance from the given sorted array of words.
+ *
+ * @param {String[]} arr - A sorted array of strings to create the set from.
+ * @returns {lunr.TokenSet}
+ * @throws Will throw an error if the input array is not sorted.
+ */
+lunr.TokenSet.fromArray = function (arr) {
+ var builder = new lunr.TokenSet.Builder
+
+ for (var i = 0, len = arr.length; i < len; i++) {
+ builder.insert(arr[i])
+ }
+
+ builder.finish()
+ return builder.root
+}
+
+/**
+ * Creates a token set from a query clause.
+ *
+ * @private
+ * @param {Object} clause - A single clause from lunr.Query.
+ * @param {string} clause.term - The query clause term.
+ * @param {number} [clause.editDistance] - The optional edit distance for the term.
+ * @returns {lunr.TokenSet}
+ */
+lunr.TokenSet.fromClause = function (clause) {
+ if ('editDistance' in clause) {
+ return lunr.TokenSet.fromFuzzyString(clause.term, clause.editDistance)
+ } else {
+ return lunr.TokenSet.fromString(clause.term)
+ }
+}
+
+/**
+ * Creates a token set representing a single string with a specified
+ * edit distance.
+ *
+ * Insertions, deletions, substitutions and transpositions are each
+ * treated as an edit distance of 1.
+ *
+ * Increasing the allowed edit distance will have a dramatic impact
+ * on the performance of both creating and intersecting these TokenSets.
+ * It is advised to keep the edit distance less than 3.
+ *
+ * @param {string} str - The string to create the token set from.
+ * @param {number} editDistance - The allowed edit distance to match.
+ * @returns {lunr.Vector}
+ */
+lunr.TokenSet.fromFuzzyString = function (str, editDistance) {
+ var root = new lunr.TokenSet
+
+ var stack = [{
+ node: root,
+ editsRemaining: editDistance,
+ str: str
+ }]
+
+ while (stack.length) {
+ var frame = stack.pop()
+
+ // no edit
+ if (frame.str.length > 0) {
+ var char = frame.str.charAt(0),
+ noEditNode
+
+ if (char in frame.node.edges) {
+ noEditNode = frame.node.edges[char]
+ } else {
+ noEditNode = new lunr.TokenSet
+ frame.node.edges[char] = noEditNode
+ }
+
+ if (frame.str.length == 1) {
+ noEditNode.final = true
+ }
+
+ stack.push({
+ node: noEditNode,
+ editsRemaining: frame.editsRemaining,
+ str: frame.str.slice(1)
+ })
+ }
+
+ if (frame.editsRemaining == 0) {
+ continue
+ }
+
+ // insertion
+ if ("*" in frame.node.edges) {
+ var insertionNode = frame.node.edges["*"]
+ } else {
+ var insertionNode = new lunr.TokenSet
+ frame.node.edges["*"] = insertionNode
+ }
+
+ if (frame.str.length == 0) {
+ insertionNode.final = true
+ }
+
+ stack.push({
+ node: insertionNode,
+ editsRemaining: frame.editsRemaining - 1,
+ str: frame.str
+ })
+
+ // deletion
+ // can only do a deletion if we have enough edits remaining
+ // and if there are characters left to delete in the string
+ if (frame.str.length > 1) {
+ stack.push({
+ node: frame.node,
+ editsRemaining: frame.editsRemaining - 1,
+ str: frame.str.slice(1)
+ })
+ }
+
+ // deletion
+ // just removing the last character from the str
+ if (frame.str.length == 1) {
+ frame.node.final = true
+ }
+
+ // substitution
+ // can only do a substitution if we have enough edits remaining
+ // and if there are characters left to substitute
+ if (frame.str.length >= 1) {
+ if ("*" in frame.node.edges) {
+ var substitutionNode = frame.node.edges["*"]
+ } else {
+ var substitutionNode = new lunr.TokenSet
+ frame.node.edges["*"] = substitutionNode
+ }
+
+ if (frame.str.length == 1) {
+ substitutionNode.final = true
+ }
+
+ stack.push({
+ node: substitutionNode,
+ editsRemaining: frame.editsRemaining - 1,
+ str: frame.str.slice(1)
+ })
+ }
+
+ // transposition
+ // can only do a transposition if there are edits remaining
+ // and there are enough characters to transpose
+ if (frame.str.length > 1) {
+ var charA = frame.str.charAt(0),
+ charB = frame.str.charAt(1),
+ transposeNode
+
+ if (charB in frame.node.edges) {
+ transposeNode = frame.node.edges[charB]
+ } else {
+ transposeNode = new lunr.TokenSet
+ frame.node.edges[charB] = transposeNode
+ }
+
+ if (frame.str.length == 1) {
+ transposeNode.final = true
+ }
+
+ stack.push({
+ node: transposeNode,
+ editsRemaining: frame.editsRemaining - 1,
+ str: charA + frame.str.slice(2)
+ })
+ }
+ }
+
+ return root
+}
+
+/**
+ * Creates a TokenSet from a string.
+ *
+ * The string may contain one or more wildcard characters (*)
+ * that will allow wildcard matching when intersecting with
+ * another TokenSet.
+ *
+ * @param {string} str - The string to create a TokenSet from.
+ * @returns {lunr.TokenSet}
+ */
+lunr.TokenSet.fromString = function (str) {
+ var node = new lunr.TokenSet,
+ root = node
+
+ /*
+ * Iterates through all characters within the passed string
+ * appending a node for each character.
+ *
+ * When a wildcard character is found then a self
+ * referencing edge is introduced to continually match
+ * any number of any characters.
+ */
+ for (var i = 0, len = str.length; i < len; i++) {
+ var char = str[i],
+ final = (i == len - 1)
+
+ if (char == "*") {
+ node.edges[char] = node
+ node.final = final
+
+ } else {
+ var next = new lunr.TokenSet
+ next.final = final
+
+ node.edges[char] = next
+ node = next
+ }
+ }
+
+ return root
+}
+
+/**
+ * Converts this TokenSet into an array of strings
+ * contained within the TokenSet.
+ *
+ * This is not intended to be used on a TokenSet that
+ * contains wildcards, in these cases the results are
+ * undefined and are likely to cause an infinite loop.
+ *
+ * @returns {string[]}
+ */
+lunr.TokenSet.prototype.toArray = function () {
+ var words = []
+
+ var stack = [{
+ prefix: "",
+ node: this
+ }]
+
+ while (stack.length) {
+ var frame = stack.pop(),
+ edges = Object.keys(frame.node.edges),
+ len = edges.length
+
+ if (frame.node.final) {
+ /* In Safari, at this point the prefix is sometimes corrupted, see:
+ * https://github.com/olivernn/lunr.js/issues/279 Calling any
+ * String.prototype method forces Safari to "cast" this string to what
+ * it's supposed to be, fixing the bug. */
+ frame.prefix.charAt(0)
+ words.push(frame.prefix)
+ }
+
+ for (var i = 0; i < len; i++) {
+ var edge = edges[i]
+
+ stack.push({
+ prefix: frame.prefix.concat(edge),
+ node: frame.node.edges[edge]
+ })
+ }
+ }
+
+ return words
+}
+
+/**
+ * Generates a string representation of a TokenSet.
+ *
+ * This is intended to allow TokenSets to be used as keys
+ * in objects, largely to aid the construction and minimisation
+ * of a TokenSet. As such it is not designed to be a human
+ * friendly representation of the TokenSet.
+ *
+ * @returns {string}
+ */
+lunr.TokenSet.prototype.toString = function () {
+ // NOTE: Using Object.keys here as this.edges is very likely
+ // to enter 'hash-mode' with many keys being added
+ //
+ // avoiding a for-in loop here as it leads to the function
+ // being de-optimised (at least in V8). From some simple
+ // benchmarks the performance is comparable, but allowing
+ // V8 to optimize may mean easy performance wins in the future.
+
+ if (this._str) {
+ return this._str
+ }
+
+ var str = this.final ? '1' : '0',
+ labels = Object.keys(this.edges).sort(),
+ len = labels.length
+
+ for (var i = 0; i < len; i++) {
+ var label = labels[i],
+ node = this.edges[label]
+
+ str = str + label + node.id
+ }
+
+ return str
+}
+
+/**
+ * Returns a new TokenSet that is the intersection of
+ * this TokenSet and the passed TokenSet.
+ *
+ * This intersection will take into account any wildcards
+ * contained within the TokenSet.
+ *
+ * @param {lunr.TokenSet} b - An other TokenSet to intersect with.
+ * @returns {lunr.TokenSet}
+ */
+lunr.TokenSet.prototype.intersect = function (b) {
+ var output = new lunr.TokenSet,
+ frame = undefined
+
+ var stack = [{
+ qNode: b,
+ output: output,
+ node: this
+ }]
+
+ while (stack.length) {
+ frame = stack.pop()
+
+ // NOTE: As with the #toString method, we are using
+ // Object.keys and a for loop instead of a for-in loop
+ // as both of these objects enter 'hash' mode, causing
+ // the function to be de-optimised in V8
+ var qEdges = Object.keys(frame.qNode.edges),
+ qLen = qEdges.length,
+ nEdges = Object.keys(frame.node.edges),
+ nLen = nEdges.length
+
+ for (var q = 0; q < qLen; q++) {
+ var qEdge = qEdges[q]
+
+ for (var n = 0; n < nLen; n++) {
+ var nEdge = nEdges[n]
+
+ if (nEdge == qEdge || qEdge == '*') {
+ var node = frame.node.edges[nEdge],
+ qNode = frame.qNode.edges[qEdge],
+ final = node.final && qNode.final,
+ next = undefined
+
+ if (nEdge in frame.output.edges) {
+ // an edge already exists for this character
+ // no need to create a new node, just set the finality
+ // bit unless this node is already final
+ next = frame.output.edges[nEdge]
+ next.final = next.final || final
+
+ } else {
+ // no edge exists yet, must create one
+ // set the finality bit and insert it
+ // into the output
+ next = new lunr.TokenSet
+ next.final = final
+ frame.output.edges[nEdge] = next
+ }
+
+ stack.push({
+ qNode: qNode,
+ output: next,
+ node: node
+ })
+ }
+ }
+ }
+ }
+
+ return output
+}
+lunr.TokenSet.Builder = function () {
+ this.previousWord = ""
+ this.root = new lunr.TokenSet
+ this.uncheckedNodes = []
+ this.minimizedNodes = {}
+}
+
+lunr.TokenSet.Builder.prototype.insert = function (word) {
+ var node,
+ commonPrefix = 0
+
+ if (word < this.previousWord) {
+ throw new Error ("Out of order word insertion")
+ }
+
+ for (var i = 0; i < word.length && i < this.previousWord.length; i++) {
+ if (word[i] != this.previousWord[i]) break
+ commonPrefix++
+ }
+
+ this.minimize(commonPrefix)
+
+ if (this.uncheckedNodes.length == 0) {
+ node = this.root
+ } else {
+ node = this.uncheckedNodes[this.uncheckedNodes.length - 1].child
+ }
+
+ for (var i = commonPrefix; i < word.length; i++) {
+ var nextNode = new lunr.TokenSet,
+ char = word[i]
+
+ node.edges[char] = nextNode
+
+ this.uncheckedNodes.push({
+ parent: node,
+ char: char,
+ child: nextNode
+ })
+
+ node = nextNode
+ }
+
+ node.final = true
+ this.previousWord = word
+}
+
+lunr.TokenSet.Builder.prototype.finish = function () {
+ this.minimize(0)
+}
+
+lunr.TokenSet.Builder.prototype.minimize = function (downTo) {
+ for (var i = this.uncheckedNodes.length - 1; i >= downTo; i--) {
+ var node = this.uncheckedNodes[i],
+ childKey = node.child.toString()
+
+ if (childKey in this.minimizedNodes) {
+ node.parent.edges[node.char] = this.minimizedNodes[childKey]
+ } else {
+ // Cache the key for this node since
+ // we know it can't change anymore
+ node.child._str = childKey
+
+ this.minimizedNodes[childKey] = node.child
+ }
+
+ this.uncheckedNodes.pop()
+ }
+}
+/*!
+ * lunr.Index
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+
+/**
+ * An index contains the built index of all documents and provides a query interface
+ * to the index.
+ *
+ * Usually instances of lunr.Index will not be created using this constructor, instead
+ * lunr.Builder should be used to construct new indexes, or lunr.Index.load should be
+ * used to load previously built and serialized indexes.
+ *
+ * @constructor
+ * @param {Object} attrs - The attributes of the built search index.
+ * @param {Object} attrs.invertedIndex - An index of term/field to document reference.
+ * @param {Object} attrs.fieldVectors - Field vectors
+ * @param {lunr.TokenSet} attrs.tokenSet - An set of all corpus tokens.
+ * @param {string[]} attrs.fields - The names of indexed document fields.
+ * @param {lunr.Pipeline} attrs.pipeline - The pipeline to use for search terms.
+ */
+lunr.Index = function (attrs) {
+ this.invertedIndex = attrs.invertedIndex
+ this.fieldVectors = attrs.fieldVectors
+ this.tokenSet = attrs.tokenSet
+ this.fields = attrs.fields
+ this.pipeline = attrs.pipeline
+}
+
+/**
+ * A result contains details of a document matching a search query.
+ * @typedef {Object} lunr.Index~Result
+ * @property {string} ref - The reference of the document this result represents.
+ * @property {number} score - A number between 0 and 1 representing how similar this document is to the query.
+ * @property {lunr.MatchData} matchData - Contains metadata about this match including which term(s) caused the match.
+ */
+
+/**
+ * Although lunr provides the ability to create queries using lunr.Query, it also provides a simple
+ * query language which itself is parsed into an instance of lunr.Query.
+ *
+ * For programmatically building queries it is advised to directly use lunr.Query, the query language
+ * is best used for human entered text rather than program generated text.
+ *
+ * At its simplest queries can just be a single term, e.g. `hello`, multiple terms are also supported
+ * and will be combined with OR, e.g `hello world` will match documents that contain either 'hello'
+ * or 'world', though those that contain both will rank higher in the results.
+ *
+ * Wildcards can be included in terms to match one or more unspecified characters, these wildcards can
+ * be inserted anywhere within the term, and more than one wildcard can exist in a single term. Adding
+ * wildcards will increase the number of documents that will be found but can also have a negative
+ * impact on query performance, especially with wildcards at the beginning of a term.
+ *
+ * Terms can be restricted to specific fields, e.g. `title:hello`, only documents with the term
+ * hello in the title field will match this query. Using a field not present in the index will lead
+ * to an error being thrown.
+ *
+ * Modifiers can also be added to terms, lunr supports edit distance and boost modifiers on terms. A term
+ * boost will make documents matching that term score higher, e.g. `foo^5`. Edit distance is also supported
+ * to provide fuzzy matching, e.g. 'hello~2' will match documents with hello with an edit distance of 2.
+ * Avoid large values for edit distance to improve query performance.
+ *
+ * Each term also supports a presence modifier. By default a term's presence in document is optional, however
+ * this can be changed to either required or prohibited. For a term's presence to be required in a document the
+ * term should be prefixed with a '+', e.g. `+foo bar` is a search for documents that must contain 'foo' and
+ * optionally contain 'bar'. Conversely a leading '-' sets the terms presence to prohibited, i.e. it must not
+ * appear in a document, e.g. `-foo bar` is a search for documents that do not contain 'foo' but may contain 'bar'.
+ *
+ * To escape special characters the backslash character '\' can be used, this allows searches to include
+ * characters that would normally be considered modifiers, e.g. `foo\~2` will search for a term "foo~2" instead
+ * of attempting to apply a boost of 2 to the search term "foo".
+ *
+ * @typedef {string} lunr.Index~QueryString
+ * @example Simple single term query
+ * hello
+ * @example Multiple term query
+ * hello world
+ * @example term scoped to a field
+ * title:hello
+ * @example term with a boost of 10
+ * hello^10
+ * @example term with an edit distance of 2
+ * hello~2
+ * @example terms with presence modifiers
+ * -foo +bar baz
+ */
+
+/**
+ * Performs a search against the index using lunr query syntax.
+ *
+ * Results will be returned sorted by their score, the most relevant results
+ * will be returned first. For details on how the score is calculated, please see
+ * the {@link https://lunrjs.com/guides/searching.html#scoring|guide}.
+ *
+ * For more programmatic querying use lunr.Index#query.
+ *
+ * @param {lunr.Index~QueryString} queryString - A string containing a lunr query.
+ * @throws {lunr.QueryParseError} If the passed query string cannot be parsed.
+ * @returns {lunr.Index~Result[]}
+ */
+lunr.Index.prototype.search = function (queryString) {
+ return this.query(function (query) {
+ var parser = new lunr.QueryParser(queryString, query)
+ parser.parse()
+ })
+}
+
+/**
+ * A query builder callback provides a query object to be used to express
+ * the query to perform on the index.
+ *
+ * @callback lunr.Index~queryBuilder
+ * @param {lunr.Query} query - The query object to build up.
+ * @this lunr.Query
+ */
+
+/**
+ * Performs a query against the index using the yielded lunr.Query object.
+ *
+ * If performing programmatic queries against the index, this method is preferred
+ * over lunr.Index#search so as to avoid the additional query parsing overhead.
+ *
+ * A query object is yielded to the supplied function which should be used to
+ * express the query to be run against the index.
+ *
+ * Note that although this function takes a callback parameter it is _not_ an
+ * asynchronous operation, the callback is just yielded a query object to be
+ * customized.
+ *
+ * @param {lunr.Index~queryBuilder} fn - A function that is used to build the query.
+ * @returns {lunr.Index~Result[]}
+ */
+lunr.Index.prototype.query = function (fn) {
+ // for each query clause
+ // * process terms
+ // * expand terms from token set
+ // * find matching documents and metadata
+ // * get document vectors
+ // * score documents
+
+ var query = new lunr.Query(this.fields),
+ matchingFields = Object.create(null),
+ queryVectors = Object.create(null),
+ termFieldCache = Object.create(null),
+ requiredMatches = Object.create(null),
+ prohibitedMatches = Object.create(null)
+
+ /*
+ * To support field level boosts a query vector is created per
+ * field. An empty vector is eagerly created to support negated
+ * queries.
+ */
+ for (var i = 0; i < this.fields.length; i++) {
+ queryVectors[this.fields[i]] = new lunr.Vector
+ }
+
+ fn.call(query, query)
+
+ for (var i = 0; i < query.clauses.length; i++) {
+ /*
+ * Unless the pipeline has been disabled for this term, which is
+ * the case for terms with wildcards, we need to pass the clause
+ * term through the search pipeline. A pipeline returns an array
+ * of processed terms. Pipeline functions may expand the passed
+ * term, which means we may end up performing multiple index lookups
+ * for a single query term.
+ */
+ var clause = query.clauses[i],
+ terms = null,
+ clauseMatches = lunr.Set.empty
+
+ if (clause.usePipeline) {
+ terms = this.pipeline.runString(clause.term, {
+ fields: clause.fields
+ })
+ } else {
+ terms = [clause.term]
+ }
+
+ for (var m = 0; m < terms.length; m++) {
+ var term = terms[m]
+
+ /*
+ * Each term returned from the pipeline needs to use the same query
+ * clause object, e.g. the same boost and or edit distance. The
+ * simplest way to do this is to re-use the clause object but mutate
+ * its term property.
+ */
+ clause.term = term
+
+ /*
+ * From the term in the clause we create a token set which will then
+ * be used to intersect the indexes token set to get a list of terms
+ * to lookup in the inverted index
+ */
+ var termTokenSet = lunr.TokenSet.fromClause(clause),
+ expandedTerms = this.tokenSet.intersect(termTokenSet).toArray()
+
+ /*
+ * If a term marked as required does not exist in the tokenSet it is
+ * impossible for the search to return any matches. We set all the field
+ * scoped required matches set to empty and stop examining any further
+ * clauses.
+ */
+ if (expandedTerms.length === 0 && clause.presence === lunr.Query.presence.REQUIRED) {
+ for (var k = 0; k < clause.fields.length; k++) {
+ var field = clause.fields[k]
+ requiredMatches[field] = lunr.Set.empty
+ }
+
+ break
+ }
+
+ for (var j = 0; j < expandedTerms.length; j++) {
+ /*
+ * For each term get the posting and termIndex, this is required for
+ * building the query vector.
+ */
+ var expandedTerm = expandedTerms[j],
+ posting = this.invertedIndex[expandedTerm],
+ termIndex = posting._index
+
+ for (var k = 0; k < clause.fields.length; k++) {
+ /*
+ * For each field that this query term is scoped by (by default
+ * all fields are in scope) we need to get all the document refs
+ * that have this term in that field.
+ *
+ * The posting is the entry in the invertedIndex for the matching
+ * term from above.
+ */
+ var field = clause.fields[k],
+ fieldPosting = posting[field],
+ matchingDocumentRefs = Object.keys(fieldPosting),
+ termField = expandedTerm + "/" + field,
+ matchingDocumentsSet = new lunr.Set(matchingDocumentRefs)
+
+ /*
+ * if the presence of this term is required ensure that the matching
+ * documents are added to the set of required matches for this clause.
+ *
+ */
+ if (clause.presence == lunr.Query.presence.REQUIRED) {
+ clauseMatches = clauseMatches.union(matchingDocumentsSet)
+
+ if (requiredMatches[field] === undefined) {
+ requiredMatches[field] = lunr.Set.complete
+ }
+ }
+
+ /*
+ * if the presence of this term is prohibited ensure that the matching
+ * documents are added to the set of prohibited matches for this field,
+ * creating that set if it does not yet exist.
+ */
+ if (clause.presence == lunr.Query.presence.PROHIBITED) {
+ if (prohibitedMatches[field] === undefined) {
+ prohibitedMatches[field] = lunr.Set.empty
+ }
+
+ prohibitedMatches[field] = prohibitedMatches[field].union(matchingDocumentsSet)
+
+ /*
+ * Prohibited matches should not be part of the query vector used for
+ * similarity scoring and no metadata should be extracted so we continue
+ * to the next field
+ */
+ continue
+ }
+
+ /*
+ * The query field vector is populated using the termIndex found for
+ * the term and a unit value with the appropriate boost applied.
+ * Using upsert because there could already be an entry in the vector
+ * for the term we are working with. In that case we just add the scores
+ * together.
+ */
+ queryVectors[field].upsert(termIndex, clause.boost, function (a, b) { return a + b })
+
+ /**
+ * If we've already seen this term, field combo then we've already collected
+ * the matching documents and metadata, no need to go through all that again
+ */
+ if (termFieldCache[termField]) {
+ continue
+ }
+
+ for (var l = 0; l < matchingDocumentRefs.length; l++) {
+ /*
+ * All metadata for this term/field/document triple
+ * are then extracted and collected into an instance
+ * of lunr.MatchData ready to be returned in the query
+ * results
+ */
+ var matchingDocumentRef = matchingDocumentRefs[l],
+ matchingFieldRef = new lunr.FieldRef (matchingDocumentRef, field),
+ metadata = fieldPosting[matchingDocumentRef],
+ fieldMatch
+
+ if ((fieldMatch = matchingFields[matchingFieldRef]) === undefined) {
+ matchingFields[matchingFieldRef] = new lunr.MatchData (expandedTerm, field, metadata)
+ } else {
+ fieldMatch.add(expandedTerm, field, metadata)
+ }
+
+ }
+
+ termFieldCache[termField] = true
+ }
+ }
+ }
+
+ /**
+ * If the presence was required we need to update the requiredMatches field sets.
+ * We do this after all fields for the term have collected their matches because
+ * the clause terms presence is required in _any_ of the fields not _all_ of the
+ * fields.
+ */
+ if (clause.presence === lunr.Query.presence.REQUIRED) {
+ for (var k = 0; k < clause.fields.length; k++) {
+ var field = clause.fields[k]
+ requiredMatches[field] = requiredMatches[field].intersect(clauseMatches)
+ }
+ }
+ }
+
+ /**
+ * Need to combine the field scoped required and prohibited
+ * matching documents into a global set of required and prohibited
+ * matches
+ */
+ var allRequiredMatches = lunr.Set.complete,
+ allProhibitedMatches = lunr.Set.empty
+
+ for (var i = 0; i < this.fields.length; i++) {
+ var field = this.fields[i]
+
+ if (requiredMatches[field]) {
+ allRequiredMatches = allRequiredMatches.intersect(requiredMatches[field])
+ }
+
+ if (prohibitedMatches[field]) {
+ allProhibitedMatches = allProhibitedMatches.union(prohibitedMatches[field])
+ }
+ }
+
+ var matchingFieldRefs = Object.keys(matchingFields),
+ results = [],
+ matches = Object.create(null)
+
+ /*
+ * If the query is negated (contains only prohibited terms)
+ * we need to get _all_ fieldRefs currently existing in the
+ * index. This is only done when we know that the query is
+ * entirely prohibited terms to avoid any cost of getting all
+ * fieldRefs unnecessarily.
+ *
+ * Additionally, blank MatchData must be created to correctly
+ * populate the results.
+ */
+ if (query.isNegated()) {
+ matchingFieldRefs = Object.keys(this.fieldVectors)
+
+ for (var i = 0; i < matchingFieldRefs.length; i++) {
+ var matchingFieldRef = matchingFieldRefs[i]
+ var fieldRef = lunr.FieldRef.fromString(matchingFieldRef)
+ matchingFields[matchingFieldRef] = new lunr.MatchData
+ }
+ }
+
+ for (var i = 0; i < matchingFieldRefs.length; i++) {
+ /*
+ * Currently we have document fields that match the query, but we
+ * need to return documents. The matchData and scores are combined
+ * from multiple fields belonging to the same document.
+ *
+ * Scores are calculated by field, using the query vectors created
+ * above, and combined into a final document score using addition.
+ */
+ var fieldRef = lunr.FieldRef.fromString(matchingFieldRefs[i]),
+ docRef = fieldRef.docRef
+
+ if (!allRequiredMatches.contains(docRef)) {
+ continue
+ }
+
+ if (allProhibitedMatches.contains(docRef)) {
+ continue
+ }
+
+ var fieldVector = this.fieldVectors[fieldRef],
+ score = queryVectors[fieldRef.fieldName].similarity(fieldVector),
+ docMatch
+
+ if ((docMatch = matches[docRef]) !== undefined) {
+ docMatch.score += score
+ docMatch.matchData.combine(matchingFields[fieldRef])
+ } else {
+ var match = {
+ ref: docRef,
+ score: score,
+ matchData: matchingFields[fieldRef]
+ }
+ matches[docRef] = match
+ results.push(match)
+ }
+ }
+
+ /*
+ * Sort the results objects by score, highest first.
+ */
+ return results.sort(function (a, b) {
+ return b.score - a.score
+ })
+}
+
+/**
+ * Prepares the index for JSON serialization.
+ *
+ * The schema for this JSON blob will be described in a
+ * separate JSON schema file.
+ *
+ * @returns {Object}
+ */
+lunr.Index.prototype.toJSON = function () {
+ var invertedIndex = Object.keys(this.invertedIndex)
+ .sort()
+ .map(function (term) {
+ return [term, this.invertedIndex[term]]
+ }, this)
+
+ var fieldVectors = Object.keys(this.fieldVectors)
+ .map(function (ref) {
+ return [ref, this.fieldVectors[ref].toJSON()]
+ }, this)
+
+ return {
+ version: lunr.version,
+ fields: this.fields,
+ fieldVectors: fieldVectors,
+ invertedIndex: invertedIndex,
+ pipeline: this.pipeline.toJSON()
+ }
+}
+
+/**
+ * Loads a previously serialized lunr.Index
+ *
+ * @param {Object} serializedIndex - A previously serialized lunr.Index
+ * @returns {lunr.Index}
+ */
+lunr.Index.load = function (serializedIndex) {
+ var attrs = {},
+ fieldVectors = {},
+ serializedVectors = serializedIndex.fieldVectors,
+ invertedIndex = Object.create(null),
+ serializedInvertedIndex = serializedIndex.invertedIndex,
+ tokenSetBuilder = new lunr.TokenSet.Builder,
+ pipeline = lunr.Pipeline.load(serializedIndex.pipeline)
+
+ if (serializedIndex.version != lunr.version) {
+ lunr.utils.warn("Version mismatch when loading serialised index. Current version of lunr '" + lunr.version + "' does not match serialized index '" + serializedIndex.version + "'")
+ }
+
+ for (var i = 0; i < serializedVectors.length; i++) {
+ var tuple = serializedVectors[i],
+ ref = tuple[0],
+ elements = tuple[1]
+
+ fieldVectors[ref] = new lunr.Vector(elements)
+ }
+
+ for (var i = 0; i < serializedInvertedIndex.length; i++) {
+ var tuple = serializedInvertedIndex[i],
+ term = tuple[0],
+ posting = tuple[1]
+
+ tokenSetBuilder.insert(term)
+ invertedIndex[term] = posting
+ }
+
+ tokenSetBuilder.finish()
+
+ attrs.fields = serializedIndex.fields
+
+ attrs.fieldVectors = fieldVectors
+ attrs.invertedIndex = invertedIndex
+ attrs.tokenSet = tokenSetBuilder.root
+ attrs.pipeline = pipeline
+
+ return new lunr.Index(attrs)
+}
+/*!
+ * lunr.Builder
+ * Copyright (C) 2020 Oliver Nightingale
+ */
+
+/**
+ * lunr.Builder performs indexing on a set of documents and
+ * returns instances of lunr.Index ready for querying.
+ *
+ * All configuration of the index is done via the builder, the
+ * fields to index, the document reference, the text processing
+ * pipeline and document scoring parameters are all set on the
+ * builder before indexing.
+ *
+ * @constructor
+ * @property {string} _ref - Internal reference to the document reference field.
+ * @property {string[]} _fields - Internal reference to the document fields to index.
+ * @property {object} invertedIndex - The inverted index maps terms to document fields.
+ * @property {object} documentTermFrequencies - Keeps track of document term frequencies.
+ * @property {object} documentLengths - Keeps track of the length of documents added to the index.
+ * @property {lunr.tokenizer} tokenizer - Function for splitting strings into tokens for indexing.
+ * @property {lunr.Pipeline} pipeline - The pipeline performs text processing on tokens before indexing.
+ * @property {lunr.Pipeline} searchPipeline - A pipeline for processing search terms before querying the index.
+ * @property {number} documentCount - Keeps track of the total number of documents indexed.
+ * @property {number} _b - A parameter to control field length normalization, setting this to 0 disabled normalization, 1 fully normalizes field lengths, the default value is 0.75.
+ * @property {number} _k1 - A parameter to control how quickly an increase in term frequency results in term frequency saturation, the default value is 1.2.
+ * @property {number} termIndex - A counter incremented for each unique term, used to identify a terms position in the vector space.
+ * @property {array} metadataWhitelist - A list of metadata keys that have been whitelisted for entry in the index.
+ */
+lunr.Builder = function () {
+ this._ref = "id"
+ this._fields = Object.create(null)
+ this._documents = Object.create(null)
+ this.invertedIndex = Object.create(null)
+ this.fieldTermFrequencies = {}
+ this.fieldLengths = {}
+ this.tokenizer = lunr.tokenizer
+ this.pipeline = new lunr.Pipeline
+ this.searchPipeline = new lunr.Pipeline
+ this.documentCount = 0
+ this._b = 0.75
+ this._k1 = 1.2
+ this.termIndex = 0
+ this.metadataWhitelist = []
+}
+
+/**
+ * Sets the document field used as the document reference. Every document must have this field.
+ * The type of this field in the document should be a string, if it is not a string it will be
+ * coerced into a string by calling toString.
+ *
+ * The default ref is 'id'.
+ *
+ * The ref should _not_ be changed during indexing, it should be set before any documents are
+ * added to the index. Changing it during indexing can lead to inconsistent results.
+ *
+ * @param {string} ref - The name of the reference field in the document.
+ */
+lunr.Builder.prototype.ref = function (ref) {
+ this._ref = ref
+}
+
+/**
+ * A function that is used to extract a field from a document.
+ *
+ * Lunr expects a field to be at the top level of a document, if however the field
+ * is deeply nested within a document an extractor function can be used to extract
+ * the right field for indexing.
+ *
+ * @callback fieldExtractor
+ * @param {object} doc - The document being added to the index.
+ * @returns {?(string|object|object[])} obj - The object that will be indexed for this field.
+ * @example Extracting a nested field
+ * function (doc) { return doc.nested.field }
+ */
+
+/**
+ * Adds a field to the list of document fields that will be indexed. Every document being
+ * indexed should have this field. Null values for this field in indexed documents will
+ * not cause errors but will limit the chance of that document being retrieved by searches.
+ *
+ * All fields should be added before adding documents to the index. Adding fields after
+ * a document has been indexed will have no effect on already indexed documents.
+ *
+ * Fields can be boosted at build time. This allows terms within that field to have more
+ * importance when ranking search results. Use a field boost to specify that matches within
+ * one field are more important than other fields.
+ *
+ * @param {string} fieldName - The name of a field to index in all documents.
+ * @param {object} attributes - Optional attributes associated with this field.
+ * @param {number} [attributes.boost=1] - Boost applied to all terms within this field.
+ * @param {fieldExtractor} [attributes.extractor] - Function to extract a field from a document.
+ * @throws {RangeError} fieldName cannot contain unsupported characters '/'
+ */
+lunr.Builder.prototype.field = function (fieldName, attributes) {
+ if (/\//.test(fieldName)) {
+ throw new RangeError ("Field '" + fieldName + "' contains illegal character '/'")
+ }
+
+ this._fields[fieldName] = attributes || {}
+}
+
+/**
+ * A parameter to tune the amount of field length normalisation that is applied when
+ * calculating relevance scores. A value of 0 will completely disable any normalisation
+ * and a value of 1 will fully normalise field lengths. The default is 0.75. Values of b
+ * will be clamped to the range 0 - 1.
+ *
+ * @param {number} number - The value to set for this tuning parameter.
+ */
+lunr.Builder.prototype.b = function (number) {
+ if (number < 0) {
+ this._b = 0
+ } else if (number > 1) {
+ this._b = 1
+ } else {
+ this._b = number
+ }
+}
+
+/**
+ * A parameter that controls the speed at which a rise in term frequency results in term
+ * frequency saturation. The default value is 1.2. Setting this to a higher value will give
+ * slower saturation levels, a lower value will result in quicker saturation.
+ *
+ * @param {number} number - The value to set for this tuning parameter.
+ */
+lunr.Builder.prototype.k1 = function (number) {
+ this._k1 = number
+}
+
+/**
+ * Adds a document to the index.
+ *
+ * Before adding fields to the index the index should have been fully setup, with the document
+ * ref and all fields to index already having been specified.
+ *
+ * The document must have a field name as specified by the ref (by default this is 'id') and
+ * it should have all fields defined for indexing, though null or undefined values will not
+ * cause errors.
+ *
+ * Entire documents can be boosted at build time. Applying a boost to a document indicates that
+ * this document should rank higher in search results than other documents.
+ *
+ * @param {object} doc - The document to add to the index.
+ * @param {object} attributes - Optional attributes associated with this document.
+ * @param {number} [attributes.boost=1] - Boost applied to all terms within this document.
+ */
+lunr.Builder.prototype.add = function (doc, attributes) {
+ var docRef = doc[this._ref],
+ fields = Object.keys(this._fields)
+
+ this._documents[docRef] = attributes || {}
+ this.documentCount += 1
+
+ for (var i = 0; i < fields.length; i++) {
+ var fieldName = fields[i],
+ extractor = this._fields[fieldName].extractor,
+ field = extractor ? extractor(doc) : doc[fieldName],
+ tokens = this.tokenizer(field, {
+ fields: [fieldName]
+ }),
+ terms = this.pipeline.run(tokens),
+ fieldRef = new lunr.FieldRef (docRef, fieldName),
+ fieldTerms = Object.create(null)
+
+ this.fieldTermFrequencies[fieldRef] = fieldTerms
+ this.fieldLengths[fieldRef] = 0
+
+ // store the length of this field for this document
+ this.fieldLengths[fieldRef] += terms.length
+
+ // calculate term frequencies for this field
+ for (var j = 0; j < terms.length; j++) {
+ var term = terms[j]
+
+ if (fieldTerms[term] == undefined) {
+ fieldTerms[term] = 0
+ }
+
+ fieldTerms[term] += 1
+
+ // add to inverted index
+ // create an initial posting if one doesn't exist
+ if (this.invertedIndex[term] == undefined) {
+ var posting = Object.create(null)
+ posting["_index"] = this.termIndex
+ this.termIndex += 1
+
+ for (var k = 0; k < fields.length; k++) {
+ posting[fields[k]] = Object.create(null)
+ }
+
+ this.invertedIndex[term] = posting
+ }
+
+ // add an entry for this term/fieldName/docRef to the invertedIndex
+ if (this.invertedIndex[term][fieldName][docRef] == undefined) {
+ this.invertedIndex[term][fieldName][docRef] = Object.create(null)
+ }
+
+ // store all whitelisted metadata about this token in the
+ // inverted index
+ for (var l = 0; l < this.metadataWhitelist.length; l++) {
+ var metadataKey = this.metadataWhitelist[l],
+ metadata = term.metadata[metadataKey]
+
+ if (this.invertedIndex[term][fieldName][docRef][metadataKey] == undefined) {
+ this.invertedIndex[term][fieldName][docRef][metadataKey] = []
+ }
+
+ this.invertedIndex[term][fieldName][docRef][metadataKey].push(metadata)
+ }
+ }
+
+ }
+}
+
+/**
+ * Calculates the average document length for this index
+ *
+ * @private
+ */
+lunr.Builder.prototype.calculateAverageFieldLengths = function () {
+
+ var fieldRefs = Object.keys(this.fieldLengths),
+ numberOfFields = fieldRefs.length,
+ accumulator = {},
+ documentsWithField = {}
+
+ for (var i = 0; i < numberOfFields; i++) {
+ var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),
+ field = fieldRef.fieldName
+
+ documentsWithField[field] || (documentsWithField[field] = 0)
+ documentsWithField[field] += 1
+
+ accumulator[field] || (accumulator[field] = 0)
+ accumulator[field] += this.fieldLengths[fieldRef]
+ }
+
+ var fields = Object.keys(this._fields)
+
+ for (var i = 0; i < fields.length; i++) {
+ var fieldName = fields[i]
+ accumulator[fieldName] = accumulator[fieldName] / documentsWithField[fieldName]
+ }
+
+ this.averageFieldLength = accumulator
+}
+
+/**
+ * Builds a vector space model of every document using lunr.Vector
+ *
+ * @private
+ */
+lunr.Builder.prototype.createFieldVectors = function () {
+ var fieldVectors = {},
+ fieldRefs = Object.keys(this.fieldTermFrequencies),
+ fieldRefsLength = fieldRefs.length,
+ termIdfCache = Object.create(null)
+
+ for (var i = 0; i < fieldRefsLength; i++) {
+ var fieldRef = lunr.FieldRef.fromString(fieldRefs[i]),
+ fieldName = fieldRef.fieldName,
+ fieldLength = this.fieldLengths[fieldRef],
+ fieldVector = new lunr.Vector,
+ termFrequencies = this.fieldTermFrequencies[fieldRef],
+ terms = Object.keys(termFrequencies),
+ termsLength = terms.length
+
+
+ var fieldBoost = this._fields[fieldName].boost || 1,
+ docBoost = this._documents[fieldRef.docRef].boost || 1
+
+ for (var j = 0; j < termsLength; j++) {
+ var term = terms[j],
+ tf = termFrequencies[term],
+ termIndex = this.invertedIndex[term]._index,
+ idf, score, scoreWithPrecision
+
+ if (termIdfCache[term] === undefined) {
+ idf = lunr.idf(this.invertedIndex[term], this.documentCount)
+ termIdfCache[term] = idf
+ } else {
+ idf = termIdfCache[term]
+ }
+
+ score = idf * ((this._k1 + 1) * tf) / (this._k1 * (1 - this._b + this._b * (fieldLength / this.averageFieldLength[fieldName])) + tf)
+ score *= fieldBoost
+ score *= docBoost
+ scoreWithPrecision = Math.round(score * 1000) / 1000
+ // Converts 1.23456789 to 1.234.
+ // Reducing the precision so that the vectors take up less
+ // space when serialised. Doing it now so that they behave
+ // the same before and after serialisation. Also, this is
+ // the fastest approach to reducing a number's precision in
+ // JavaScript.
+
+ fieldVector.insert(termIndex, scoreWithPrecision)
+ }
+
+ fieldVectors[fieldRef] = fieldVector
+ }
+
+ this.fieldVectors = fieldVectors
+}
+
+/**
+ * Creates a token set of all tokens in the index using lunr.TokenSet
+ *
+ * @private
+ */
+lunr.Builder.prototype.createTokenSet = function () {
+ this.tokenSet = lunr.TokenSet.fromArray(
+ Object.keys(this.invertedIndex).sort()
+ )
+}
+
+/**
+ * Builds the index, creating an instance of lunr.Index.
+ *
+ * This completes the indexing process and should only be called
+ * once all documents have been added to the index.
+ *
+ * @returns {lunr.Index}
+ */
+lunr.Builder.prototype.build = function () {
+ this.calculateAverageFieldLengths()
+ this.createFieldVectors()
+ this.createTokenSet()
+
+ return new lunr.Index({
+ invertedIndex: this.invertedIndex,
+ fieldVectors: this.fieldVectors,
+ tokenSet: this.tokenSet,
+ fields: Object.keys(this._fields),
+ pipeline: this.searchPipeline
+ })
+}
+
+/**
+ * Applies a plugin to the index builder.
+ *
+ * A plugin is a function that is called with the index builder as its context.
+ * Plugins can be used to customise or extend the behaviour of the index
+ * in some way. A plugin is just a function, that encapsulated the custom
+ * behaviour that should be applied when building the index.
+ *
+ * The plugin function will be called with the index builder as its argument, additional
+ * arguments can also be passed when calling use. The function will be called
+ * with the index builder as its context.
+ *
+ * @param {Function} plugin The plugin to apply.
+ */
+lunr.Builder.prototype.use = function (fn) {
+ var args = Array.prototype.slice.call(arguments, 1)
+ args.unshift(this)
+ fn.apply(this, args)
+}
+/**
+ * Contains and collects metadata about a matching document.
+ * A single instance of lunr.MatchData is returned as part of every
+ * lunr.Index~Result.
+ *
+ * @constructor
+ * @param {string} term - The term this match data is associated with
+ * @param {string} field - The field in which the term was found
+ * @param {object} metadata - The metadata recorded about this term in this field
+ * @property {object} metadata - A cloned collection of metadata associated with this document.
+ * @see {@link lunr.Index~Result}
+ */
+lunr.MatchData = function (term, field, metadata) {
+ var clonedMetadata = Object.create(null),
+ metadataKeys = Object.keys(metadata || {})
+
+ // Cloning the metadata to prevent the original
+ // being mutated during match data combination.
+ // Metadata is kept in an array within the inverted
+ // index so cloning the data can be done with
+ // Array#slice
+ for (var i = 0; i < metadataKeys.length; i++) {
+ var key = metadataKeys[i]
+ clonedMetadata[key] = metadata[key].slice()
+ }
+
+ this.metadata = Object.create(null)
+
+ if (term !== undefined) {
+ this.metadata[term] = Object.create(null)
+ this.metadata[term][field] = clonedMetadata
+ }
+}
+
+/**
+ * An instance of lunr.MatchData will be created for every term that matches a
+ * document. However only one instance is required in a lunr.Index~Result. This
+ * method combines metadata from another instance of lunr.MatchData with this
+ * objects metadata.
+ *
+ * @param {lunr.MatchData} otherMatchData - Another instance of match data to merge with this one.
+ * @see {@link lunr.Index~Result}
+ */
+lunr.MatchData.prototype.combine = function (otherMatchData) {
+ var terms = Object.keys(otherMatchData.metadata)
+
+ for (var i = 0; i < terms.length; i++) {
+ var term = terms[i],
+ fields = Object.keys(otherMatchData.metadata[term])
+
+ if (this.metadata[term] == undefined) {
+ this.metadata[term] = Object.create(null)
+ }
+
+ for (var j = 0; j < fields.length; j++) {
+ var field = fields[j],
+ keys = Object.keys(otherMatchData.metadata[term][field])
+
+ if (this.metadata[term][field] == undefined) {
+ this.metadata[term][field] = Object.create(null)
+ }
+
+ for (var k = 0; k < keys.length; k++) {
+ var key = keys[k]
+
+ if (this.metadata[term][field][key] == undefined) {
+ this.metadata[term][field][key] = otherMatchData.metadata[term][field][key]
+ } else {
+ this.metadata[term][field][key] = this.metadata[term][field][key].concat(otherMatchData.metadata[term][field][key])
+ }
+
+ }
+ }
+ }
+}
+
+/**
+ * Add metadata for a term/field pair to this instance of match data.
+ *
+ * @param {string} term - The term this match data is associated with
+ * @param {string} field - The field in which the term was found
+ * @param {object} metadata - The metadata recorded about this term in this field
+ */
+lunr.MatchData.prototype.add = function (term, field, metadata) {
+ if (!(term in this.metadata)) {
+ this.metadata[term] = Object.create(null)
+ this.metadata[term][field] = metadata
+ return
+ }
+
+ if (!(field in this.metadata[term])) {
+ this.metadata[term][field] = metadata
+ return
+ }
+
+ var metadataKeys = Object.keys(metadata)
+
+ for (var i = 0; i < metadataKeys.length; i++) {
+ var key = metadataKeys[i]
+
+ if (key in this.metadata[term][field]) {
+ this.metadata[term][field][key] = this.metadata[term][field][key].concat(metadata[key])
+ } else {
+ this.metadata[term][field][key] = metadata[key]
+ }
+ }
+}
+/**
+ * A lunr.Query provides a programmatic way of defining queries to be performed
+ * against a {@link lunr.Index}.
+ *
+ * Prefer constructing a lunr.Query using the {@link lunr.Index#query} method
+ * so the query object is pre-initialized with the right index fields.
+ *
+ * @constructor
+ * @property {lunr.Query~Clause[]} clauses - An array of query clauses.
+ * @property {string[]} allFields - An array of all available fields in a lunr.Index.
+ */
+lunr.Query = function (allFields) {
+ this.clauses = []
+ this.allFields = allFields
+}
+
+/**
+ * Constants for indicating what kind of automatic wildcard insertion will be used when constructing a query clause.
+ *
+ * This allows wildcards to be added to the beginning and end of a term without having to manually do any string
+ * concatenation.
+ *
+ * The wildcard constants can be bitwise combined to select both leading and trailing wildcards.
+ *
+ * @constant
+ * @default
+ * @property {number} wildcard.NONE - The term will have no wildcards inserted, this is the default behaviour
+ * @property {number} wildcard.LEADING - Prepend the term with a wildcard, unless a leading wildcard already exists
+ * @property {number} wildcard.TRAILING - Append a wildcard to the term, unless a trailing wildcard already exists
+ * @see lunr.Query~Clause
+ * @see lunr.Query#clause
+ * @see lunr.Query#term
+ * @example query term with trailing wildcard
+ * query.term('foo', { wildcard: lunr.Query.wildcard.TRAILING })
+ * @example query term with leading and trailing wildcard
+ * query.term('foo', {
+ * wildcard: lunr.Query.wildcard.LEADING | lunr.Query.wildcard.TRAILING
+ * })
+ */
+
+lunr.Query.wildcard = new String ("*")
+lunr.Query.wildcard.NONE = 0
+lunr.Query.wildcard.LEADING = 1
+lunr.Query.wildcard.TRAILING = 2
+
+/**
+ * Constants for indicating what kind of presence a term must have in matching documents.
+ *
+ * @constant
+ * @enum {number}
+ * @see lunr.Query~Clause
+ * @see lunr.Query#clause
+ * @see lunr.Query#term
+ * @example query term with required presence
+ * query.term('foo', { presence: lunr.Query.presence.REQUIRED })
+ */
+lunr.Query.presence = {
+ /**
+ * Term's presence in a document is optional, this is the default value.
+ */
+ OPTIONAL: 1,
+
+ /**
+ * Term's presence in a document is required, documents that do not contain
+ * this term will not be returned.
+ */
+ REQUIRED: 2,
+
+ /**
+ * Term's presence in a document is prohibited, documents that do contain
+ * this term will not be returned.
+ */
+ PROHIBITED: 3
+}
+
+/**
+ * A single clause in a {@link lunr.Query} contains a term and details on how to
+ * match that term against a {@link lunr.Index}.
+ *
+ * @typedef {Object} lunr.Query~Clause
+ * @property {string[]} fields - The fields in an index this clause should be matched against.
+ * @property {number} [boost=1] - Any boost that should be applied when matching this clause.
+ * @property {number} [editDistance] - Whether the term should have fuzzy matching applied, and how fuzzy the match should be.
+ * @property {boolean} [usePipeline] - Whether the term should be passed through the search pipeline.
+ * @property {number} [wildcard=lunr.Query.wildcard.NONE] - Whether the term should have wildcards appended or prepended.
+ * @property {number} [presence=lunr.Query.presence.OPTIONAL] - The terms presence in any matching documents.
+ */
+
+/**
+ * Adds a {@link lunr.Query~Clause} to this query.
+ *
+ * Unless the clause contains the fields to be matched all fields will be matched. In addition
+ * a default boost of 1 is applied to the clause.
+ *
+ * @param {lunr.Query~Clause} clause - The clause to add to this query.
+ * @see lunr.Query~Clause
+ * @returns {lunr.Query}
+ */
+lunr.Query.prototype.clause = function (clause) {
+ if (!('fields' in clause)) {
+ clause.fields = this.allFields
+ }
+
+ if (!('boost' in clause)) {
+ clause.boost = 1
+ }
+
+ if (!('usePipeline' in clause)) {
+ clause.usePipeline = true
+ }
+
+ if (!('wildcard' in clause)) {
+ clause.wildcard = lunr.Query.wildcard.NONE
+ }
+
+ if ((clause.wildcard & lunr.Query.wildcard.LEADING) && (clause.term.charAt(0) != lunr.Query.wildcard)) {
+ clause.term = "*" + clause.term
+ }
+
+ if ((clause.wildcard & lunr.Query.wildcard.TRAILING) && (clause.term.slice(-1) != lunr.Query.wildcard)) {
+ clause.term = "" + clause.term + "*"
+ }
+
+ if (!('presence' in clause)) {
+ clause.presence = lunr.Query.presence.OPTIONAL
+ }
+
+ this.clauses.push(clause)
+
+ return this
+}
+
+/**
+ * A negated query is one in which every clause has a presence of
+ * prohibited. These queries require some special processing to return
+ * the expected results.
+ *
+ * @returns boolean
+ */
+lunr.Query.prototype.isNegated = function () {
+ for (var i = 0; i < this.clauses.length; i++) {
+ if (this.clauses[i].presence != lunr.Query.presence.PROHIBITED) {
+ return false
+ }
+ }
+
+ return true
+}
+
+/**
+ * Adds a term to the current query, under the covers this will create a {@link lunr.Query~Clause}
+ * to the list of clauses that make up this query.
+ *
+ * The term is used as is, i.e. no tokenization will be performed by this method. Instead conversion
+ * to a token or token-like string should be done before calling this method.
+ *
+ * The term will be converted to a string by calling `toString`. Multiple terms can be passed as an
+ * array, each term in the array will share the same options.
+ *
+ * @param {object|object[]} term - The term(s) to add to the query.
+ * @param {object} [options] - Any additional properties to add to the query clause.
+ * @returns {lunr.Query}
+ * @see lunr.Query#clause
+ * @see lunr.Query~Clause
+ * @example adding a single term to a query
+ * query.term("foo")
+ * @example adding a single term to a query and specifying search fields, term boost and automatic trailing wildcard
+ * query.term("foo", {
+ * fields: ["title"],
+ * boost: 10,
+ * wildcard: lunr.Query.wildcard.TRAILING
+ * })
+ * @example using lunr.tokenizer to convert a string to tokens before using them as terms
+ * query.term(lunr.tokenizer("foo bar"))
+ */
+lunr.Query.prototype.term = function (term, options) {
+ if (Array.isArray(term)) {
+ term.forEach(function (t) { this.term(t, lunr.utils.clone(options)) }, this)
+ return this
+ }
+
+ var clause = options || {}
+ clause.term = term.toString()
+
+ this.clause(clause)
+
+ return this
+}
+lunr.QueryParseError = function (message, start, end) {
+ this.name = "QueryParseError"
+ this.message = message
+ this.start = start
+ this.end = end
+}
+
+lunr.QueryParseError.prototype = new Error
+lunr.QueryLexer = function (str) {
+ this.lexemes = []
+ this.str = str
+ this.length = str.length
+ this.pos = 0
+ this.start = 0
+ this.escapeCharPositions = []
+}
+
+lunr.QueryLexer.prototype.run = function () {
+ var state = lunr.QueryLexer.lexText
+
+ while (state) {
+ state = state(this)
+ }
+}
+
+lunr.QueryLexer.prototype.sliceString = function () {
+ var subSlices = [],
+ sliceStart = this.start,
+ sliceEnd = this.pos
+
+ for (var i = 0; i < this.escapeCharPositions.length; i++) {
+ sliceEnd = this.escapeCharPositions[i]
+ subSlices.push(this.str.slice(sliceStart, sliceEnd))
+ sliceStart = sliceEnd + 1
+ }
+
+ subSlices.push(this.str.slice(sliceStart, this.pos))
+ this.escapeCharPositions.length = 0
+
+ return subSlices.join('')
+}
+
+lunr.QueryLexer.prototype.emit = function (type) {
+ this.lexemes.push({
+ type: type,
+ str: this.sliceString(),
+ start: this.start,
+ end: this.pos
+ })
+
+ this.start = this.pos
+}
+
+lunr.QueryLexer.prototype.escapeCharacter = function () {
+ this.escapeCharPositions.push(this.pos - 1)
+ this.pos += 1
+}
+
+lunr.QueryLexer.prototype.next = function () {
+ if (this.pos >= this.length) {
+ return lunr.QueryLexer.EOS
+ }
+
+ var char = this.str.charAt(this.pos)
+ this.pos += 1
+ return char
+}
+
+lunr.QueryLexer.prototype.width = function () {
+ return this.pos - this.start
+}
+
+lunr.QueryLexer.prototype.ignore = function () {
+ if (this.start == this.pos) {
+ this.pos += 1
+ }
+
+ this.start = this.pos
+}
+
+lunr.QueryLexer.prototype.backup = function () {
+ this.pos -= 1
+}
+
+lunr.QueryLexer.prototype.acceptDigitRun = function () {
+ var char, charCode
+
+ do {
+ char = this.next()
+ charCode = char.charCodeAt(0)
+ } while (charCode > 47 && charCode < 58)
+
+ if (char != lunr.QueryLexer.EOS) {
+ this.backup()
+ }
+}
+
+lunr.QueryLexer.prototype.more = function () {
+ return this.pos < this.length
+}
+
+lunr.QueryLexer.EOS = 'EOS'
+lunr.QueryLexer.FIELD = 'FIELD'
+lunr.QueryLexer.TERM = 'TERM'
+lunr.QueryLexer.EDIT_DISTANCE = 'EDIT_DISTANCE'
+lunr.QueryLexer.BOOST = 'BOOST'
+lunr.QueryLexer.PRESENCE = 'PRESENCE'
+
+lunr.QueryLexer.lexField = function (lexer) {
+ lexer.backup()
+ lexer.emit(lunr.QueryLexer.FIELD)
+ lexer.ignore()
+ return lunr.QueryLexer.lexText
+}
+
+lunr.QueryLexer.lexTerm = function (lexer) {
+ if (lexer.width() > 1) {
+ lexer.backup()
+ lexer.emit(lunr.QueryLexer.TERM)
+ }
+
+ lexer.ignore()
+
+ if (lexer.more()) {
+ return lunr.QueryLexer.lexText
+ }
+}
+
+lunr.QueryLexer.lexEditDistance = function (lexer) {
+ lexer.ignore()
+ lexer.acceptDigitRun()
+ lexer.emit(lunr.QueryLexer.EDIT_DISTANCE)
+ return lunr.QueryLexer.lexText
+}
+
+lunr.QueryLexer.lexBoost = function (lexer) {
+ lexer.ignore()
+ lexer.acceptDigitRun()
+ lexer.emit(lunr.QueryLexer.BOOST)
+ return lunr.QueryLexer.lexText
+}
+
+lunr.QueryLexer.lexEOS = function (lexer) {
+ if (lexer.width() > 0) {
+ lexer.emit(lunr.QueryLexer.TERM)
+ }
+}
+
+// This matches the separator used when tokenising fields
+// within a document. These should match otherwise it is
+// not possible to search for some tokens within a document.
+//
+// It is possible for the user to change the separator on the
+// tokenizer so it _might_ clash with any other of the special
+// characters already used within the search string, e.g. :.
+//
+// This means that it is possible to change the separator in
+// such a way that makes some words unsearchable using a search
+// string.
+lunr.QueryLexer.termSeparator = lunr.tokenizer.separator
+
+lunr.QueryLexer.lexText = function (lexer) {
+ while (true) {
+ var char = lexer.next()
+
+ if (char == lunr.QueryLexer.EOS) {
+ return lunr.QueryLexer.lexEOS
+ }
+
+ // Escape character is '\'
+ if (char.charCodeAt(0) == 92) {
+ lexer.escapeCharacter()
+ continue
+ }
+
+ if (char == ":") {
+ return lunr.QueryLexer.lexField
+ }
+
+ if (char == "~") {
+ lexer.backup()
+ if (lexer.width() > 0) {
+ lexer.emit(lunr.QueryLexer.TERM)
+ }
+ return lunr.QueryLexer.lexEditDistance
+ }
+
+ if (char == "^") {
+ lexer.backup()
+ if (lexer.width() > 0) {
+ lexer.emit(lunr.QueryLexer.TERM)
+ }
+ return lunr.QueryLexer.lexBoost
+ }
+
+ // "+" indicates term presence is required
+ // checking for length to ensure that only
+ // leading "+" are considered
+ if (char == "+" && lexer.width() === 1) {
+ lexer.emit(lunr.QueryLexer.PRESENCE)
+ return lunr.QueryLexer.lexText
+ }
+
+ // "-" indicates term presence is prohibited
+ // checking for length to ensure that only
+ // leading "-" are considered
+ if (char == "-" && lexer.width() === 1) {
+ lexer.emit(lunr.QueryLexer.PRESENCE)
+ return lunr.QueryLexer.lexText
+ }
+
+ if (char.match(lunr.QueryLexer.termSeparator)) {
+ return lunr.QueryLexer.lexTerm
+ }
+ }
+}
+
+lunr.QueryParser = function (str, query) {
+ this.lexer = new lunr.QueryLexer (str)
+ this.query = query
+ this.currentClause = {}
+ this.lexemeIdx = 0
+}
+
+lunr.QueryParser.prototype.parse = function () {
+ this.lexer.run()
+ this.lexemes = this.lexer.lexemes
+
+ var state = lunr.QueryParser.parseClause
+
+ while (state) {
+ state = state(this)
+ }
+
+ return this.query
+}
+
+lunr.QueryParser.prototype.peekLexeme = function () {
+ return this.lexemes[this.lexemeIdx]
+}
+
+lunr.QueryParser.prototype.consumeLexeme = function () {
+ var lexeme = this.peekLexeme()
+ this.lexemeIdx += 1
+ return lexeme
+}
+
+lunr.QueryParser.prototype.nextClause = function () {
+ var completedClause = this.currentClause
+ this.query.clause(completedClause)
+ this.currentClause = {}
+}
+
+lunr.QueryParser.parseClause = function (parser) {
+ var lexeme = parser.peekLexeme()
+
+ if (lexeme == undefined) {
+ return
+ }
+
+ switch (lexeme.type) {
+ case lunr.QueryLexer.PRESENCE:
+ return lunr.QueryParser.parsePresence
+ case lunr.QueryLexer.FIELD:
+ return lunr.QueryParser.parseField
+ case lunr.QueryLexer.TERM:
+ return lunr.QueryParser.parseTerm
+ default:
+ var errorMessage = "expected either a field or a term, found " + lexeme.type
+
+ if (lexeme.str.length >= 1) {
+ errorMessage += " with value '" + lexeme.str + "'"
+ }
+
+ throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
+ }
+}
+
+lunr.QueryParser.parsePresence = function (parser) {
+ var lexeme = parser.consumeLexeme()
+
+ if (lexeme == undefined) {
+ return
+ }
+
+ switch (lexeme.str) {
+ case "-":
+ parser.currentClause.presence = lunr.Query.presence.PROHIBITED
+ break
+ case "+":
+ parser.currentClause.presence = lunr.Query.presence.REQUIRED
+ break
+ default:
+ var errorMessage = "unrecognised presence operator'" + lexeme.str + "'"
+ throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
+ }
+
+ var nextLexeme = parser.peekLexeme()
+
+ if (nextLexeme == undefined) {
+ var errorMessage = "expecting term or field, found nothing"
+ throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
+ }
+
+ switch (nextLexeme.type) {
+ case lunr.QueryLexer.FIELD:
+ return lunr.QueryParser.parseField
+ case lunr.QueryLexer.TERM:
+ return lunr.QueryParser.parseTerm
+ default:
+ var errorMessage = "expecting term or field, found '" + nextLexeme.type + "'"
+ throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
+ }
+}
+
+lunr.QueryParser.parseField = function (parser) {
+ var lexeme = parser.consumeLexeme()
+
+ if (lexeme == undefined) {
+ return
+ }
+
+ if (parser.query.allFields.indexOf(lexeme.str) == -1) {
+ var possibleFields = parser.query.allFields.map(function (f) { return "'" + f + "'" }).join(', '),
+ errorMessage = "unrecognised field '" + lexeme.str + "', possible fields: " + possibleFields
+
+ throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
+ }
+
+ parser.currentClause.fields = [lexeme.str]
+
+ var nextLexeme = parser.peekLexeme()
+
+ if (nextLexeme == undefined) {
+ var errorMessage = "expecting term, found nothing"
+ throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
+ }
+
+ switch (nextLexeme.type) {
+ case lunr.QueryLexer.TERM:
+ return lunr.QueryParser.parseTerm
+ default:
+ var errorMessage = "expecting term, found '" + nextLexeme.type + "'"
+ throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
+ }
+}
+
+lunr.QueryParser.parseTerm = function (parser) {
+ var lexeme = parser.consumeLexeme()
+
+ if (lexeme == undefined) {
+ return
+ }
+
+ parser.currentClause.term = lexeme.str.toLowerCase()
+
+ if (lexeme.str.indexOf("*") != -1) {
+ parser.currentClause.usePipeline = false
+ }
+
+ var nextLexeme = parser.peekLexeme()
+
+ if (nextLexeme == undefined) {
+ parser.nextClause()
+ return
+ }
+
+ switch (nextLexeme.type) {
+ case lunr.QueryLexer.TERM:
+ parser.nextClause()
+ return lunr.QueryParser.parseTerm
+ case lunr.QueryLexer.FIELD:
+ parser.nextClause()
+ return lunr.QueryParser.parseField
+ case lunr.QueryLexer.EDIT_DISTANCE:
+ return lunr.QueryParser.parseEditDistance
+ case lunr.QueryLexer.BOOST:
+ return lunr.QueryParser.parseBoost
+ case lunr.QueryLexer.PRESENCE:
+ parser.nextClause()
+ return lunr.QueryParser.parsePresence
+ default:
+ var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
+ throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
+ }
+}
+
+lunr.QueryParser.parseEditDistance = function (parser) {
+ var lexeme = parser.consumeLexeme()
+
+ if (lexeme == undefined) {
+ return
+ }
+
+ var editDistance = parseInt(lexeme.str, 10)
+
+ if (isNaN(editDistance)) {
+ var errorMessage = "edit distance must be numeric"
+ throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
+ }
+
+ parser.currentClause.editDistance = editDistance
+
+ var nextLexeme = parser.peekLexeme()
+
+ if (nextLexeme == undefined) {
+ parser.nextClause()
+ return
+ }
+
+ switch (nextLexeme.type) {
+ case lunr.QueryLexer.TERM:
+ parser.nextClause()
+ return lunr.QueryParser.parseTerm
+ case lunr.QueryLexer.FIELD:
+ parser.nextClause()
+ return lunr.QueryParser.parseField
+ case lunr.QueryLexer.EDIT_DISTANCE:
+ return lunr.QueryParser.parseEditDistance
+ case lunr.QueryLexer.BOOST:
+ return lunr.QueryParser.parseBoost
+ case lunr.QueryLexer.PRESENCE:
+ parser.nextClause()
+ return lunr.QueryParser.parsePresence
+ default:
+ var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
+ throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
+ }
+}
+
+lunr.QueryParser.parseBoost = function (parser) {
+ var lexeme = parser.consumeLexeme()
+
+ if (lexeme == undefined) {
+ return
+ }
+
+ var boost = parseInt(lexeme.str, 10)
+
+ if (isNaN(boost)) {
+ var errorMessage = "boost must be numeric"
+ throw new lunr.QueryParseError (errorMessage, lexeme.start, lexeme.end)
+ }
+
+ parser.currentClause.boost = boost
+
+ var nextLexeme = parser.peekLexeme()
+
+ if (nextLexeme == undefined) {
+ parser.nextClause()
+ return
+ }
+
+ switch (nextLexeme.type) {
+ case lunr.QueryLexer.TERM:
+ parser.nextClause()
+ return lunr.QueryParser.parseTerm
+ case lunr.QueryLexer.FIELD:
+ parser.nextClause()
+ return lunr.QueryParser.parseField
+ case lunr.QueryLexer.EDIT_DISTANCE:
+ return lunr.QueryParser.parseEditDistance
+ case lunr.QueryLexer.BOOST:
+ return lunr.QueryParser.parseBoost
+ case lunr.QueryLexer.PRESENCE:
+ parser.nextClause()
+ return lunr.QueryParser.parsePresence
+ default:
+ var errorMessage = "Unexpected lexeme type '" + nextLexeme.type + "'"
+ throw new lunr.QueryParseError (errorMessage, nextLexeme.start, nextLexeme.end)
+ }
+}
+
+ /**
+ * export the module via AMD, CommonJS or as a browser global
+ * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js
+ */
+ ;(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(factory)
+ } else if (typeof exports === 'object') {
+ /**
+ * Node. Does not work with strict CommonJS, but
+ * only CommonJS-like environments that support module.exports,
+ * like Node.
+ */
+ module.exports = factory()
+ } else {
+ // Browser globals (root is window)
+ root.lunr = factory()
+ }
+ }(this, function () {
+ /**
+ * Just return a value to define the module export.
+ * This example returns an object, but the module
+ * can return a function as the exported value.
+ */
+ return lunr
+ }))
+})();
diff --git a/search/main.js b/search/main.js
new file mode 100644
index 0000000000..a5e469d7c8
--- /dev/null
+++ b/search/main.js
@@ -0,0 +1,109 @@
+function getSearchTermFromLocation() {
+ var sPageURL = window.location.search.substring(1);
+ var sURLVariables = sPageURL.split('&');
+ for (var i = 0; i < sURLVariables.length; i++) {
+ var sParameterName = sURLVariables[i].split('=');
+ if (sParameterName[0] == 'q') {
+ return decodeURIComponent(sParameterName[1].replace(/\+/g, '%20'));
+ }
+ }
+}
+
+function joinUrl (base, path) {
+ if (path.substring(0, 1) === "/") {
+ // path starts with `/`. Thus it is absolute.
+ return path;
+ }
+ if (base.substring(base.length-1) === "/") {
+ // base ends with `/`
+ return base + path;
+ }
+ return base + "/" + path;
+}
+
+function escapeHtml (value) {
+ return value.replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(//g, '>');
+}
+
+function formatResult (location, title, summary) {
+ return '' + escapeHtml(summary) +'
';
+}
+
+function displayResults (results) {
+ var search_results = document.getElementById("mkdocs-search-results");
+ while (search_results.firstChild) {
+ search_results.removeChild(search_results.firstChild);
+ }
+ if (results.length > 0){
+ for (var i=0; i < results.length; i++){
+ var result = results[i];
+ var html = formatResult(result.location, result.title, result.summary);
+ search_results.insertAdjacentHTML('beforeend', html);
+ }
+ } else {
+ var noResultsText = search_results.getAttribute('data-no-results-text');
+ if (!noResultsText) {
+ noResultsText = "No results found";
+ }
+ search_results.insertAdjacentHTML('beforeend', '' + noResultsText + '
');
+ }
+}
+
+function doSearch () {
+ var query = document.getElementById('mkdocs-search-query').value;
+ if (query.length > min_search_length) {
+ if (!window.Worker) {
+ displayResults(search(query));
+ } else {
+ searchWorker.postMessage({query: query});
+ }
+ } else {
+ // Clear results for short queries
+ displayResults([]);
+ }
+}
+
+function initSearch () {
+ var search_input = document.getElementById('mkdocs-search-query');
+ if (search_input) {
+ search_input.addEventListener("keyup", doSearch);
+ }
+ var term = getSearchTermFromLocation();
+ if (term) {
+ search_input.value = term;
+ doSearch();
+ }
+}
+
+function onWorkerMessage (e) {
+ if (e.data.allowSearch) {
+ initSearch();
+ } else if (e.data.results) {
+ var results = e.data.results;
+ displayResults(results);
+ } else if (e.data.config) {
+ min_search_length = e.data.config.min_search_length-1;
+ }
+}
+
+if (!window.Worker) {
+ console.log('Web Worker API not supported');
+ // load index in main thread
+ $.getScript(joinUrl(base_url, "search/worker.js")).done(function () {
+ console.log('Loaded worker');
+ init();
+ window.postMessage = function (msg) {
+ onWorkerMessage({data: msg});
+ };
+ }).fail(function (jqxhr, settings, exception) {
+ console.error('Could not load worker.js');
+ });
+} else {
+ // Wrap search in a web worker
+ var searchWorker = new Worker(joinUrl(base_url, "search/worker.js"));
+ searchWorker.postMessage({init: true});
+ searchWorker.onmessage = onWorkerMessage;
+}
diff --git a/search/search_index.json b/search/search_index.json
new file mode 100644
index 0000000000..37f11c2109
--- /dev/null
+++ b/search/search_index.json
@@ -0,0 +1 @@
+{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"elmah.io Installation Quick Start Welcome to the quick-start installation guide. Here you will find a quick introduction to installing elmah.io. For the full overview, read through the individual guides by clicking a technology below: ASP.NET ASP.NET MVC ASP.NET Web API ASP.NET Core Extensions.Logging Functions Serilog log4net NLog Logary Umbraco JavaScript ASP.NET / MVC / Web API Install the Elmah.Io NuGet package: Package Manager .NET CLI PackageReference Paket CLI Install-Package Elmah.Io dotnet add package Elmah.Io paket add Elmah.Io During the installation, you will be asked for your API key and log ID. For more information, check out the installation guides for WebForms , MVC , and Web API . There is a short video tutorial available here: ASP.NET Core Install the Elmah.Io.AspNetCore NuGet package: Package Manager .NET CLI PackageReference Paket CLI Install-Package Elmah.Io.AspNetCore dotnet add package Elmah.Io.AspNetCore paket add Elmah.Io.AspNetCore Once installed, call AddElmahIo and UseElmahIo in the Program.cs file: var builder = WebApplication.CreateBuilder(args); // ... builder.Services.AddElmahIo(options => // \ud83d\udc48 { options.ApiKey = \"API_KEY\"; options.LogId = new Guid(\"LOG_ID\"); }); // ... var app = builder.Build(); // ... app.UseElmahIo(); // \ud83d\udc48 // ... app.Run(); Make sure to insert your API key and log ID. For more information, check out the installation guides for ASP.NET Core and Microsoft.Extensions.Logging . JavaScript Install the elmah.io.javascript npm package: npm install elmah.io.javascript Reference the installed script and include your API key and log ID as part of the URL: For more information, check out the installation guide for JavaScript .","title":"Quick start"},{"location":"#elmahio-installation-quick-start","text":"Welcome to the quick-start installation guide. Here you will find a quick introduction to installing elmah.io. For the full overview, read through the individual guides by clicking a technology below: ASP.NET ASP.NET MVC ASP.NET Web API ASP.NET Core Extensions.Logging Functions Serilog log4net NLog Logary Umbraco JavaScript","title":"elmah.io Installation Quick Start"},{"location":"#aspnet-mvc-web-api","text":"Install the Elmah.Io NuGet package: Package Manager .NET CLI PackageReference Paket CLI Install-Package Elmah.Io dotnet add package Elmah.Io paket add Elmah.Io During the installation, you will be asked for your API key and log ID. For more information, check out the installation guides for WebForms , MVC , and Web API . There is a short video tutorial available here:","title":"ASP.NET / MVC / Web API"},{"location":"#aspnet-core","text":"Install the Elmah.Io.AspNetCore NuGet package: Package Manager .NET CLI PackageReference Paket CLI Install-Package Elmah.Io.AspNetCore dotnet add package Elmah.Io.AspNetCore paket add Elmah.Io.AspNetCore Once installed, call AddElmahIo and UseElmahIo in the Program.cs file: var builder = WebApplication.CreateBuilder(args); // ... builder.Services.AddElmahIo(options => // \ud83d\udc48 { options.ApiKey = \"API_KEY\"; options.LogId = new Guid(\"LOG_ID\"); }); // ... var app = builder.Build(); // ... app.UseElmahIo(); // \ud83d\udc48 // ... app.Run(); Make sure to insert your API key and log ID. For more information, check out the installation guides for ASP.NET Core and Microsoft.Extensions.Logging .","title":"ASP.NET Core"},{"location":"#javascript","text":"Install the elmah.io.javascript npm package: npm install elmah.io.javascript Reference the installed script and include your API key and log ID as part of the URL: For more information, check out the installation guide for JavaScript .","title":"JavaScript"},{"location":"adding-version-information/","text":"Adding Version Information Adding Version Information Version Numbers on the UI Adding Version Numbers ASP.NET Core ASP.NET Microsoft.Extensions.Logging log4net NLog Serilog Almost every piece of software has some sort of version. Whether it's a nice-looking SemVer string or a simple timestamp, being able to distinguish one version from the other is important. elmah.io supports sending version information from your application in every message logged in two ways: By adding the version manually (as explained in this document). By using the Deployment Tracking feature (as explained in Set Up Deployment Tracking ). Version Numbers on the UI Let's start by looking at how version numbers are represented in the elmah.io UI. Every message contains a version property as illustrated below: The error is logged by an application with version number 1.0.0. This way, you will be able to see which version of your software logged each message. Having the version number on the message opens up some interesting search possibilities. Imagine that you want to search for every message logged by 1.0.* versions of your software, including release candidates, etc. Simply search in the search box like this: The example above finds every message logged from 1.0.0, 1.0.0-rc1, 1.0.1, etc. Adding Version Numbers How you choose to represent version numbers in your system is really up to you. elmah.io doesn't require SemVer, even though we strongly recommend you use it. Version is a simple string in our API , which means that you can send anything from SemVer to a stringified timestamp. Adding a version number to every message logged in elmah.io is easy as 1-2-3. If you're using our API, there's a property named version where you can put the version of your application. Chances are that you are using one of the integrations for ASP.NET Core, log4net, or Serilog. There are multiple ways to send a version number to elmah.io. ASP.NET Core Adding version information to all errors logged from Elmah.Io.AspNetCore can be achieved using the OnMessage action when initializing logging to elmah.io: builder.Services.AddElmahIo(options => { // ... options.OnMessage = msg => { msg.Version = \"1.2.3\"; }; }); ASP.NET You probably want to attach the same version number on every message logged in elmah.io. The easiest way to achieve that is to create a global event handler for the OnMessage event, which is triggered every time the elmah.io client logs a message to elmah.io: Elmah.ErrorLog.GetDefault(null); // Forces creation of logger client var logger = ErrorLog.Client; logger.OnMessage += (sender, args) => { args.Message.Version = \"1.2.3\"; } In the example, the message send off to elmah.io is decorated with the version number 1.2.3 You will need to replace this with the value of an app setting, the assembly info, or whatever strategy you've used to make the version number available to your code. If you're logging errors to elmah.io in catch blocks, logging the version number can be done using a similar approach to the above: try { CallSomeBusinessLogic(inputValue); } catch (Exception e) { e.Data.Add(\"X-ELMAHIO-VERSION\", \"1.2.3\"); ErrorSignal.FromCurrentContext().Raise(e); } In this case, the code at this point doesn't know anything about elmah.io. Luckily, there's an alternative to the Version property, by putting a custom element in the Data dictionary on Exception. The exact name of the key must be X-ELMAHIO-VERSION for elmah.io to interpret this as the version number. Microsoft.Extensions.Logging When using the Elmah.Io.Extensions.Logging package there are multiple ways of enriching log messages with version information. If you want the same version number on all log messages you can use the OnMessage action: builder.Logging.AddElmahIo(options => { // ... options.OnMessage = msg => { msg.Version = \"1.2.3\"; }; }); As an alternative, you can push a version property on individual log messages using either a structured property: logger.LogInformation(\"Message from {version}\", \"1.2.3\"); Or using scopes: using (logger.BeginScope(new { Version = \"1.2.3\" })) { logger.LogInformation(\"A message inside a logging scope\"); } log4net log4net supports the concept of customer properties in various ways. Since log4net properties are converted to custom properties in elmah.io, the easiest way to add a version number of every message logged through log4net is by configuring a global property somewhere in your initialization code: log4net.GlobalContext.Properties[\"version\"] = \"1.2.3\"; log4net supports custom properties in the context of a log call as well. To do that, put the version property in the ThreadContext before logging to log4net: log4net.ThreadContext.Properties[\"version\"] = \"1.2.3\"; log4net.Error(\"This is an error message\"); NLog NLog supports structured properties as well as various context objects. To set a version number on all log messages you can include the following code after initializing NLog: GlobalDiagnosticsContext.Set(\"version\", \"1.2.3\"); If the elmah.io NLog target is initialized from C# code you can also use the OnMessage action: var elmahIoTarget = new ElmahIoTarget(); // ... elmahIoTarget.OnMessage = msg => { msg.Version = \"1.2.3\"; }; To set a version number on a single log message, you can include it as a property: log.Info(\"Message from {version}\", \"1.2.3\"); Serilog Serilog can decorate all log messages using enrichers: var logger = new LoggerConfiguration() .Enrich.WithProperty(\"version\", \"1.2.3\") .Enrich.FromLogContext() .WriteTo.ElmahIo(new ElmahIoSinkOptions(\"API_KEY\", new Guid(\"LOG_ID\"))) .CreateLogger(); You can also enrich a single log message with a version number using a structured property: Log.Information(\"Meesage from {version}\", \"1.2.3\"); Or using the LogContext : using (LogContext.PushProperty(\"version\", \"1.2.3\")) { Log.Error(\"An error message\"); }","title":"Adding version information"},{"location":"adding-version-information/#adding-version-information","text":"Adding Version Information Version Numbers on the UI Adding Version Numbers ASP.NET Core ASP.NET Microsoft.Extensions.Logging log4net NLog Serilog Almost every piece of software has some sort of version. Whether it's a nice-looking SemVer string or a simple timestamp, being able to distinguish one version from the other is important. elmah.io supports sending version information from your application in every message logged in two ways: By adding the version manually (as explained in this document). By using the Deployment Tracking feature (as explained in Set Up Deployment Tracking ).","title":"Adding Version Information"},{"location":"adding-version-information/#version-numbers-on-the-ui","text":"Let's start by looking at how version numbers are represented in the elmah.io UI. Every message contains a version property as illustrated below: The error is logged by an application with version number 1.0.0. This way, you will be able to see which version of your software logged each message. Having the version number on the message opens up some interesting search possibilities. Imagine that you want to search for every message logged by 1.0.* versions of your software, including release candidates, etc. Simply search in the search box like this: The example above finds every message logged from 1.0.0, 1.0.0-rc1, 1.0.1, etc.","title":"Version Numbers on the UI"},{"location":"adding-version-information/#adding-version-numbers","text":"How you choose to represent version numbers in your system is really up to you. elmah.io doesn't require SemVer, even though we strongly recommend you use it. Version is a simple string in our API , which means that you can send anything from SemVer to a stringified timestamp. Adding a version number to every message logged in elmah.io is easy as 1-2-3. If you're using our API, there's a property named version where you can put the version of your application. Chances are that you are using one of the integrations for ASP.NET Core, log4net, or Serilog. There are multiple ways to send a version number to elmah.io.","title":"Adding Version Numbers"},{"location":"adding-version-information/#aspnet-core","text":"Adding version information to all errors logged from Elmah.Io.AspNetCore can be achieved using the OnMessage action when initializing logging to elmah.io: builder.Services.AddElmahIo(options => { // ... options.OnMessage = msg => { msg.Version = \"1.2.3\"; }; });","title":"ASP.NET Core"},{"location":"adding-version-information/#aspnet","text":"You probably want to attach the same version number on every message logged in elmah.io. The easiest way to achieve that is to create a global event handler for the OnMessage event, which is triggered every time the elmah.io client logs a message to elmah.io: Elmah.ErrorLog.GetDefault(null); // Forces creation of logger client var logger = ErrorLog.Client; logger.OnMessage += (sender, args) => { args.Message.Version = \"1.2.3\"; } In the example, the message send off to elmah.io is decorated with the version number 1.2.3 You will need to replace this with the value of an app setting, the assembly info, or whatever strategy you've used to make the version number available to your code. If you're logging errors to elmah.io in catch blocks, logging the version number can be done using a similar approach to the above: try { CallSomeBusinessLogic(inputValue); } catch (Exception e) { e.Data.Add(\"X-ELMAHIO-VERSION\", \"1.2.3\"); ErrorSignal.FromCurrentContext().Raise(e); } In this case, the code at this point doesn't know anything about elmah.io. Luckily, there's an alternative to the Version property, by putting a custom element in the Data dictionary on Exception. The exact name of the key must be X-ELMAHIO-VERSION for elmah.io to interpret this as the version number.","title":"ASP.NET"},{"location":"adding-version-information/#microsoftextensionslogging","text":"When using the Elmah.Io.Extensions.Logging package there are multiple ways of enriching log messages with version information. If you want the same version number on all log messages you can use the OnMessage action: builder.Logging.AddElmahIo(options => { // ... options.OnMessage = msg => { msg.Version = \"1.2.3\"; }; }); As an alternative, you can push a version property on individual log messages using either a structured property: logger.LogInformation(\"Message from {version}\", \"1.2.3\"); Or using scopes: using (logger.BeginScope(new { Version = \"1.2.3\" })) { logger.LogInformation(\"A message inside a logging scope\"); }","title":"Microsoft.Extensions.Logging"},{"location":"adding-version-information/#log4net","text":"log4net supports the concept of customer properties in various ways. Since log4net properties are converted to custom properties in elmah.io, the easiest way to add a version number of every message logged through log4net is by configuring a global property somewhere in your initialization code: log4net.GlobalContext.Properties[\"version\"] = \"1.2.3\"; log4net supports custom properties in the context of a log call as well. To do that, put the version property in the ThreadContext before logging to log4net: log4net.ThreadContext.Properties[\"version\"] = \"1.2.3\"; log4net.Error(\"This is an error message\");","title":"log4net"},{"location":"adding-version-information/#nlog","text":"NLog supports structured properties as well as various context objects. To set a version number on all log messages you can include the following code after initializing NLog: GlobalDiagnosticsContext.Set(\"version\", \"1.2.3\"); If the elmah.io NLog target is initialized from C# code you can also use the OnMessage action: var elmahIoTarget = new ElmahIoTarget(); // ... elmahIoTarget.OnMessage = msg => { msg.Version = \"1.2.3\"; }; To set a version number on a single log message, you can include it as a property: log.Info(\"Message from {version}\", \"1.2.3\");","title":"NLog"},{"location":"adding-version-information/#serilog","text":"Serilog can decorate all log messages using enrichers: var logger = new LoggerConfiguration() .Enrich.WithProperty(\"version\", \"1.2.3\") .Enrich.FromLogContext() .WriteTo.ElmahIo(new ElmahIoSinkOptions(\"API_KEY\", new Guid(\"LOG_ID\"))) .CreateLogger(); You can also enrich a single log message with a version number using a structured property: Log.Information(\"Meesage from {version}\", \"1.2.3\"); Or using the LogContext : using (LogContext.PushProperty(\"version\", \"1.2.3\")) { Log.Error(\"An error message\"); }","title":"Serilog"},{"location":"allowing-elmah-io-uptime-agents/","text":"Allowing elmah.io uptime agents elmah.io Uptime Monitoring runs in 9 different regions. Depending on which regions you enabled on each uptime check, your network infrastructure/firewall, or security settings, you may need to allow requests from elmah.io uptime agents. You can allow requests by allowing all requests with the following user-agent: User-Agent: elmahio-uptimebot/2.0 Allowing requests by possible inbound IP addresses is also an option. Since we run on Azure these changes over time, but here's a list by region as of time of writing this article: United States West 23.100.40.244,23.100.40.246,23.100.40.2,23.100.32.186,40.83.253.229,138.91.92.9,138.91.172.82,138.91.230.36,138.91.228.104,23.100.46.198,40.112.243.45,20.190.0.233,20.190.1.8,20.190.1.21,20.190.1.32,20.190.1.42,20.190.1.61,20.190.1.72,20.190.0.33,20.190.1.162,20.190.1.177,20.190.1.187,20.190.1.191,52.149.26.109,52.149.26.223,52.149.26.248,52.149.27.21,52.149.27.137,52.149.28.241,51.143.61.29,52.137.93.170,52.149.29.95,52.149.29.238,52.149.30.76,52.149.30.96,20.190.1.217,20.190.2.43,20.190.2.66,52.143.80.96,52.156.145.74,52.156.145.106 United States Central 13.86.38.119,13.89.106.62,40.89.243.166,52.143.244.58,52.154.168.87,52.154.169.9,52.154.169.57,52.154.169.86,52.154.169.106,52.154.169.204,52.154.170.25,52.154.170.83,52.154.170.158,52.154.171.81,52.154.171.88,52.154.173.21,40.89.244.137,52.154.173.79,52.154.173.101,52.154.173.180,52.154.174.243,52.154.175.58,52.154.240.147,52.154.240.170,52.154.241.97,52.154.241.132,52.154.241.246,52.154.43.251,52.154.44.21,52.154.44.95 United States East 13.92.137.62,13.92.138.72,13.92.137.49,13.92.143.167,13.68.182.254,13.82.23.172,13.82.23.182,13.82.18.16,13.82.18.62,13.92.139.214,40.71.11.179,52.247.85.151,52.247.85.207,52.247.85.217,52.247.85.218,52.247.85.228,52.247.85.242,52.247.74.244,52.247.75.195,52.247.76.11,52.247.76.21,52.247.76.72,52.247.76.155,52.247.85.252,52.247.86.14,52.247.86.22,52.167.77.53,52.177.247.146,52.184.204.69,20.49.97.1 United Kingdom South 51.140.180.76,51.140.191.115,51.140.176.159,51.140.176.11,51.140.185.39,51.140.177.87,51.140.185.119,51.140.188.39,51.140.188.241,51.140.183.68,51.140.184.173 United Kingdom West 51.140.210.96,51.140.252.30,51.140.251.198,51.141.123.138,51.140.248.129,51.140.252.164,51.140.248.213,52.142.164.59,51.140.248.195,51.140.250.153 Australia South East 191.239.187.197,191.239.187.149,191.239.184.74,191.239.188.25,52.189.210.149,52.189.209.158,52.189.215.220,52.189.208.40,40.127.95.32,40.115.76.80,40.115.76.83,40.115.76.93,40.115.76.94,40.115.76.101,40.115.76.109,40.115.76.129,40.115.76.143,191.239.188.11,13.77.50.103 Europe North 40.127.186.114,40.127.183.46,40.127.189.87,40.127.183.64,40.115.126.151,137.135.216.255,40.85.101.214,40.85.101.216,40.85.101.219,40.127.139.252,13.69.228.38 Europe West 20.71.75.100,20.71.75.133,20.71.75.198,20.71.75.237,20.71.76.1,20.71.76.23,20.71.76.57,20.71.76.171,20.71.76.196,20.71.77.10,20.71.77.83,20.71.77.134,20.71.77.205,20.71.78.20,20.71.78.208,20.71.78.243,20.71.79.77,20.71.79.165,20.71.79.228,20.73.232.14,20.73.232.51,20.73.232.121,20.73.232.130,20.73.232.163,20.73.232.186,20.73.232.217,20.73.232.241,20.73.132.133,20.73.132.167,20.73.133.173 Brazil South 191.232.176.16,191.232.180.88,191.232.177.130,191.232.180.187,191.232.179.236,191.232.177.40,191.232.180.122","title":"Allowing elmah.io uptime agents"},{"location":"allowing-elmah-io-uptime-agents/#allowing-elmahio-uptime-agents","text":"elmah.io Uptime Monitoring runs in 9 different regions. Depending on which regions you enabled on each uptime check, your network infrastructure/firewall, or security settings, you may need to allow requests from elmah.io uptime agents. You can allow requests by allowing all requests with the following user-agent: User-Agent: elmahio-uptimebot/2.0 Allowing requests by possible inbound IP addresses is also an option. Since we run on Azure these changes over time, but here's a list by region as of time of writing this article: United States West 23.100.40.244,23.100.40.246,23.100.40.2,23.100.32.186,40.83.253.229,138.91.92.9,138.91.172.82,138.91.230.36,138.91.228.104,23.100.46.198,40.112.243.45,20.190.0.233,20.190.1.8,20.190.1.21,20.190.1.32,20.190.1.42,20.190.1.61,20.190.1.72,20.190.0.33,20.190.1.162,20.190.1.177,20.190.1.187,20.190.1.191,52.149.26.109,52.149.26.223,52.149.26.248,52.149.27.21,52.149.27.137,52.149.28.241,51.143.61.29,52.137.93.170,52.149.29.95,52.149.29.238,52.149.30.76,52.149.30.96,20.190.1.217,20.190.2.43,20.190.2.66,52.143.80.96,52.156.145.74,52.156.145.106 United States Central 13.86.38.119,13.89.106.62,40.89.243.166,52.143.244.58,52.154.168.87,52.154.169.9,52.154.169.57,52.154.169.86,52.154.169.106,52.154.169.204,52.154.170.25,52.154.170.83,52.154.170.158,52.154.171.81,52.154.171.88,52.154.173.21,40.89.244.137,52.154.173.79,52.154.173.101,52.154.173.180,52.154.174.243,52.154.175.58,52.154.240.147,52.154.240.170,52.154.241.97,52.154.241.132,52.154.241.246,52.154.43.251,52.154.44.21,52.154.44.95 United States East 13.92.137.62,13.92.138.72,13.92.137.49,13.92.143.167,13.68.182.254,13.82.23.172,13.82.23.182,13.82.18.16,13.82.18.62,13.92.139.214,40.71.11.179,52.247.85.151,52.247.85.207,52.247.85.217,52.247.85.218,52.247.85.228,52.247.85.242,52.247.74.244,52.247.75.195,52.247.76.11,52.247.76.21,52.247.76.72,52.247.76.155,52.247.85.252,52.247.86.14,52.247.86.22,52.167.77.53,52.177.247.146,52.184.204.69,20.49.97.1 United Kingdom South 51.140.180.76,51.140.191.115,51.140.176.159,51.140.176.11,51.140.185.39,51.140.177.87,51.140.185.119,51.140.188.39,51.140.188.241,51.140.183.68,51.140.184.173 United Kingdom West 51.140.210.96,51.140.252.30,51.140.251.198,51.141.123.138,51.140.248.129,51.140.252.164,51.140.248.213,52.142.164.59,51.140.248.195,51.140.250.153 Australia South East 191.239.187.197,191.239.187.149,191.239.184.74,191.239.188.25,52.189.210.149,52.189.209.158,52.189.215.220,52.189.208.40,40.127.95.32,40.115.76.80,40.115.76.83,40.115.76.93,40.115.76.94,40.115.76.101,40.115.76.109,40.115.76.129,40.115.76.143,191.239.188.11,13.77.50.103 Europe North 40.127.186.114,40.127.183.46,40.127.189.87,40.127.183.64,40.115.126.151,137.135.216.255,40.85.101.214,40.85.101.216,40.85.101.219,40.127.139.252,13.69.228.38 Europe West 20.71.75.100,20.71.75.133,20.71.75.198,20.71.75.237,20.71.76.1,20.71.76.23,20.71.76.57,20.71.76.171,20.71.76.196,20.71.77.10,20.71.77.83,20.71.77.134,20.71.77.205,20.71.78.20,20.71.78.208,20.71.78.243,20.71.79.77,20.71.79.165,20.71.79.228,20.73.232.14,20.73.232.51,20.73.232.121,20.73.232.130,20.73.232.163,20.73.232.186,20.73.232.217,20.73.232.241,20.73.132.133,20.73.132.167,20.73.133.173 Brazil South 191.232.176.16,191.232.180.88,191.232.177.130,191.232.180.187,191.232.179.236,191.232.177.40,191.232.180.122","title":"Allowing elmah.io uptime agents"},{"location":"asp-net-core-troubleshooting/","text":"ASP.NET Core Troubleshooting So, your ASP.NET Core application doesn't log errors to elmah.io? Here is a list of things to try out: Run the diagnose command with the elmah.io CLI as shown here: Diagnose potential problems with an elmah.io installation . Make sure to reference the most recent version of the Elmah.Io.AspNetCore NuGet package. Make sure that the Elmah.Io.Client NuGet package is installed and that the major version matches that of Elmah.Io.AspNetCore . Make sure that you are calling both the AddElmahIo - and UseElmahIo -methods in the Program.cs file (or Startup.cs for older applications), as described on Logging to elmah.io from ASP.NET Core . Make sure that you call the UseElmahIo -method after invoking other Use* methods that in any way inspect exceptions (like UseDeveloperExceptionPage and UseExceptionHandler ). Make sure that you call the UseElmahIo -method before invoking UseMvc , UseEndpoints , and similar. Make sure that your server has an outgoing internet connection and that it can communicate with api.elmah.io on port 443 . The integration for ASP.NET Core support setting up an HTTP proxy if your server doesn't allow outgoing traffic. Check out Logging through a proxy for details. Make sure that you didn't enable any Ignore filters or set up any Rules with an ignore action on the log in question. Make sure that you don't have any code catching all exceptions happening in your system and ignoring them (could be a logging filter, a piece of middleware, or similar). Make sure that you haven't reached the message limit included in your current plan. Your current usage can be viewed on the Subscription tab on organization settings. Some of the bullets above have been implemented as Roslyn analyzers. Check out Roslyn analyzers for elmah.io and ASP.NET Core for details. Common problems and how to fix them Here you will a list of common exceptions and how to solve them. InvalidOperationException Exception [InvalidOperationException: Unable to resolve service for type 'Elmah.Io.AspNetCore.IBackgroundTaskQueue' while attempting to activate 'Elmah.Io.AspNetCore.ElmahIoMiddleware'.] Microsoft.Extensions.Internal.ActivatorUtilities+ConstructorMatcher.CreateInstance(IServiceProvider provider) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+<>c__DisplayClass4_0.b__0(RequestDelegate next) Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder.Build() Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication() Solution You forgot to call the AddElmahIo -method in the Program.cs file: builder.Services.AddElmahIo(o => { // ... }); ArgumentException Exception [ArgumentException: Input an API key Parameter name: apiKey] Elmah.Io.AspNetCore.Extensions.StringExtensions.AssertApiKey(string apiKey) Elmah.Io.AspNetCore.ElmahIoMiddleware..ctor(RequestDelegate next, IBackgroundTaskQueue queue, IOptions options) Microsoft.Extensions.Internal.ActivatorUtilities+ConstructorMatcher.CreateInstance(IServiceProvider provider) Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+<>c__DisplayClass4_0.b__0(RequestDelegate next) Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder.Build() Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication() Solution You forgot to call the AddElmahIo -method in the Program.cs file: builder.Services.AddElmahIo(o => { // ... }); or you called AddElmahIo without options and didn't provide these options elsewhere: builder.Services.AddElmahIo(); Even though you configure elmah.io through appsettings.json you still need to call AddElmahIo . In this case, you can register ElmahIoOptions manually and use the empty AddElmahIo overload: builder.Services.Configure(Configuration.GetSection(\"ElmahIo\")); builder.Services.AddElmahIo(); An error occurred while starting the application If you see the error An error occurred while starting the application and the exception isn't logged to elmah.io, the error probably happens before hitting the elmah.io middleware. To help find out what is going on, add the following lines to your Program.cs file: builder.WebHost.UseSetting(WebHostDefaults.DetailedErrorsKey, \"true\"); builder.WebHost.CaptureStartupErrors(true); URL missing when using Map When handling requests with the Map method, ASP.NET Core will remove the path from HttpRequest.Path . In this case, Elmah.Io.AspNetCore will look for an URL in the HttpRequest.PathBase property. This is not already enough and won't always return the right URL. Consider using the MapWhen method instead. Thread pool thread or asynchronous tasks blocked on a synchronous call Azure and other systems with runtime diagnostics and validation may complain with the error Thread pool thread or asynchronous tasks blocked on a synchronous call in the Elmah.Io.AspNetCore package. This can be caused by our implementation of the package using a background worker for processing batches of messages. This background worker runs in a single thread and will never cause thread starvation as suggested by the error. We may want to move the internal implementation from BlockingCollection to Channel at some point.","title":"ASP.NET Core troubleshooting"},{"location":"asp-net-core-troubleshooting/#aspnet-core-troubleshooting","text":"So, your ASP.NET Core application doesn't log errors to elmah.io? Here is a list of things to try out: Run the diagnose command with the elmah.io CLI as shown here: Diagnose potential problems with an elmah.io installation . Make sure to reference the most recent version of the Elmah.Io.AspNetCore NuGet package. Make sure that the Elmah.Io.Client NuGet package is installed and that the major version matches that of Elmah.Io.AspNetCore . Make sure that you are calling both the AddElmahIo - and UseElmahIo -methods in the Program.cs file (or Startup.cs for older applications), as described on Logging to elmah.io from ASP.NET Core . Make sure that you call the UseElmahIo -method after invoking other Use* methods that in any way inspect exceptions (like UseDeveloperExceptionPage and UseExceptionHandler ). Make sure that you call the UseElmahIo -method before invoking UseMvc , UseEndpoints , and similar. Make sure that your server has an outgoing internet connection and that it can communicate with api.elmah.io on port 443 . The integration for ASP.NET Core support setting up an HTTP proxy if your server doesn't allow outgoing traffic. Check out Logging through a proxy for details. Make sure that you didn't enable any Ignore filters or set up any Rules with an ignore action on the log in question. Make sure that you don't have any code catching all exceptions happening in your system and ignoring them (could be a logging filter, a piece of middleware, or similar). Make sure that you haven't reached the message limit included in your current plan. Your current usage can be viewed on the Subscription tab on organization settings. Some of the bullets above have been implemented as Roslyn analyzers. Check out Roslyn analyzers for elmah.io and ASP.NET Core for details.","title":"ASP.NET Core Troubleshooting"},{"location":"asp-net-core-troubleshooting/#common-problems-and-how-to-fix-them","text":"Here you will a list of common exceptions and how to solve them.","title":"Common problems and how to fix them"},{"location":"asp-net-core-troubleshooting/#invalidoperationexception","text":"Exception [InvalidOperationException: Unable to resolve service for type 'Elmah.Io.AspNetCore.IBackgroundTaskQueue' while attempting to activate 'Elmah.Io.AspNetCore.ElmahIoMiddleware'.] Microsoft.Extensions.Internal.ActivatorUtilities+ConstructorMatcher.CreateInstance(IServiceProvider provider) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+<>c__DisplayClass4_0.b__0(RequestDelegate next) Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder.Build() Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication() Solution You forgot to call the AddElmahIo -method in the Program.cs file: builder.Services.AddElmahIo(o => { // ... });","title":"InvalidOperationException"},{"location":"asp-net-core-troubleshooting/#argumentexception","text":"Exception [ArgumentException: Input an API key Parameter name: apiKey] Elmah.Io.AspNetCore.Extensions.StringExtensions.AssertApiKey(string apiKey) Elmah.Io.AspNetCore.ElmahIoMiddleware..ctor(RequestDelegate next, IBackgroundTaskQueue queue, IOptions options) Microsoft.Extensions.Internal.ActivatorUtilities+ConstructorMatcher.CreateInstance(IServiceProvider provider) Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters) Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+<>c__DisplayClass4_0.b__0(RequestDelegate next) Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder.Build() Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication() Solution You forgot to call the AddElmahIo -method in the Program.cs file: builder.Services.AddElmahIo(o => { // ... }); or you called AddElmahIo without options and didn't provide these options elsewhere: builder.Services.AddElmahIo(); Even though you configure elmah.io through appsettings.json you still need to call AddElmahIo . In this case, you can register ElmahIoOptions manually and use the empty AddElmahIo overload: builder.Services.Configure(Configuration.GetSection(\"ElmahIo\")); builder.Services.AddElmahIo();","title":"ArgumentException"},{"location":"asp-net-core-troubleshooting/#an-error-occurred-while-starting-the-application","text":"If you see the error An error occurred while starting the application and the exception isn't logged to elmah.io, the error probably happens before hitting the elmah.io middleware. To help find out what is going on, add the following lines to your Program.cs file: builder.WebHost.UseSetting(WebHostDefaults.DetailedErrorsKey, \"true\"); builder.WebHost.CaptureStartupErrors(true);","title":"An error occurred while starting the application"},{"location":"asp-net-core-troubleshooting/#url-missing-when-using-map","text":"When handling requests with the Map method, ASP.NET Core will remove the path from HttpRequest.Path . In this case, Elmah.Io.AspNetCore will look for an URL in the HttpRequest.PathBase property. This is not already enough and won't always return the right URL. Consider using the MapWhen method instead.","title":"URL missing when using Map"},{"location":"asp-net-core-troubleshooting/#thread-pool-thread-or-asynchronous-tasks-blocked-on-a-synchronous-call","text":"Azure and other systems with runtime diagnostics and validation may complain with the error Thread pool thread or asynchronous tasks blocked on a synchronous call in the Elmah.Io.AspNetCore package. This can be caused by our implementation of the package using a background worker for processing batches of messages. This background worker runs in a single thread and will never cause thread starvation as suggested by the error. We may want to move the internal implementation from BlockingCollection to Channel at some point.","title":"Thread pool thread or asynchronous tasks blocked on a synchronous call"},{"location":"asp-net-troubleshooting/","text":"ASP.NET Troubleshooting You are probably here because your application doesn't log errors to elmah.io, even though you installed the integration. Before contacting support, there are some things you can try out yourself. Run the diagnose command with the elmah.io CLI as shown here: Diagnose potential problems with an elmah.io installation . Make sure that you are referencing one of the following NuGet packages: Elmah.Io , Elmah.Io.AspNet , Elmah.Io.Mvc or Elmah.Io.WebApi . Make sure that the Elmah.Io.Client NuGet package is installed and that the major version matches that of Elmah.Io , Elmah.Io.AspNet , Elmah.Io.Mvc or Elmah.Io.WebApi . Make sure that your project reference the following assemblies: Elmah , Elmah.Io , and Elmah.Io.Client . Make sure that your web.config file contains valid config as described here . You can validate your web.config file using this Web.config Validator . When installing the Elmah.Io NuGet package, config is automatically added to your web.config file, as long as your Visual Studio allows for running PowerShell scripts as part of the installation. To check if you have the correct execution policy, go to the Package Manager Console and verify that the result of the following statement is RemoteSigned : Get-ExecutionPolicy Make sure that your server has an outgoing internet connection and that it can communicate with api.elmah.io on port 443 . Most of our integrations support setting up an HTTP proxy if your server doesn't allow outgoing traffic. Make sure that you didn't enable any Ignore filters or set up any Rules with an ignore action on the log in question. Make sure that you don't have any code catching all exceptions happening in your system and ignoring them (could be a logging filter or similar). If you are using custom errors, make sure to configure it correctly. For more details, check out the following posts: Web.config customErrors element with ASP.NET explained and Demystifying ASP.NET MVC 5 Error Pages and Error Logging . Common errors and how to fix them Here you will a list of common errors/exceptions and how to solve them. TypeLoadException Exception [TypeLoadException: Inheritance security rules violated by type: 'System.Net.Http.WebRequestHandler'. Derived types must either match the security accessibility of the base type or be less accessible.] Microsoft.Rest.ServiceClient`1.CreateRootHandler() +0 Microsoft.Rest.ServiceClient`1..ctor(DelegatingHandler[] handlers) +59 Elmah.Io.Client.ElmahioAPI..ctor(DelegatingHandler[] handlers) +96 Elmah.Io.Client.ElmahioAPI..ctor(ServiceClientCredentials credentials, DelegatingHandler[] handlers) +70 Elmah.Io.Client.ElmahioAPI.Create(String apiKey, ElmahIoOptions options) +146 Elmah.Io.Client.ElmahioAPI.Create(String apiKey) +91 Elmah.Io.ErrorLog..ctor(IDictionary config) +109 Solution This is most likely caused by a problem with the System.Net.Http NuGet package. Make sure to upgrade to the newest version ( 4.3.4 as of writing this). The default template for creating a new web application installs version 4.3.0 which is seriously flawed. AmbiguousMatchException Exception [AmbiguousMatchException: Multiple custom attributes of the same type found.] System.Attribute.GetCustomAttribute(Assembly element, Type attributeType, Boolean inherit) +119 System.Reflection.CustomAttributeExtensions.GetCustomAttribute(Assembly element, Type attributeType) +16 Microsoft.Rest.ServiceClient`1.get_FrameworkVersion() +226 Microsoft.Rest.ServiceClient`1.SetDefaultAgentInfo() +93 Microsoft.Rest.ServiceClient`1.InitializeHttpClient(HttpClient httpClient, HttpClientHandler httpClientHandler, DelegatingHandler[] handlers) +386 Microsoft.Rest.ServiceClient`1..ctor(HttpClient serviceHttpClient, HttpClientHandler rootHandler, Boolean disposeHttpClient, DelegatingHandler[] delHandlers) +82 Microsoft.Rest.ServiceClient`1..ctor(HttpClientHandler rootHandler, DelegatingHandler[] handlers) +66 Elmah.Io.Client.ElmahioAPI..ctor(HttpClientHandler rootHandler, DelegatingHandler[] handlers) +104 Elmah.Io.Client.ElmahioAPI..ctor(ServiceClientCredentials credentials, HttpClientHandler rootHandler, DelegatingHandler[] handlers) +78 Elmah.Io.ErrorLog..ctor(IDictionary config) +225 [TargetInvocationException: Exception has been thrown by the target of an invocation.] System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) +0 System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) +359 System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark) +1485 System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes) +298 System.Activator.CreateInstance(Type type, Object[] args) +34 Elmah.ErrorLog.GetDefaultImpl(HttpContext context) +178 Elmah.ServiceCenter.GetService(Object context, Type serviceType) +17 Elmah.ErrorLog.GetDefault(HttpContext context) +34 Elmah.ErrorPageBase.get_ErrorLog() +39 Elmah.ErrorLogPage.OnLoad(EventArgs e) +400 System.Web.UI.Control.LoadRecursive() +154 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +4082 Solution This is most likely caused by some other software installed on the machine hosting your website. Applications like Microsoft Monitoring Agent (Application Insights) are known for creating problems for other software running on the same machine. Pause or stop any other monitoring service to see if the problem goes away. Exceptions aren't logged to elmah.io when adding the HandleError attribute Much like custom errors, the HandleError attribute can swallow exceptions from your website. This means that ASP.NET MVC catches any exceptions and shows the Error.cshtml view. To log exceptions with this setup, you will need to extend your Error.cshtml file: @model System.Web.Mvc.HandleErrorInfo @{ if (Model.Exception != null) { Elmah.ErrorLog.GetDefault(HttpContext.Current).Log(new Elmah.Error(Model.Exception, HttpContext.Current)); } } Your error page content goes here
Errors are not logged when using update panels ASP.NET Update Panels are great at many things. Unfortunately, one of those things is causing problems when trying to log server-side errors. If errors aren't logged as part of a call made from an update panel, you can try to add the following code to Global.asax.cs : protected void Application_Error(object sender, EventArgs e) { var ex = Server.GetLastError(); var error = new Elmah.Error(ex); error.ServerVariables.Add(\"URL\", HttpContext.Current?.Request?.Url?.AbsolutePath); error.ServerVariables.Add(\"HTTP_USER_AGENT\", HttpContext.Current?.Request?.UserAgent); error.ServerVariables.Add(\"REMOTE_ADDR\", HttpContext.Current?.Request?.UserHostAddress); Elmah.ErrorLog.GetDefault(HttpContext.Current).Log(error); } If you start experiencing errors logged multiple times, you can remove the ELMAH module from web.config : ","title":"ASP.NET troubleshooting"},{"location":"asp-net-troubleshooting/#aspnet-troubleshooting","text":"You are probably here because your application doesn't log errors to elmah.io, even though you installed the integration. Before contacting support, there are some things you can try out yourself. Run the diagnose command with the elmah.io CLI as shown here: Diagnose potential problems with an elmah.io installation . Make sure that you are referencing one of the following NuGet packages: Elmah.Io , Elmah.Io.AspNet , Elmah.Io.Mvc or Elmah.Io.WebApi . Make sure that the Elmah.Io.Client NuGet package is installed and that the major version matches that of Elmah.Io , Elmah.Io.AspNet , Elmah.Io.Mvc or Elmah.Io.WebApi . Make sure that your project reference the following assemblies: Elmah , Elmah.Io , and Elmah.Io.Client . Make sure that your web.config file contains valid config as described here . You can validate your web.config file using this Web.config Validator . When installing the Elmah.Io NuGet package, config is automatically added to your web.config file, as long as your Visual Studio allows for running PowerShell scripts as part of the installation. To check if you have the correct execution policy, go to the Package Manager Console and verify that the result of the following statement is RemoteSigned : Get-ExecutionPolicy Make sure that your server has an outgoing internet connection and that it can communicate with api.elmah.io on port 443 . Most of our integrations support setting up an HTTP proxy if your server doesn't allow outgoing traffic. Make sure that you didn't enable any Ignore filters or set up any Rules with an ignore action on the log in question. Make sure that you don't have any code catching all exceptions happening in your system and ignoring them (could be a logging filter or similar). If you are using custom errors, make sure to configure it correctly. For more details, check out the following posts: Web.config customErrors element with ASP.NET explained and Demystifying ASP.NET MVC 5 Error Pages and Error Logging .","title":"ASP.NET Troubleshooting"},{"location":"asp-net-troubleshooting/#common-errors-and-how-to-fix-them","text":"Here you will a list of common errors/exceptions and how to solve them.","title":"Common errors and how to fix them"},{"location":"asp-net-troubleshooting/#typeloadexception","text":"Exception [TypeLoadException: Inheritance security rules violated by type: 'System.Net.Http.WebRequestHandler'. Derived types must either match the security accessibility of the base type or be less accessible.] Microsoft.Rest.ServiceClient`1.CreateRootHandler() +0 Microsoft.Rest.ServiceClient`1..ctor(DelegatingHandler[] handlers) +59 Elmah.Io.Client.ElmahioAPI..ctor(DelegatingHandler[] handlers) +96 Elmah.Io.Client.ElmahioAPI..ctor(ServiceClientCredentials credentials, DelegatingHandler[] handlers) +70 Elmah.Io.Client.ElmahioAPI.Create(String apiKey, ElmahIoOptions options) +146 Elmah.Io.Client.ElmahioAPI.Create(String apiKey) +91 Elmah.Io.ErrorLog..ctor(IDictionary config) +109 Solution This is most likely caused by a problem with the System.Net.Http NuGet package. Make sure to upgrade to the newest version ( 4.3.4 as of writing this). The default template for creating a new web application installs version 4.3.0 which is seriously flawed.","title":"TypeLoadException"},{"location":"asp-net-troubleshooting/#ambiguousmatchexception","text":"Exception [AmbiguousMatchException: Multiple custom attributes of the same type found.] System.Attribute.GetCustomAttribute(Assembly element, Type attributeType, Boolean inherit) +119 System.Reflection.CustomAttributeExtensions.GetCustomAttribute(Assembly element, Type attributeType) +16 Microsoft.Rest.ServiceClient`1.get_FrameworkVersion() +226 Microsoft.Rest.ServiceClient`1.SetDefaultAgentInfo() +93 Microsoft.Rest.ServiceClient`1.InitializeHttpClient(HttpClient httpClient, HttpClientHandler httpClientHandler, DelegatingHandler[] handlers) +386 Microsoft.Rest.ServiceClient`1..ctor(HttpClient serviceHttpClient, HttpClientHandler rootHandler, Boolean disposeHttpClient, DelegatingHandler[] delHandlers) +82 Microsoft.Rest.ServiceClient`1..ctor(HttpClientHandler rootHandler, DelegatingHandler[] handlers) +66 Elmah.Io.Client.ElmahioAPI..ctor(HttpClientHandler rootHandler, DelegatingHandler[] handlers) +104 Elmah.Io.Client.ElmahioAPI..ctor(ServiceClientCredentials credentials, HttpClientHandler rootHandler, DelegatingHandler[] handlers) +78 Elmah.Io.ErrorLog..ctor(IDictionary config) +225 [TargetInvocationException: Exception has been thrown by the target of an invocation.] System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor) +0 System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) +359 System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes, StackCrawlMark& stackMark) +1485 System.Activator.CreateInstance(Type type, BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes) +298 System.Activator.CreateInstance(Type type, Object[] args) +34 Elmah.ErrorLog.GetDefaultImpl(HttpContext context) +178 Elmah.ServiceCenter.GetService(Object context, Type serviceType) +17 Elmah.ErrorLog.GetDefault(HttpContext context) +34 Elmah.ErrorPageBase.get_ErrorLog() +39 Elmah.ErrorLogPage.OnLoad(EventArgs e) +400 System.Web.UI.Control.LoadRecursive() +154 System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +4082 Solution This is most likely caused by some other software installed on the machine hosting your website. Applications like Microsoft Monitoring Agent (Application Insights) are known for creating problems for other software running on the same machine. Pause or stop any other monitoring service to see if the problem goes away.","title":"AmbiguousMatchException"},{"location":"asp-net-troubleshooting/#exceptions-arent-logged-to-elmahio-when-adding-the-handleerror-attribute","text":"Much like custom errors, the HandleError attribute can swallow exceptions from your website. This means that ASP.NET MVC catches any exceptions and shows the Error.cshtml view. To log exceptions with this setup, you will need to extend your Error.cshtml file: @model System.Web.Mvc.HandleErrorInfo @{ if (Model.Exception != null) { Elmah.ErrorLog.GetDefault(HttpContext.Current).Log(new Elmah.Error(Model.Exception, HttpContext.Current)); } } Your error page content goes here
","title":"Exceptions aren't logged to elmah.io when adding the HandleError attribute"},{"location":"asp-net-troubleshooting/#errors-are-not-logged-when-using-update-panels","text":"ASP.NET Update Panels are great at many things. Unfortunately, one of those things is causing problems when trying to log server-side errors. If errors aren't logged as part of a call made from an update panel, you can try to add the following code to Global.asax.cs : protected void Application_Error(object sender, EventArgs e) { var ex = Server.GetLastError(); var error = new Elmah.Error(ex); error.ServerVariables.Add(\"URL\", HttpContext.Current?.Request?.Url?.AbsolutePath); error.ServerVariables.Add(\"HTTP_USER_AGENT\", HttpContext.Current?.Request?.UserAgent); error.ServerVariables.Add(\"REMOTE_ADDR\", HttpContext.Current?.Request?.UserHostAddress); Elmah.ErrorLog.GetDefault(HttpContext.Current).Log(error); } If you start experiencing errors logged multiple times, you can remove the ELMAH module from web.config : ","title":"Errors are not logged when using update panels"},{"location":"authentication/","text":"Authentication All of our integrations communicates with the elmah.io API. In order to request endpoints on the API, each client will need to provide a valid API key. API keys are available on the Organization Settings view, as well as on the Install tab on the Log Settings screen. We wrote a guide to help you find your API key here: Where is my API key? . A default API key is created when you create your organization, but new keys can be added, keys revoked, and more. Sending the API key to the elmah.io API, is typically handled by the Elmah.Io.Client NuGet package. All integrations have a dependency to this package, which means that it will be automatically installed through NuGet. How you provide your API key depends on the integration you are installing. Some integrations expect the API key in a config file, while others, accept the key in C#. For details about how to provide the API key for each integration, click the various installation guides in the left menu. Besides a unique string representing an API key, each key can have a set of permissions. As default, API keys only have the Write Messages permission, which means that the key cannot be used to read data from your logs. In 99% of all scenarios, you will browse through errors using the elmah.io UI, which will require you to sign in using username/password or one of the supported social providers. In the case you want to enable one of the native UIs provided by different integrations (like the /elmah.axd endpoint part of the ELMAH package) or you are building a third-party integration to elmah.io, you will need to assign additional permissions to your API key. For details about API key permissions, check out How to configure API key permissions .","title":"Authentication"},{"location":"authentication/#authentication","text":"All of our integrations communicates with the elmah.io API. In order to request endpoints on the API, each client will need to provide a valid API key. API keys are available on the Organization Settings view, as well as on the Install tab on the Log Settings screen. We wrote a guide to help you find your API key here: Where is my API key? . A default API key is created when you create your organization, but new keys can be added, keys revoked, and more. Sending the API key to the elmah.io API, is typically handled by the Elmah.Io.Client NuGet package. All integrations have a dependency to this package, which means that it will be automatically installed through NuGet. How you provide your API key depends on the integration you are installing. Some integrations expect the API key in a config file, while others, accept the key in C#. For details about how to provide the API key for each integration, click the various installation guides in the left menu. Besides a unique string representing an API key, each key can have a set of permissions. As default, API keys only have the Write Messages permission, which means that the key cannot be used to read data from your logs. In 99% of all scenarios, you will browse through errors using the elmah.io UI, which will require you to sign in using username/password or one of the supported social providers. In the case you want to enable one of the native UIs provided by different integrations (like the /elmah.axd endpoint part of the ELMAH package) or you are building a third-party integration to elmah.io, you will need to assign additional permissions to your API key. For details about API key permissions, check out How to configure API key permissions .","title":"Authentication"},{"location":"bot-detection/","text":"Bot detection elmah.io can help you with classifying log messages generated by bots and crawlers. When storing a log message, we run a range of checks to try and identify if a log message is generated by an automated script or a real human visitor of your website/application. In this case, a flag named isBot is set to true on the message. In case we couldn't identify a log message as generated by a bot, the flag is set to false . Besides an automated check, you can also mark a log message as generated by a bot manually. This is done from within the elmah.io UI: The benefit of marking log messages with the isBot flag manually is that elmah.io will then automatically mark new instances of this log message with isBot=true (this feature is available for automatically bot-marked log messages as well). By doing so you get the possibilities listed later in this article. Log messages marked as generated by bots include a small robot icon on the search result: Search by or not by bots If you want to show all log messages generated by bots you can create a search query like this: isBot:true Or include a search filter for the Is Bot field: By reversing the filter, you see a list of log messages NOT generated by a bot, which can make it easier to get an overview of \"real\" errors. Hide or ignore log messages generated by bots By using the isBot field in Hide and Ignore filters, you can let elmah.io automatically hide or ignore future log messages generated by bots. Be aware that creating ignore filters based on the isBot field is only available for users on the Enterprise plan. To create a new filter, navigate to the Rules tab on log settings, and click the Add a new rule button. Input a query including the isBot field and select either the Hide or Ignore action:","title":"Bot detection"},{"location":"bot-detection/#bot-detection","text":"elmah.io can help you with classifying log messages generated by bots and crawlers. When storing a log message, we run a range of checks to try and identify if a log message is generated by an automated script or a real human visitor of your website/application. In this case, a flag named isBot is set to true on the message. In case we couldn't identify a log message as generated by a bot, the flag is set to false . Besides an automated check, you can also mark a log message as generated by a bot manually. This is done from within the elmah.io UI: The benefit of marking log messages with the isBot flag manually is that elmah.io will then automatically mark new instances of this log message with isBot=true (this feature is available for automatically bot-marked log messages as well). By doing so you get the possibilities listed later in this article. Log messages marked as generated by bots include a small robot icon on the search result:","title":"Bot detection"},{"location":"bot-detection/#search-by-or-not-by-bots","text":"If you want to show all log messages generated by bots you can create a search query like this: isBot:true Or include a search filter for the Is Bot field: By reversing the filter, you see a list of log messages NOT generated by a bot, which can make it easier to get an overview of \"real\" errors.","title":"Search by or not by bots"},{"location":"bot-detection/#hide-or-ignore-log-messages-generated-by-bots","text":"By using the isBot field in Hide and Ignore filters, you can let elmah.io automatically hide or ignore future log messages generated by bots. Be aware that creating ignore filters based on the isBot field is only available for users on the Enterprise plan. To create a new filter, navigate to the Rules tab on log settings, and click the Add a new rule button. Input a query including the isBot field and select either the Hide or Ignore action:","title":"Hide or ignore log messages generated by bots"},{"location":"cli-clear/","text":"Clearing log messages from the CLI The clear command is used to delete one or more messages from a log. Be aware that clearing a log does not reset the monthly counter towards log messages included in your current plan. The clear command is intended for cleanup in non-expired log messages you no longer need. Usage > elmahio clear --help Description: Delete one or more messages from a log Usage: elmahio clear [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The log ID of the log to clear messages --query (REQUIRED) Clear messages matching this query (use * for all messages) --from Optional date and time to clear messages from --to Optional date and time to clear messages to -?, -h, --help Show help and usage information Examples Simple: elmahio clear --apiKey API_KEY --logId LOG_ID --query \"statusCode:404\" Full: elmahio clear --apiKey API_KEY --logId LOG_ID --query \"statusCode:404\" --from 2022-05-17 --to 2022-05-18","title":"Clear"},{"location":"cli-clear/#clearing-log-messages-from-the-cli","text":"The clear command is used to delete one or more messages from a log. Be aware that clearing a log does not reset the monthly counter towards log messages included in your current plan. The clear command is intended for cleanup in non-expired log messages you no longer need.","title":"Clearing log messages from the CLI"},{"location":"cli-clear/#usage","text":"> elmahio clear --help Description: Delete one or more messages from a log Usage: elmahio clear [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The log ID of the log to clear messages --query (REQUIRED) Clear messages matching this query (use * for all messages) --from Optional date and time to clear messages from --to Optional date and time to clear messages to -?, -h, --help Show help and usage information","title":"Usage"},{"location":"cli-clear/#examples","text":"Simple: elmahio clear --apiKey API_KEY --logId LOG_ID --query \"statusCode:404\" Full: elmahio clear --apiKey API_KEY --logId LOG_ID --query \"statusCode:404\" --from 2022-05-17 --to 2022-05-18","title":"Examples"},{"location":"cli-dataloader/","text":"Dataloader loads data from the CLI The dataloader command loads 50 log messages into a specified log. Usage > elmahio dataloader --help Description: Load 50 log messages into the specified log Usage: elmahio dataloader [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The log ID of the log to import messages into -?, -h, --help Show help and usage information Example elmahio dataloader --apiKey API_KEY --logId LOG_ID","title":"Dataloader"},{"location":"cli-dataloader/#dataloader-loads-data-from-the-cli","text":"The dataloader command loads 50 log messages into a specified log.","title":"Dataloader loads data from the CLI"},{"location":"cli-dataloader/#usage","text":"> elmahio dataloader --help Description: Load 50 log messages into the specified log Usage: elmahio dataloader [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The log ID of the log to import messages into -?, -h, --help Show help and usage information","title":"Usage"},{"location":"cli-dataloader/#example","text":"elmahio dataloader --apiKey API_KEY --logId LOG_ID","title":"Example"},{"location":"cli-deployment/","text":"Create a deployment from the CLI The deployment command is used to create new deployments on elmah.io. Usage > elmahio deployment --help Description: Create a new deployment Usage: elmahio deployment [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --version (REQUIRED) The version number of this deployment --created When was this deployment created in UTC --description Description of this deployment --userName The name of the person responsible for creating this deployment --userEmail The email of the person responsible for creating this deployment --logId The ID of a log if this deployment is specific to a single log -?, -h, --help Show help and usage information Examples Simple: elmahio deployment --apiKey API_KEY --version 1.0.0 Full: elmahio deployment --apiKey API_KEY --version 1.0.0 --created 2022-02-08 --description \"My new cool release\" --userName \"Thomas Ardal\" --userEmail \"thomas@elmah.io\" --logId LOG_ID","title":"Deployment"},{"location":"cli-deployment/#create-a-deployment-from-the-cli","text":"The deployment command is used to create new deployments on elmah.io.","title":"Create a deployment from the CLI"},{"location":"cli-deployment/#usage","text":"> elmahio deployment --help Description: Create a new deployment Usage: elmahio deployment [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --version (REQUIRED) The version number of this deployment --created When was this deployment created in UTC --description Description of this deployment --userName The name of the person responsible for creating this deployment --userEmail The email of the person responsible for creating this deployment --logId The ID of a log if this deployment is specific to a single log -?, -h, --help Show help and usage information","title":"Usage"},{"location":"cli-deployment/#examples","text":"Simple: elmahio deployment --apiKey API_KEY --version 1.0.0 Full: elmahio deployment --apiKey API_KEY --version 1.0.0 --created 2022-02-08 --description \"My new cool release\" --userName \"Thomas Ardal\" --userEmail \"thomas@elmah.io\" --logId LOG_ID","title":"Examples"},{"location":"cli-diagnose/","text":"Diagnose potential problems with an elmah.io installation The diagnose command can be run in the root folder of an elmah.io installation to find potential problems with the configuration. Usage > elmahio diagnose --help Description: Diagnose potential problems with an elmah.io installation Usage: elmahio diagnose [options] Options: --directory The root directory to check [default: C:\\test] --verbose Output verbose diagnostics to help debug problems [default: False] -?, -h, --help Show help and usage information Examples Simple: elmahio diagnose --directory c:\\projects\\my-project Full: elmahio diagnose --directory c:\\projects\\my-project --verbose","title":"Diagnose"},{"location":"cli-diagnose/#diagnose-potential-problems-with-an-elmahio-installation","text":"The diagnose command can be run in the root folder of an elmah.io installation to find potential problems with the configuration.","title":"Diagnose potential problems with an elmah.io installation"},{"location":"cli-diagnose/#usage","text":"> elmahio diagnose --help Description: Diagnose potential problems with an elmah.io installation Usage: elmahio diagnose [options] Options: --directory The root directory to check [default: C:\\test] --verbose Output verbose diagnostics to help debug problems [default: False] -?, -h, --help Show help and usage information","title":"Usage"},{"location":"cli-diagnose/#examples","text":"Simple: elmahio diagnose --directory c:\\projects\\my-project Full: elmahio diagnose --directory c:\\projects\\my-project --verbose","title":"Examples"},{"location":"cli-export/","text":"Exporting log messages from the CLI The export command is used to export one or more log messages from a log to JSON. Usage > elmahio export --help Description: Export log messages from a specified log Usage: elmahio export [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The ID of the log to export messages from --dateFrom (REQUIRED) Defines the Date from which the logs start. Ex. \" --dateFrom 2023-03-09\" --dateTo (REQUIRED) Defines the Date from which the logs end. Ex. \" --dateTo 2023-03-16\" --filename Defines the path and filename of the file to export to. Ex. \" --filename C:\\myDirectory\\myFile.json\" [default: C:\\Users\\thoma\\Export-638145521994987555.json] --query Defines the query that is passed to the API [default: *] --includeHeaders Include headers, cookies, etc. in output (will take longer to export) -?, -h, --help Show help and usage information Examples Simple: elmahio export --apiKey API_KEY --logId LOG_ID --dateFrom 2020-08-21 --dateTo 2020-08-28 Full: elmahio export --apiKey API_KEY --logId LOG_ID --dateFrom 2020-08-21 --dateTo 2020-08-28 --filename c:\\temp\\elmahio.json --query \"statusCode: 404\" --includeHeaders","title":"Export"},{"location":"cli-export/#exporting-log-messages-from-the-cli","text":"The export command is used to export one or more log messages from a log to JSON.","title":"Exporting log messages from the CLI"},{"location":"cli-export/#usage","text":"> elmahio export --help Description: Export log messages from a specified log Usage: elmahio export [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The ID of the log to export messages from --dateFrom (REQUIRED) Defines the Date from which the logs start. Ex. \" --dateFrom 2023-03-09\" --dateTo (REQUIRED) Defines the Date from which the logs end. Ex. \" --dateTo 2023-03-16\" --filename Defines the path and filename of the file to export to. Ex. \" --filename C:\\myDirectory\\myFile.json\" [default: C:\\Users\\thoma\\Export-638145521994987555.json] --query Defines the query that is passed to the API [default: *] --includeHeaders Include headers, cookies, etc. in output (will take longer to export) -?, -h, --help Show help and usage information","title":"Usage"},{"location":"cli-export/#examples","text":"Simple: elmahio export --apiKey API_KEY --logId LOG_ID --dateFrom 2020-08-21 --dateTo 2020-08-28 Full: elmahio export --apiKey API_KEY --logId LOG_ID --dateFrom 2020-08-21 --dateTo 2020-08-28 --filename c:\\temp\\elmahio.json --query \"statusCode: 404\" --includeHeaders","title":"Examples"},{"location":"cli-import/","text":"Importing log messages from the CLI The import command is used to import log messages from IIS log files and W3C Extended log files to an elmah.io log. Usage > elmahio import --help Description: Import log messages to a specified log Usage: elmahio import [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The ID of the log to import messages to --type (REQUIRED) The type of log file to import. Use 'w3c' for W3C Extended Log File Format and 'iis' for IIS Log File Format --filename (REQUIRED) Defines the path and filename of the file to import from. Ex. \" --filename C:\\myDirectory\\log.txt\" --dateFrom Defines the Date from which the logs start. Ex. \" --dateFrom 2023-03-06\" --dateTo Defines the Date from which the logs end. Ex. \" --dateTo 2023-03-13\" -?, -h, --help Show help and usage information Examples IIS: elmahio import --apiKey API_KEY --logId LOG_ID --type iis --filename u_inetsv1.log --dateFrom 2023-03-13T10:14 --dateTo 2023-03-13T10:16 w3c: elmahio import --apiKey API_KEY --logId LOG_ID --type w3c --filename u_extend1.log --dateFrom 2023-03-13T10:14 --dateTo 2023-03-13T10:16","title":"Import"},{"location":"cli-import/#importing-log-messages-from-the-cli","text":"The import command is used to import log messages from IIS log files and W3C Extended log files to an elmah.io log.","title":"Importing log messages from the CLI"},{"location":"cli-import/#usage","text":"> elmahio import --help Description: Import log messages to a specified log Usage: elmahio import [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The ID of the log to import messages to --type (REQUIRED) The type of log file to import. Use 'w3c' for W3C Extended Log File Format and 'iis' for IIS Log File Format --filename (REQUIRED) Defines the path and filename of the file to import from. Ex. \" --filename C:\\myDirectory\\log.txt\" --dateFrom Defines the Date from which the logs start. Ex. \" --dateFrom 2023-03-06\" --dateTo Defines the Date from which the logs end. Ex. \" --dateTo 2023-03-13\" -?, -h, --help Show help and usage information","title":"Usage"},{"location":"cli-import/#examples","text":"IIS: elmahio import --apiKey API_KEY --logId LOG_ID --type iis --filename u_inetsv1.log --dateFrom 2023-03-13T10:14 --dateTo 2023-03-13T10:16 w3c: elmahio import --apiKey API_KEY --logId LOG_ID --type w3c --filename u_extend1.log --dateFrom 2023-03-13T10:14 --dateTo 2023-03-13T10:16","title":"Examples"},{"location":"cli-log/","text":"Log a message from the CLI The log command is used to store a log message in a specified log. Usage > elmahio log --help Description: Log a message to the specified log Usage: elmahio log [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The ID of the log to send the log message to --application Used to identify which application logged this message. You can use this if you have multiple applications and services logging to the same log --detail A longer description of the message. For errors this could be a stacktrace, but it's really up to you what to log in there. --hostname The hostname of the server logging the message. --title (REQUIRED) The textual title or headline of the message to log. --titleTemplate The title template of the message to log. This property can be used from logging frameworks that supports structured logging like: \"{user} says {quote}\". In the example, titleTemplate will be this string and title will be \"Gilfoyle says It's not magic. It's talent and sweat\". --source The source of the code logging the message. This could be the assembly name. --statusCode If the message logged relates to a HTTP status code, you can put the code in this property. This would probably only be relevant for errors, but could be used for logging successful status codes as well. --dateTime The date and time in UTC of the message. If you don't provide us with a value in dateTime, we will set the current date and time in UTC. --type The type of message. If logging an error, the type of the exception would go into type but you can put anything in there, that makes sense for your domain. --user An identification of the user triggering this message. You can put the users email address or your user key into this property. --severity An enum value representing the severity of this message. The following values are allowed: Verbose, Debug, Information, Warning, Error, Fatal. --url If message relates to a HTTP request, you may send the URL of that request. If you don't provide us with an URL, we will try to find a key named URL in serverVariables. --method If message relates to a HTTP request, you may send the HTTP method of that request. If you don't provide us with a method, we will try to find a key named REQUEST_METHOD in serverVariables. --version Versions can be used to distinguish messages from different versions of your software. The value of version can be a SemVer compliant string or any other syntax that you are using as your version numbering scheme. --correlationId CorrelationId can be used to group similar log messages together into a single discoverable batch. A correlation ID could be a session ID from ASP.NET Core, a unique string spanning multiple microsservices handling the same request, or similar. --category The category to set on the message. Category can be used to emulate a logger name when created from a logging framework. -?, -h, --help Show help and usage information Examples Simple: elmahio log --apiKey API_KEY --logId LOG_ID --title \"An error happened\" Full: elmahio log --apiKey API_KEY --logId LOG_ID --title \"An error happened\" --application \"My app\" --details \"Some details\" --hostname \"localhost\" --titleTemplate \"An {severity} happened\" --source \"A source\" --statusCode 500 --dateTime 2022-01-13 --type \"The type\" --user \"A user\" --severity \"Error\" --url \"https://elmah.io\" --method \"GET\" --version \"1.0.0\"","title":"Log"},{"location":"cli-log/#log-a-message-from-the-cli","text":"The log command is used to store a log message in a specified log.","title":"Log a message from the CLI"},{"location":"cli-log/#usage","text":"> elmahio log --help Description: Log a message to the specified log Usage: elmahio log [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The ID of the log to send the log message to --application Used to identify which application logged this message. You can use this if you have multiple applications and services logging to the same log --detail A longer description of the message. For errors this could be a stacktrace, but it's really up to you what to log in there. --hostname The hostname of the server logging the message. --title (REQUIRED) The textual title or headline of the message to log. --titleTemplate The title template of the message to log. This property can be used from logging frameworks that supports structured logging like: \"{user} says {quote}\". In the example, titleTemplate will be this string and title will be \"Gilfoyle says It's not magic. It's talent and sweat\". --source The source of the code logging the message. This could be the assembly name. --statusCode If the message logged relates to a HTTP status code, you can put the code in this property. This would probably only be relevant for errors, but could be used for logging successful status codes as well. --dateTime The date and time in UTC of the message. If you don't provide us with a value in dateTime, we will set the current date and time in UTC. --type The type of message. If logging an error, the type of the exception would go into type but you can put anything in there, that makes sense for your domain. --user An identification of the user triggering this message. You can put the users email address or your user key into this property. --severity An enum value representing the severity of this message. The following values are allowed: Verbose, Debug, Information, Warning, Error, Fatal. --url If message relates to a HTTP request, you may send the URL of that request. If you don't provide us with an URL, we will try to find a key named URL in serverVariables. --method If message relates to a HTTP request, you may send the HTTP method of that request. If you don't provide us with a method, we will try to find a key named REQUEST_METHOD in serverVariables. --version Versions can be used to distinguish messages from different versions of your software. The value of version can be a SemVer compliant string or any other syntax that you are using as your version numbering scheme. --correlationId CorrelationId can be used to group similar log messages together into a single discoverable batch. A correlation ID could be a session ID from ASP.NET Core, a unique string spanning multiple microsservices handling the same request, or similar. --category The category to set on the message. Category can be used to emulate a logger name when created from a logging framework. -?, -h, --help Show help and usage information","title":"Usage"},{"location":"cli-log/#examples","text":"Simple: elmahio log --apiKey API_KEY --logId LOG_ID --title \"An error happened\" Full: elmahio log --apiKey API_KEY --logId LOG_ID --title \"An error happened\" --application \"My app\" --details \"Some details\" --hostname \"localhost\" --titleTemplate \"An {severity} happened\" --source \"A source\" --statusCode 500 --dateTime 2022-01-13 --type \"The type\" --user \"A user\" --severity \"Error\" --url \"https://elmah.io\" --method \"GET\" --version \"1.0.0\"","title":"Examples"},{"location":"cli-overview/","text":"CLI overview The elmah.io CLI lets you execute common tasks against elmah.io. Installing the CLI The elmah.io CLI requires .NET 6 or newer installed. The elmah.io CLI can be installed in several ways. To set up everything automatically, execute the following script from the command line: dotnet tool install --global Elmah.Io.Cli or make sure to run on the latest version if you already have the CLI installed: dotnet tool update --global Elmah.Io.Cli If you prefer downloading the CLI as a zip you can download the latest version from GitHub . To clone and build the CLI manually, check out the instructions below. Run the CLI Clear Dataloader Deployment Diagnose Export Import Log Sourcemap Tail Run the CLI to get help: elmahio --help Help similar to this is outputted to the console: elmahio: CLI for executing various actions against elmah.io Usage: elmahio [options] [command] Options: --nologo Doesn't display the startup banner or the copyright message --version Show version information -?, -h, --help Show help and usage information Commands: clear Delete one or more messages from a log dataloader Load 50 log messages into the specified log deployment Create a new deployment diagnose Diagnose potential problems with an elmah.io installation export Export log messages from a specified log import Import log messages to a specified log log Log a message to the specified log sourcemap Upload a source map and minified JavaScript tail Tail log messages from a specified log Cloning the CLI Create a new folder and git clone the repository: git clone https://github.com/elmahio/Elmah.Io.Cli.git Building the CLI Navigate to the root repository of the code and execute the following command: dotnet build","title":"CLI overview"},{"location":"cli-overview/#cli-overview","text":"The elmah.io CLI lets you execute common tasks against elmah.io.","title":"CLI overview"},{"location":"cli-overview/#installing-the-cli","text":"The elmah.io CLI requires .NET 6 or newer installed. The elmah.io CLI can be installed in several ways. To set up everything automatically, execute the following script from the command line: dotnet tool install --global Elmah.Io.Cli or make sure to run on the latest version if you already have the CLI installed: dotnet tool update --global Elmah.Io.Cli If you prefer downloading the CLI as a zip you can download the latest version from GitHub . To clone and build the CLI manually, check out the instructions below.","title":"Installing the CLI"},{"location":"cli-overview/#run-the-cli","text":"Clear Dataloader Deployment Diagnose Export Import Log Sourcemap Tail Run the CLI to get help: elmahio --help Help similar to this is outputted to the console: elmahio: CLI for executing various actions against elmah.io Usage: elmahio [options] [command] Options: --nologo Doesn't display the startup banner or the copyright message --version Show version information -?, -h, --help Show help and usage information Commands: clear Delete one or more messages from a log dataloader Load 50 log messages into the specified log deployment Create a new deployment diagnose Diagnose potential problems with an elmah.io installation export Export log messages from a specified log import Import log messages to a specified log log Log a message to the specified log sourcemap Upload a source map and minified JavaScript tail Tail log messages from a specified log","title":"Run the CLI"},{"location":"cli-overview/#cloning-the-cli","text":"Create a new folder and git clone the repository: git clone https://github.com/elmahio/Elmah.Io.Cli.git","title":"Cloning the CLI"},{"location":"cli-overview/#building-the-cli","text":"Navigate to the root repository of the code and execute the following command: dotnet build","title":"Building the CLI"},{"location":"cli-sourcemap/","text":"Upload a source map from the CLI The sourcemap command is used to upload source maps and minified JavaScript files to elmah.io. Usage > elmahio sourcemap --help Description: Upload a source map and minified JavaScript Usage: elmahio sourcemap [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The ID of the log which should contain the minified JavaScript and source map --path (REQUIRED) An URL to the online minified JavaScript file --sourceMap (REQUIRED) The source map file. Only files with an extension of .map and content type of application/json will be accepted --minifiedJavaScript (REQUIRED) The minified JavaScript file. Only files with an extension of .js and content type of text/javascript will be accepted -?, -h, --help Show help and usage information Examples sourcemap --apiKey API_KEY --logId LOG_ID --path \"/bundles/sharedbundle.min.js\" --sourceMap \"c:\\path\\to\\sharedbundle.map\" --minifiedJavaScript \"c:\\path\\to\\sharedbundle.min.js\"","title":"Sourcemap"},{"location":"cli-sourcemap/#upload-a-source-map-from-the-cli","text":"The sourcemap command is used to upload source maps and minified JavaScript files to elmah.io.","title":"Upload a source map from the CLI"},{"location":"cli-sourcemap/#usage","text":"> elmahio sourcemap --help Description: Upload a source map and minified JavaScript Usage: elmahio sourcemap [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The ID of the log which should contain the minified JavaScript and source map --path (REQUIRED) An URL to the online minified JavaScript file --sourceMap (REQUIRED) The source map file. Only files with an extension of .map and content type of application/json will be accepted --minifiedJavaScript (REQUIRED) The minified JavaScript file. Only files with an extension of .js and content type of text/javascript will be accepted -?, -h, --help Show help and usage information","title":"Usage"},{"location":"cli-sourcemap/#examples","text":"sourcemap --apiKey API_KEY --logId LOG_ID --path \"/bundles/sharedbundle.min.js\" --sourceMap \"c:\\path\\to\\sharedbundle.map\" --minifiedJavaScript \"c:\\path\\to\\sharedbundle.min.js\"","title":"Examples"},{"location":"cli-tail/","text":"Tail log messages from the CLI The tail command is used to tail log messages in a specified log. Usage > elmahio tail --help Description: Tail log messages from a specified log Usage: elmahio tail [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The ID of the log to send the log message to -?, -h, --help Show help and usage information Example elmahio tail --apiKey API_KEY --logId LOG_ID","title":"Tail"},{"location":"cli-tail/#tail-log-messages-from-the-cli","text":"The tail command is used to tail log messages in a specified log.","title":"Tail log messages from the CLI"},{"location":"cli-tail/#usage","text":"> elmahio tail --help Description: Tail log messages from a specified log Usage: elmahio tail [options] Options: --apiKey (REQUIRED) An API key with permission to execute the command --logId (REQUIRED) The ID of the log to send the log message to -?, -h, --help Show help and usage information","title":"Usage"},{"location":"cli-tail/#example","text":"elmahio tail --apiKey API_KEY --logId LOG_ID","title":"Example"},{"location":"configure-elmah-io-from-code/","text":"Configure elmah.io from code You typically configure elmah.io in your web.config file. With a little help from some custom code, you will be able to configure everything in code as well: using Elmah; using System.Collections.Generic; using System.ComponentModel.Design; [assembly: WebActivatorEx.PreApplicationStartMethod(typeof(ElmahFromCodeExample.ElmahConfig), \"Start\")] namespace ElmahFromCodeExample { public static class ElmahConfig { public static void Start() { ServiceCenter.Current = CreateServiceProviderQueryHandler(ServiceCenter.Current); HttpApplication.RegisterModule(typeof(ErrorLogModule)); } private static ServiceProviderQueryHandler CreateServiceProviderQueryHandler(ServiceProviderQueryHandler sp) { return context => { var container = new ServiceContainer(sp(context)); var config = new Dictionary(); config[\"apiKey\"] = \"API_KEY\"; config[\"logId\"] = \"LOG_ID\"; var log = new Elmah.Io.ErrorLog(config); container.AddService(typeof(Elmah.ErrorLog), log); return container; }; } } } Replace API_KEY with your API key ( Where is my API key? ) and LOG_ID with a log ID ( Where is my log ID? ). Let's look at the code. Our class ElmahConfig is configured as a PreApplicationStartMethod which means, that ASP.NET (MVC) will execute the Start method when the web application starts up. Inside this method, we set the ServiceCenter.Current property to the return type of the CreateServiceProviderQueryHandler method. This method is where the magic happens. Besides creating the new ServiceContainer , we created the Elmah.Io.ErrorLog class normally configured through XML. The Dictionary should contain the API key and log ID as explained earlier. In the second line of the Start -method, we call the RegisterModule -method with ErrorLogModule as parameter. This replaces the need for registering the module in web.config as part of the system.webServer element. That's it! You no longer need the element, config sections, or anything else related to ELMAH and elmah.io in your web.config file.","title":"Configure elmah.io from code"},{"location":"configure-elmah-io-from-code/#configure-elmahio-from-code","text":"You typically configure elmah.io in your web.config file. With a little help from some custom code, you will be able to configure everything in code as well: using Elmah; using System.Collections.Generic; using System.ComponentModel.Design; [assembly: WebActivatorEx.PreApplicationStartMethod(typeof(ElmahFromCodeExample.ElmahConfig), \"Start\")] namespace ElmahFromCodeExample { public static class ElmahConfig { public static void Start() { ServiceCenter.Current = CreateServiceProviderQueryHandler(ServiceCenter.Current); HttpApplication.RegisterModule(typeof(ErrorLogModule)); } private static ServiceProviderQueryHandler CreateServiceProviderQueryHandler(ServiceProviderQueryHandler sp) { return context => { var container = new ServiceContainer(sp(context)); var config = new Dictionary(); config[\"apiKey\"] = \"API_KEY\"; config[\"logId\"] = \"LOG_ID\"; var log = new Elmah.Io.ErrorLog(config); container.AddService(typeof(Elmah.ErrorLog), log); return container; }; } } } Replace API_KEY with your API key ( Where is my API key? ) and LOG_ID with a log ID ( Where is my log ID? ). Let's look at the code. Our class ElmahConfig is configured as a PreApplicationStartMethod which means, that ASP.NET (MVC) will execute the Start method when the web application starts up. Inside this method, we set the ServiceCenter.Current property to the return type of the CreateServiceProviderQueryHandler method. This method is where the magic happens. Besides creating the new ServiceContainer , we created the Elmah.Io.ErrorLog class normally configured through XML. The Dictionary should contain the API key and log ID as explained earlier. In the second line of the Start -method, we call the RegisterModule -method with ErrorLogModule as parameter. This replaces the need for registering the module in web.config as part of the system.webServer element. That's it! You no longer need the element, config sections, or anything else related to ELMAH and elmah.io in your web.config file.","title":"Configure elmah.io from code"},{"location":"configure-elmah-io-manually/","text":"Configure elmah.io manually The Elmah.Io NuGet package normally adds all of the necessary configuration, to get up and running with elmah.io. This is one of our killer features and our customers tell us, that we have the simplest installer on the market. In some cases, you may experience problems with the automatic configuration, though. Different reasons can cause the configuration not to be added automatically. The most common reason is restrictions to executing PowerShell inside Visual Studio. Start by installing the Elmah.Io package: Package Manager .NET CLI PackageReference Paket CLI Install-Package Elmah.Io dotnet add package Elmah.Io paket add Elmah.Io If a dialog is shown during the installation, input your API key ( Where is my API key? ) and log ID ( Where is my log ID? ). Don't worry if the configuration isn't added, since we will verify this later. Add the following to the element in your web.config : Add the following to the element (inside ) in your web.config : Add the following to the element (inside ) in your web.config : Add the following to the system.webServer element in your web.config : Add the following as a root element beneath the element in your web.config : Replace API_KEY with your API key ( Where is my API key? ) and LOG_ID with your log ID ( Where is my log ID? ). That's it. You managed to install elmah.io manually and you should go to your LinkedIn profile and update with a new certification called \"Certified elmah.io installer\" :) Here's a full example of ELMAH configuration in a web.config file: In case you need to access your error log on /elmah.axd , you need to add the following to the element in your web.config : We don't recommend browsing your error logs through the /elmah.axd endpoint. The elmah.io UI will let you control different levels of access and more.","title":"Configure elmah.io manually"},{"location":"configure-elmah-io-manually/#configure-elmahio-manually","text":"The Elmah.Io NuGet package normally adds all of the necessary configuration, to get up and running with elmah.io. This is one of our killer features and our customers tell us, that we have the simplest installer on the market. In some cases, you may experience problems with the automatic configuration, though. Different reasons can cause the configuration not to be added automatically. The most common reason is restrictions to executing PowerShell inside Visual Studio. Start by installing the Elmah.Io package: Package Manager .NET CLI PackageReference Paket CLI Install-Package Elmah.Io dotnet add package Elmah.Io paket add Elmah.Io If a dialog is shown during the installation, input your API key ( Where is my API key? ) and log ID ( Where is my log ID? ). Don't worry if the configuration isn't added, since we will verify this later. Add the following to the element in your web.config : Add the following to the element (inside ) in your web.config : Add the following to the element (inside ) in your web.config : Add the following to the system.webServer element in your web.config : Add the following as a root element beneath the element in your web.config : Replace API_KEY with your API key ( Where is my API key? ) and LOG_ID with your log ID ( Where is my log ID? ). That's it. You managed to install elmah.io manually and you should go to your LinkedIn profile and update with a new certification called \"Certified elmah.io installer\" :) Here's a full example of ELMAH configuration in a web.config file: In case you need to access your error log on /elmah.axd , you need to add the following to the element in your web.config : We don't recommend browsing your error logs through the /elmah.axd endpoint. The elmah.io UI will let you control different levels of access and more.","title":"Configure elmah.io manually"},{"location":"create-deployments-from-atlassian-bamboo/","text":"Create deployments from Atlassian Bamboo Setting up elmah.io Deployment Tracking on Bamboo is easy using a bit of PowerShell. Add a new Script Task and select Windows PowerShell in Interpreter . Select Inline in Script location and add the following PowerShell to Script body : $ProgressPreference = \"SilentlyContinue\" Write-Host $bamboo_buildNumber $url = \"https://api.elmah.io/v3/deployments?api_key=API_KEY\" $body = @{ version = $Env:bamboo_buildNumber logId = \"LOG_ID\" } [Net.ServicePointManager]::SecurityProtocol = ` [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls Invoke-RestMethod -Method Post -Uri $url -Body $body Replace API_KEY and LOG_ID and everything is configured. The script uses the build number of the current build as version number ( $Env:bamboo_buildNumber ). If you prefer another scheme, Bamboo offers a range of variables .","title":"Create deployments from Atlassian Bamboo"},{"location":"create-deployments-from-atlassian-bamboo/#create-deployments-from-atlassian-bamboo","text":"Setting up elmah.io Deployment Tracking on Bamboo is easy using a bit of PowerShell. Add a new Script Task and select Windows PowerShell in Interpreter . Select Inline in Script location and add the following PowerShell to Script body : $ProgressPreference = \"SilentlyContinue\" Write-Host $bamboo_buildNumber $url = \"https://api.elmah.io/v3/deployments?api_key=API_KEY\" $body = @{ version = $Env:bamboo_buildNumber logId = \"LOG_ID\" } [Net.ServicePointManager]::SecurityProtocol = ` [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls Invoke-RestMethod -Method Post -Uri $url -Body $body Replace API_KEY and LOG_ID and everything is configured. The script uses the build number of the current build as version number ( $Env:bamboo_buildNumber ). If you prefer another scheme, Bamboo offers a range of variables .","title":"Create deployments from Atlassian Bamboo"},{"location":"create-deployments-from-azure-devops-pipelines/","text":"Create deployments from Azure DevOps Pipelines Create deployments from Azure DevOps Pipelines Using YAML Using Classic editor Notifying elmah.io about new deployments is possible as a build step in Azure DevOps, by adding a bit of PowerShell. Using YAML Edit your build definition YAML file. If not already shown, open the assistant by clicking the Show assistant button. Search for 'powershell'. Click the PowerShell task. Select the Inline radio button and input the following script: $ProgressPreference = \"SilentlyContinue\" $url = \"https://api.elmah.io/v3/deployments?api_key=API_KEY\" $body = @{ version = \"$env:BUILD_BUILDNUMBER\" description = \"$env:BUILD_SOURCEVERSIONMESSAGE\" userName = \"$env:BUILD_REQUESTEDFOR\" userEmail = \"$env:BUILD_REQUESTEDFOREMAIL\" logId = \"LOG_ID\" } [Net.ServicePointManager]::SecurityProtocol = ` [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls Invoke-RestMethod -Method Post -Uri $url -Body $body Replace API_KEY with your API key ( Where is my API key? ) and LOG_ID ( Where is my log ID? ) with the id of the log representing the application deployed by this build configuration. Click the Add button and the new task will be added to your YAML definition. You typically want to move the deployment task to the last placement in tasks . Using Classic editor Edit the build definition currently building your project(s). Click the Add task button and locate the PowerShell task. Click Add . Fill in the details as shown in the screenshot. ... and here's the code from the screenshot above: $ProgressPreference = \"SilentlyContinue\" $url = \"https://api.elmah.io/v3/deployments?api_key=API_KEY\" $body = @{ version = \"$env:BUILD_BUILDNUMBER\" description = \"$env:BUILD_SOURCEVERSIONMESSAGE\" userName = \"$env:BUILD_REQUESTEDFOR\" userEmail = \"$env:BUILD_REQUESTEDFOREMAIL\" logId = \"LOG_ID\" } [Net.ServicePointManager]::SecurityProtocol = ` [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls Invoke-RestMethod -Method Post -Uri $url -Body $body Replace API_KEY with your API key ( Where is my API key? ) and LOG_ID ( Where is my log ID? ) with the id of the log representing the application deployed by this build configuration.","title":"Create deployments from Azure DevOps Pipelines"},{"location":"create-deployments-from-azure-devops-pipelines/#create-deployments-from-azure-devops-pipelines","text":"Create deployments from Azure DevOps Pipelines Using YAML Using Classic editor Notifying elmah.io about new deployments is possible as a build step in Azure DevOps, by adding a bit of PowerShell.","title":"Create deployments from Azure DevOps Pipelines"},{"location":"create-deployments-from-azure-devops-pipelines/#using-yaml","text":"Edit your build definition YAML file. If not already shown, open the assistant by clicking the Show assistant button. Search for 'powershell'. Click the PowerShell task. Select the Inline radio button and input the following script: $ProgressPreference = \"SilentlyContinue\" $url = \"https://api.elmah.io/v3/deployments?api_key=API_KEY\" $body = @{ version = \"$env:BUILD_BUILDNUMBER\" description = \"$env:BUILD_SOURCEVERSIONMESSAGE\" userName = \"$env:BUILD_REQUESTEDFOR\" userEmail = \"$env:BUILD_REQUESTEDFOREMAIL\" logId = \"LOG_ID\" } [Net.ServicePointManager]::SecurityProtocol = ` [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls Invoke-RestMethod -Method Post -Uri $url -Body $body Replace API_KEY with your API key ( Where is my API key? ) and LOG_ID ( Where is my log ID? ) with the id of the log representing the application deployed by this build configuration. Click the Add button and the new task will be added to your YAML definition. You typically want to move the deployment task to the last placement in tasks .","title":"Using YAML"},{"location":"create-deployments-from-azure-devops-pipelines/#using-classic-editor","text":"Edit the build definition currently building your project(s). Click the Add task button and locate the PowerShell task. Click Add . Fill in the details as shown in the screenshot. ... and here's the code from the screenshot above: $ProgressPreference = \"SilentlyContinue\" $url = \"https://api.elmah.io/v3/deployments?api_key=API_KEY\" $body = @{ version = \"$env:BUILD_BUILDNUMBER\" description = \"$env:BUILD_SOURCEVERSIONMESSAGE\" userName = \"$env:BUILD_REQUESTEDFOR\" userEmail = \"$env:BUILD_REQUESTEDFOREMAIL\" logId = \"LOG_ID\" } [Net.ServicePointManager]::SecurityProtocol = ` [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls Invoke-RestMethod -Method Post -Uri $url -Body $body Replace API_KEY with your API key ( Where is my API key? ) and LOG_ID ( Where is my log ID? ) with the id of the log representing the application deployed by this build configuration.","title":"Using Classic editor"},{"location":"create-deployments-from-azure-devops-releases/","text":"Create deployments from Azure DevOps Releases If you are using Releases in Azure DevOps, you should use our extension to notify elmah.io about new deployments. To install and configure the extension, follow the simple steps below: Go to the elmah.io Deployment Tasks extension on the Azure DevOps Marketplace and click the Get it free button: Select your organization and click the Install button: Go to your Azure DevOps project and add the elmah.io Deployment Notification task. Fill in all fields as shown here: You will need to replace API_KEY with an API key ( Where is my API key? ) with permission ( How to configure API key permissions ) to create deployments. If the deployment is specific to a single log, insert a log ID ( Where is my log ID? ) with the ID of the log instead of LOG_ID . Deployments without a log ID will show on all logs in the organization. That's it! Azure DevOps will now notify elmah.io every time the release pipeline is executed.","title":"Create deployments from Azure DevOps Releases"},{"location":"create-deployments-from-azure-devops-releases/#create-deployments-from-azure-devops-releases","text":"If you are using Releases in Azure DevOps, you should use our extension to notify elmah.io about new deployments. To install and configure the extension, follow the simple steps below: Go to the elmah.io Deployment Tasks extension on the Azure DevOps Marketplace and click the Get it free button: Select your organization and click the Install button: Go to your Azure DevOps project and add the elmah.io Deployment Notification task. Fill in all fields as shown here: You will need to replace API_KEY with an API key ( Where is my API key? ) with permission ( How to configure API key permissions ) to create deployments. If the deployment is specific to a single log, insert a log ID ( Where is my log ID? ) with the ID of the log instead of LOG_ID . Deployments without a log ID will show on all logs in the organization. That's it! Azure DevOps will now notify elmah.io every time the release pipeline is executed.","title":"Create deployments from Azure DevOps Releases"},{"location":"create-deployments-from-bitbucket-pipelines/","text":"Create deployments from Bitbucket Pipelines Pipelines use scripts, embedded in YAML files, to configure the different steps required to build and deploy software. To notify elmah.io as part of a build/deployment, the first you will need to do is to add your API key as a secure environment variable. To do so, go to Settings | Workspace Settings | Workspace variables and add a new variable: Where is my API key? Then add a new script to your build YAML-file after building and deploying your software: pipelines: default: - step: script: # ... - curl -X POST -d \"{\\\"version\\\":\\\"$BITBUCKET_BUILD_NUMBER\\\"}\" -H \"Content-Type:application/json\" https://api.elmah.io/v3/deployments?api_key=$ELMAHIO_APIKEY The script uses curl to invoke the elmah.io Deployments endpoint with the API key ( $ELMAHIO_APIKEY ) and a version number ( $BITBUCKET_BUILD_NUMBER ). The posted JSON can be extended to support additional properties like a changelog and the name of the person triggering the deployment. Check out the API documentation for details.","title":"Create deployments from Bitbucket Pipelines"},{"location":"create-deployments-from-bitbucket-pipelines/#create-deployments-from-bitbucket-pipelines","text":"Pipelines use scripts, embedded in YAML files, to configure the different steps required to build and deploy software. To notify elmah.io as part of a build/deployment, the first you will need to do is to add your API key as a secure environment variable. To do so, go to Settings | Workspace Settings | Workspace variables and add a new variable: Where is my API key? Then add a new script to your build YAML-file after building and deploying your software: pipelines: default: - step: script: # ... - curl -X POST -d \"{\\\"version\\\":\\\"$BITBUCKET_BUILD_NUMBER\\\"}\" -H \"Content-Type:application/json\" https://api.elmah.io/v3/deployments?api_key=$ELMAHIO_APIKEY The script uses curl to invoke the elmah.io Deployments endpoint with the API key ( $ELMAHIO_APIKEY ) and a version number ( $BITBUCKET_BUILD_NUMBER ). The posted JSON can be extended to support additional properties like a changelog and the name of the person triggering the deployment. Check out the API documentation for details.","title":"Create deployments from Bitbucket Pipelines"},{"location":"create-deployments-from-cli/","text":"Create deployments from the elmah.io CLI Deployments can be easily created from either the command-line or a build server using the elmah.io CLI. There's a help page dedicated to the deployment command but here's a quick recap. If not already installed, start by installing the elmah.io CLI: dotnet tool install --global Elmah.Io.Cli Then, create a new deployment using the deployment command: elmahio deployment --apiKey API_KEY --version 1.0.0 In case you are calling the CLI from a build server, you may want to exclude the elmah.io logo and copyright message using the --nologo parameter to reduce log output and to avoid cluttering the build output: elmahio deployment --nologo --apiKey API_KEY --version 1.0.0","title":"Create deployments from CLI"},{"location":"create-deployments-from-cli/#create-deployments-from-the-elmahio-cli","text":"Deployments can be easily created from either the command-line or a build server using the elmah.io CLI. There's a help page dedicated to the deployment command but here's a quick recap. If not already installed, start by installing the elmah.io CLI: dotnet tool install --global Elmah.Io.Cli Then, create a new deployment using the deployment command: elmahio deployment --apiKey API_KEY --version 1.0.0 In case you are calling the CLI from a build server, you may want to exclude the elmah.io logo and copyright message using the --nologo parameter to reduce log output and to avoid cluttering the build output: elmahio deployment --nologo --apiKey API_KEY --version 1.0.0","title":"Create deployments from the elmah.io CLI"},{"location":"create-deployments-from-github-actions/","text":"Create deployments from GitHub Actions GitHub Actions is a great platform for building and releasing software. To notify elmah.io when you deploy a new version of your project, you will need an additional step in your build definition. Before you do that, start by creating new secrets: Go to your project on GitHub. Click the Settings tab. Click the Secrets navigation item. Click New repository secret . Name the secret ELMAH_IO_API_KEY . Insert your elmah.io API key in Value ( Where is my API key? ). Make sure to use an API key that includes the Deployments | Write permission ( How to configure API key permissions ). Click Add secret Do the same for your elmah.io log ID but name it ELMAH_IO_LOG_ID ( Where is my log ID? ). Insert the following step as the last one in your YAML build specification: - name: Create Deployment on elmah.io uses: elmahio/github-create-deployment-action@v1 with: apiKey: ${{ secrets.ELMAH_IO_API_KEY }} version: ${{ github.run_number }} logId: ${{ secrets.ELMAH_IO_LOG_ID }} The configuration will automatically notify elmah.io every time the build script is running. The build number ( github.run_number ) is used as the version for this sample, but you can modify this if you prefer another scheme. Here's a full overview of properties: Name Required Description apiKey \u2714\ufe0f An API key with permission to create deployments. version \u2714\ufe0f The version number of this deployment. The value of version can be a SemVer compliant string or any other syntax that you are using as your version numbering scheme. You can use ${{ github.run_number }} to use the build number as the version or you can pick another scheme or combine the two. description Optional description of this deployment. Can be markdown or cleartext. The latest commit message can be used as the description by using ${{ github.event.head_commit.message }} . userName The name of the person responsible for creating this deployment. This can be set manually or dynamically using the ${{ github.actor }} variable. userEmail The email of the person responsible for creating this deployment. There doesn't seem to be a way to pull the email responsible for triggering the build through variables, why this will need to be set manually. logId As default, deployments are attached to all logs of the organization. If you want a deployment to attach to a single log only, set this to the ID of that log.","title":"Create deployments from GitHub Actions"},{"location":"create-deployments-from-github-actions/#create-deployments-from-github-actions","text":"GitHub Actions is a great platform for building and releasing software. To notify elmah.io when you deploy a new version of your project, you will need an additional step in your build definition. Before you do that, start by creating new secrets: Go to your project on GitHub. Click the Settings tab. Click the Secrets navigation item. Click New repository secret . Name the secret ELMAH_IO_API_KEY . Insert your elmah.io API key in Value ( Where is my API key? ). Make sure to use an API key that includes the Deployments | Write permission ( How to configure API key permissions ). Click Add secret Do the same for your elmah.io log ID but name it ELMAH_IO_LOG_ID ( Where is my log ID? ). Insert the following step as the last one in your YAML build specification: - name: Create Deployment on elmah.io uses: elmahio/github-create-deployment-action@v1 with: apiKey: ${{ secrets.ELMAH_IO_API_KEY }} version: ${{ github.run_number }} logId: ${{ secrets.ELMAH_IO_LOG_ID }} The configuration will automatically notify elmah.io every time the build script is running. The build number ( github.run_number ) is used as the version for this sample, but you can modify this if you prefer another scheme. Here's a full overview of properties: Name Required Description apiKey \u2714\ufe0f An API key with permission to create deployments. version \u2714\ufe0f The version number of this deployment. The value of version can be a SemVer compliant string or any other syntax that you are using as your version numbering scheme. You can use ${{ github.run_number }} to use the build number as the version or you can pick another scheme or combine the two. description Optional description of this deployment. Can be markdown or cleartext. The latest commit message can be used as the description by using ${{ github.event.head_commit.message }} . userName The name of the person responsible for creating this deployment. This can be set manually or dynamically using the ${{ github.actor }} variable. userEmail The email of the person responsible for creating this deployment. There doesn't seem to be a way to pull the email responsible for triggering the build through variables, why this will need to be set manually. logId As default, deployments are attached to all logs of the organization. If you want a deployment to attach to a single log only, set this to the ID of that log.","title":"Create deployments from GitHub Actions"},{"location":"create-deployments-from-kudu/","text":"Create deployments from Kudu Kudu is the engine behind Git deployments on Microsoft Azure. To create a new elmah.io deployment every time you deploy a new app service to Azure, add a new post-deployment script by navigating your browser to https://yoursite.scm.azurewebsites.net where yoursite is the name of your Azure website. Click the Debug console and navigate to site\\deployments\\tools\\PostDeploymentActions (create it if it doesn't exist). To create the new PowerShell file, write the following in the prompt: touch CreateDeployment.ps1 With a post-deployment script running inside Kudu, we can to extract some more information about the current deployment. A full deployment PowerShell script for Kudu would look like this: $version = Get-Date -format u (Get-Content ..\\wwwroot\\web.config).replace('$version', $version) | Set-Content ..\\wwwroot\\web.config $ProgressPreference = \"SilentlyContinue\" $commit = [System.Environment]::GetEnvironmentVariable(\"SCM_COMMIT_MESSAGE\"); $commitId = [System.Environment]::GetEnvironmentVariable(\"SCM_COMMIT_ID\"); $httpHost = [System.Environment]::GetEnvironmentVariable(\"HTTP_HOST\"); $deployUrl = \"https://$httpHost/api/deployments/$commitId\" $username = \"MY_USERNAME\" $password = \"MY_PASSWORD\" $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((\"{0}:{1}\" -f $username,$password))) $deployInfo = Invoke-RestMethod -Method Get -Uri $deployUrl -Headers @{Authorization=(\"Basic {0}\" -f $base64AuthInfo)} $url = 'https://api.elmah.io/v3/deployments?api_key=API_KEY' $body = @{ version = $version description = $commit userName = $deployInfo.author userEmail = $deployInfo.author_email } [Net.ServicePointManager]::SecurityProtocol = ` [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls Invoke-RestMethod -Method Post -Uri $url -Body $body (replace MY_USERNAME and MY_PASSWORD with your Azure deployment credentials and API_KEY with your elmah.io API key located on your organization settings page) The script generates a new version string from the current date and time. How you want your version string looking, is really up to you. To fetch additional information about the deployment, the Kudu deployments endpoint is requested with the current commit id. Finally, the script creates the deployment using the elmah.io REST API.","title":"Create deployments from Kudu"},{"location":"create-deployments-from-kudu/#create-deployments-from-kudu","text":"Kudu is the engine behind Git deployments on Microsoft Azure. To create a new elmah.io deployment every time you deploy a new app service to Azure, add a new post-deployment script by navigating your browser to https://yoursite.scm.azurewebsites.net where yoursite is the name of your Azure website. Click the Debug console and navigate to site\\deployments\\tools\\PostDeploymentActions (create it if it doesn't exist). To create the new PowerShell file, write the following in the prompt: touch CreateDeployment.ps1 With a post-deployment script running inside Kudu, we can to extract some more information about the current deployment. A full deployment PowerShell script for Kudu would look like this: $version = Get-Date -format u (Get-Content ..\\wwwroot\\web.config).replace('$version', $version) | Set-Content ..\\wwwroot\\web.config $ProgressPreference = \"SilentlyContinue\" $commit = [System.Environment]::GetEnvironmentVariable(\"SCM_COMMIT_MESSAGE\"); $commitId = [System.Environment]::GetEnvironmentVariable(\"SCM_COMMIT_ID\"); $httpHost = [System.Environment]::GetEnvironmentVariable(\"HTTP_HOST\"); $deployUrl = \"https://$httpHost/api/deployments/$commitId\" $username = \"MY_USERNAME\" $password = \"MY_PASSWORD\" $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((\"{0}:{1}\" -f $username,$password))) $deployInfo = Invoke-RestMethod -Method Get -Uri $deployUrl -Headers @{Authorization=(\"Basic {0}\" -f $base64AuthInfo)} $url = 'https://api.elmah.io/v3/deployments?api_key=API_KEY' $body = @{ version = $version description = $commit userName = $deployInfo.author userEmail = $deployInfo.author_email } [Net.ServicePointManager]::SecurityProtocol = ` [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls Invoke-RestMethod -Method Post -Uri $url -Body $body (replace MY_USERNAME and MY_PASSWORD with your Azure deployment credentials and API_KEY with your elmah.io API key located on your organization settings page) The script generates a new version string from the current date and time. How you want your version string looking, is really up to you. To fetch additional information about the deployment, the Kudu deployments endpoint is requested with the current commit id. Finally, the script creates the deployment using the elmah.io REST API.","title":"Create deployments from Kudu"},{"location":"create-deployments-from-octopus-deploy/","text":"Create deployments from Octopus Deploy Notifying elmah.io of a new deployment from Octopus Deploy is supported through a custom step template. The step template can be installed in multiple ways as explained on Community step templates . In this document, the step template will be installed directly from the Process Editor : Go to the Process Editor and click the ADD STEP button. In the Choose Step Template section search for 'elmah.io': Hover over the 'elmah.io - Register Deployment' community template and click the INSTALL AND ADD button. In the Install and add modal click the SAVE button. The step template is now added to the process. Fill in your API key ( Where is my API key? ) and log ID ( Where is my log ID? ) in the step template fields and click the SAVE button: And we're done. On every new deployment, Octopus Deploy will notify elmah.io. In case you want an alternative version naming scheme, the Version field in the step template can be used to change the format.","title":"Create deployments from Octopus Deploy"},{"location":"create-deployments-from-octopus-deploy/#create-deployments-from-octopus-deploy","text":"Notifying elmah.io of a new deployment from Octopus Deploy is supported through a custom step template. The step template can be installed in multiple ways as explained on Community step templates . In this document, the step template will be installed directly from the Process Editor : Go to the Process Editor and click the ADD STEP button. In the Choose Step Template section search for 'elmah.io': Hover over the 'elmah.io - Register Deployment' community template and click the INSTALL AND ADD button. In the Install and add modal click the SAVE button. The step template is now added to the process. Fill in your API key ( Where is my API key? ) and log ID ( Where is my log ID? ) in the step template fields and click the SAVE button: And we're done. On every new deployment, Octopus Deploy will notify elmah.io. In case you want an alternative version naming scheme, the Version field in the step template can be used to change the format.","title":"Create deployments from Octopus Deploy"},{"location":"create-deployments-from-powershell/","text":"Create deployments from PowerShell If you release your software using a build or deployment server, creating the new release is easy using a bit of PowerShell. To request the deployments endpoint, write the following PowerShell script: $version = \"1.42.7\" $ProgressPreference = \"SilentlyContinue\" $url = 'https://api.elmah.io/v3/deployments?api_key=API_KEY' $body = @{ version = $version } [Net.ServicePointManager]::SecurityProtocol = ` [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls Invoke-RestMethod -Method Post -Uri $url -Body $body (replace API_KEY with your API key found on your organization settings page) In the example, a simple version string is sent to the API and elmah.io will automatically put a timestamp on that. Overriding user information and description make the experience within the elmah.io UI better. Pulling release notes and the name and email of the deployer, is usually available through environment variables or similar, depending on the technology used for creating the deployment. Here's an example of a full payload for the create deployment endpoint: $body = @{ version = \"1.0.0\" created = [datetime]::UtcNow.ToString(\"o\") description = \"my deployment\" userName = \"Thomas\" userEmail = \"thomas@elmah.io\" logId = \"39e60b0b-21b4-4d12-8f09-81f3642c64be\" } In this example, the deployment belongs to a single log why the logId property is set. The description property can be used to include a changelog or similar. Markdown is supported.","title":"Create deployments from PowerShell"},{"location":"create-deployments-from-powershell/#create-deployments-from-powershell","text":"If you release your software using a build or deployment server, creating the new release is easy using a bit of PowerShell. To request the deployments endpoint, write the following PowerShell script: $version = \"1.42.7\" $ProgressPreference = \"SilentlyContinue\" $url = 'https://api.elmah.io/v3/deployments?api_key=API_KEY' $body = @{ version = $version } [Net.ServicePointManager]::SecurityProtocol = ` [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls Invoke-RestMethod -Method Post -Uri $url -Body $body (replace API_KEY with your API key found on your organization settings page) In the example, a simple version string is sent to the API and elmah.io will automatically put a timestamp on that. Overriding user information and description make the experience within the elmah.io UI better. Pulling release notes and the name and email of the deployer, is usually available through environment variables or similar, depending on the technology used for creating the deployment. Here's an example of a full payload for the create deployment endpoint: $body = @{ version = \"1.0.0\" created = [datetime]::UtcNow.ToString(\"o\") description = \"my deployment\" userName = \"Thomas\" userEmail = \"thomas@elmah.io\" logId = \"39e60b0b-21b4-4d12-8f09-81f3642c64be\" } In this example, the deployment belongs to a single log why the logId property is set. The description property can be used to include a changelog or similar. Markdown is supported.","title":"Create deployments from PowerShell"},{"location":"create-deployments-from-umbraco-cloud/","text":"Create deployments from Umbraco Cloud Umbraco Cloud uses Azure to host Umbraco websites, so supporting deployment tracking pretty much corresponds to the steps specified in Using Kudu . Navigate to https://your-umbraco-site.scm.s1.umbraco.io where your-umbraco-site is the name of your Umbraco site. Click the Debug console link and navigate to site\\deployments\\tools\\PostDeploymentActions\\deploymenthooks (create it if it doesn't exist). Notice the folder deploymenthooks , which is required for your scripts to run on Umbraco Cloud. Unlike Kudu, Umbraco Cloud only executes cmd and bat files. Create a new cmd file: touch create-deployment.cmd with the following content: echo \"Creating elmah.io deployment\" cd %POST_DEPLOYMENT_ACTIONS_DIR% cd deploymenthooks powershell -command \". .\\create-deployment.ps1\" The script executes a PowerShell script, which we will create next: touch create-deployment.ps1 The content of the PowerShell script looks a lot like in Using Kudu , but with some minor tweaks to support Umbraco Cloud: $version = Get-Date -format u $ProgressPreference = \"SilentlyContinue\" $commitId = [System.Environment]::GetEnvironmentVariable(\"SCM_COMMIT_ID\"); $deployUrl = \"https://your-umbraco-site.scm.s1.umbraco.io/api/deployments/$commitId\" $username = \"MY_USERNAME\" $password = \"MY_PASSWORD\" $logId = \"LOG_ID\" $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((\"{0}:{1}\" -f $username,$password))) $deployInfo = Invoke-RestMethod -Method Get -Uri $deployUrl -Headers @{Authorization=(\"Basic {0}\" -f $base64AuthInfo)} $url = 'https://api.elmah.io/v3/deployments?api_key=API_KEY' $body = @{ version = $version description = $deployInfo.message userName = $deployInfo.author userEmail = $deployInfo.author_email logId = $logId } [Net.ServicePointManager]::SecurityProtocol = ` [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls Invoke-RestMethod -Method Post -Uri $url -Body $body Replace your-umbraco-site with the name of your site, MY_USERNAME with your Umbraco Cloud username, MY_PASSWORD with your Umbraco Cloud password, LOG_ID with the id if the elmah.io log that should contain the deployments ( Where is my log ID? ), and finally API_KEY with your elmah.io API key, found and your organization settings page. There you go. When deploying changes to your Umbraco Cloud site, a new deployment is automatically created on elmah.io.","title":"Create deployments from Umbraco Cloud"},{"location":"create-deployments-from-umbraco-cloud/#create-deployments-from-umbraco-cloud","text":"Umbraco Cloud uses Azure to host Umbraco websites, so supporting deployment tracking pretty much corresponds to the steps specified in Using Kudu . Navigate to https://your-umbraco-site.scm.s1.umbraco.io where your-umbraco-site is the name of your Umbraco site. Click the Debug console link and navigate to site\\deployments\\tools\\PostDeploymentActions\\deploymenthooks (create it if it doesn't exist). Notice the folder deploymenthooks , which is required for your scripts to run on Umbraco Cloud. Unlike Kudu, Umbraco Cloud only executes cmd and bat files. Create a new cmd file: touch create-deployment.cmd with the following content: echo \"Creating elmah.io deployment\" cd %POST_DEPLOYMENT_ACTIONS_DIR% cd deploymenthooks powershell -command \". .\\create-deployment.ps1\" The script executes a PowerShell script, which we will create next: touch create-deployment.ps1 The content of the PowerShell script looks a lot like in Using Kudu , but with some minor tweaks to support Umbraco Cloud: $version = Get-Date -format u $ProgressPreference = \"SilentlyContinue\" $commitId = [System.Environment]::GetEnvironmentVariable(\"SCM_COMMIT_ID\"); $deployUrl = \"https://your-umbraco-site.scm.s1.umbraco.io/api/deployments/$commitId\" $username = \"MY_USERNAME\" $password = \"MY_PASSWORD\" $logId = \"LOG_ID\" $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes((\"{0}:{1}\" -f $username,$password))) $deployInfo = Invoke-RestMethod -Method Get -Uri $deployUrl -Headers @{Authorization=(\"Basic {0}\" -f $base64AuthInfo)} $url = 'https://api.elmah.io/v3/deployments?api_key=API_KEY' $body = @{ version = $version description = $deployInfo.message userName = $deployInfo.author userEmail = $deployInfo.author_email logId = $logId } [Net.ServicePointManager]::SecurityProtocol = ` [Net.SecurityProtocolType]::Tls12, [Net.SecurityProtocolType]::Tls11, [Net.SecurityProtocolType]::Tls Invoke-RestMethod -Method Post -Uri $url -Body $body Replace your-umbraco-site with the name of your site, MY_USERNAME with your Umbraco Cloud username, MY_PASSWORD with your Umbraco Cloud password, LOG_ID with the id if the elmah.io log that should contain the deployments ( Where is my log ID? ), and finally API_KEY with your elmah.io API key, found and your organization settings page. There you go. When deploying changes to your Umbraco Cloud site, a new deployment is automatically created on elmah.io.","title":"Create deployments from Umbraco Cloud"},{"location":"creating-rules-to-perform-actions-on-messages/","text":"Creating Rules to Perform Actions on Messages elmah.io comes with a great rule engine for performing various actions when messages are logged in your log. This guide is also available as a short video tutorial here: The rule engine is located beneath each log on the log settings page: A rule consists of three parts: a title, a query, and an action. The title should be a short text explaining what this rule does. We don't use the title for anything, so please write something that helps you identify rules and to keep them apart. The query should contain either a full-text search string or a Lucene query. When new messages are logged, the message is matched up against all queries registered on that log. If and only if a message matches a query, the action registered on the rule is performed. As mentioned above, the action part of a rule is executed when a message matches the query specified in the same rule. An action can be one of four types: Ignore, Hide, Mail, and HTTP Request. To illustrate how to use each action type, here are four examples of useful rules. Ignore errors with an HTTP status code of 400 Be aware that Ignore rules are only meant as a temporary way of ignoring messages. In case you want to permanently ignore one or more log messages, use client-side filtering as explained in the documentation for each client integration. In addition, there's a client-side filtering help dialog available on the log message details toolbar. Ignoring a large number of messages with Ignore rules will slow down your application logging, use unnecessary network bandwidth, and risk hitting the elmah.io API request limit. To ignore all messages with an HTTP status code of 400, you would need to set up the following: Title Query Then Ignore 400s statusCode:400 Ignore The rule would look like this in the UI: Hide warnings To hide all messages with a severity of Warning , you would need to set up the following: Title Query Then Hide Warnings severity:Warning Hide The rule would look like this in the UI: Send an email on all messages containing a word To send an email on all messages containing the word billing somewhere, you would need to set up the following: Title Query Then Mail on billing billing Email The rule would look like this in the UI: Make an HTTP request on all new and fatal messages To make an HTTP request on every new message with a severity of Fatal , you would need to set up the following: Title Query Then Request on new fatal isNew:true AND severity:Fatal HTTP The rule would look like this in the UI:","title":"Creating Rules to Perform Actions on Messages"},{"location":"creating-rules-to-perform-actions-on-messages/#creating-rules-to-perform-actions-on-messages","text":"elmah.io comes with a great rule engine for performing various actions when messages are logged in your log. This guide is also available as a short video tutorial here: The rule engine is located beneath each log on the log settings page: A rule consists of three parts: a title, a query, and an action. The title should be a short text explaining what this rule does. We don't use the title for anything, so please write something that helps you identify rules and to keep them apart. The query should contain either a full-text search string or a Lucene query. When new messages are logged, the message is matched up against all queries registered on that log. If and only if a message matches a query, the action registered on the rule is performed. As mentioned above, the action part of a rule is executed when a message matches the query specified in the same rule. An action can be one of four types: Ignore, Hide, Mail, and HTTP Request. To illustrate how to use each action type, here are four examples of useful rules.","title":"Creating Rules to Perform Actions on Messages"},{"location":"creating-rules-to-perform-actions-on-messages/#ignore-errors-with-an-http-status-code-of-400","text":"Be aware that Ignore rules are only meant as a temporary way of ignoring messages. In case you want to permanently ignore one or more log messages, use client-side filtering as explained in the documentation for each client integration. In addition, there's a client-side filtering help dialog available on the log message details toolbar. Ignoring a large number of messages with Ignore rules will slow down your application logging, use unnecessary network bandwidth, and risk hitting the elmah.io API request limit. To ignore all messages with an HTTP status code of 400, you would need to set up the following: Title Query Then Ignore 400s statusCode:400 Ignore The rule would look like this in the UI:","title":"Ignore errors with an HTTP status code of 400"},{"location":"creating-rules-to-perform-actions-on-messages/#hide-warnings","text":"To hide all messages with a severity of Warning , you would need to set up the following: Title Query Then Hide Warnings severity:Warning Hide The rule would look like this in the UI:","title":"Hide warnings"},{"location":"creating-rules-to-perform-actions-on-messages/#send-an-email-on-all-messages-containing-a-word","text":"To send an email on all messages containing the word billing somewhere, you would need to set up the following: Title Query Then Mail on billing billing Email The rule would look like this in the UI:","title":"Send an email on all messages containing a word"},{"location":"creating-rules-to-perform-actions-on-messages/#make-an-http-request-on-all-new-and-fatal-messages","text":"To make an HTTP request on every new message with a severity of Fatal , you would need to set up the following: Title Query Then Request on new fatal isNew:true AND severity:Fatal HTTP The rule would look like this in the UI:","title":"Make an HTTP request on all new and fatal messages"},{"location":"elmah-and-custom-errors/","text":"ELMAH and custom errors ELMAH and ASP.NET (MVC) custom errors aren't exactly known to be best friends. Question after question has been posted on forums like Stack Overflow, from people having problems with ELMAH, when custom errors are configured. These problems make perfect sense since both ELMAH and custom errors are designed to catch errors and do something about them. Before looking at some code, we recommend you to read Web.config customErrors element with ASP.NET explained and Demystifying ASP.NET MVC 5 Error Pages and Error Logging . Together, the posts are a great introduction to different ways of implementing custom error pages in ASP.NET MVC. Back to ELMAH. In most implementations of custom error pages, ASP.NET swallows any uncaught exceptions, putting ELMAH out of play. To overcome this issue, you can utilize MVC's IExceptionFilter to log all exceptions, whether or not it is handled by a custom error page: public class ElmahExceptionLogger : IExceptionFilter { public void OnException (ExceptionContext context) { if (context.ExceptionHandled) { ErrorSignal.FromCurrentContext().Raise(context.Exception); } } } The OnException method on ElmahExceptionLogger is executed every time an error is happening, by registering it in Application_Start : protected void Application_Start() { // ... GlobalConfiguration.Configuration.Filters.Add(new ElmahExceptionLogger()); // ... }","title":"ELMAH and custom errors"},{"location":"elmah-and-custom-errors/#elmah-and-custom-errors","text":"ELMAH and ASP.NET (MVC) custom errors aren't exactly known to be best friends. Question after question has been posted on forums like Stack Overflow, from people having problems with ELMAH, when custom errors are configured. These problems make perfect sense since both ELMAH and custom errors are designed to catch errors and do something about them. Before looking at some code, we recommend you to read Web.config customErrors element with ASP.NET explained and Demystifying ASP.NET MVC 5 Error Pages and Error Logging . Together, the posts are a great introduction to different ways of implementing custom error pages in ASP.NET MVC. Back to ELMAH. In most implementations of custom error pages, ASP.NET swallows any uncaught exceptions, putting ELMAH out of play. To overcome this issue, you can utilize MVC's IExceptionFilter to log all exceptions, whether or not it is handled by a custom error page: public class ElmahExceptionLogger : IExceptionFilter { public void OnException (ExceptionContext context) { if (context.ExceptionHandled) { ErrorSignal.FromCurrentContext().Raise(context.Exception); } } } The OnException method on ElmahExceptionLogger is executed every time an error is happening, by registering it in Application_Start : protected void Application_Start() { // ... GlobalConfiguration.Configuration.Filters.Add(new ElmahExceptionLogger()); // ... }","title":"ELMAH and custom errors"},{"location":"elmah-and-elmah-io-differences/","text":"ELMAH and elmah.io differences We receive a lot of questions like these: What is the difference between ELMAH and elmah.io? I thought ELMAH was free. Why do you suddenly charge? My ELMAH SQL Server configuration doesn't work. Why not? We understand the confusion. The purpose of this article is to give a bit of background of the differences between ELMAH and elmah.io and why they share similar names. What is ELMAH? ELMAH is an error logging framework originally developed by Atif Aziz able to log all unhandled exceptions from .NET web applications. Errors can be logged to a variety of destinations through ELMAH\u2019s plugin model called error logs. Plugins for XML, SQL Server, MySQL, Elasticsearch, and many more exists. ELMAH automatically collects a lot of information from the HTTP context when logging the error, giving you the possibility to inspect request parameters, cookies, and much more for the failed request. Custom errors can be logged to ELMAH, by manually calling the error log. What is elmah.io? elmah.io is a cloud-based error management system originally developed on top of ELMAH (see history for details). Besides supporting ELMAH, elmah.io also integrates with popular logging frameworks like log4net , NLog , Serilog , and web frameworks like ASP.NET Core . elmah.io offers a superior notification model to ELMAH, with integrations to mail, Slack, Microsoft Teams, and many others. elmah.io also built a lot of features outside the scope of ELMAH, like a complete uptime monitoring system. Comparison Feature ELMAH elmah.io Error Logging \u2705 \u2705 Self-hosted \u2705 \u274c Cloud-hosted \u274c \u2705 Search \u274c \u2705 New error detection \u274c \u2705 Error grouping \u274c \u2705 Issue tracking \u274c \u2705 log4net / NLog / Serilog \u274c \u2705 Clientside error logging \u274c \u2705 Slack/Teams/HipChat/etc. \u274c \u2705 Deployment tracking \u274c \u2705 Uptime monitoring \u274c \u2705 Heartbeats \u274c \u2705 Machine learning \u274c \u2705 Discount on popular software \u274c \u2705 History So, why name a service elmah.io, when only a minor part of a client integration uses ELMAH? When elmah.io was introduced back in 2013, the intention was to create a cloud-based error logger for ELMAH. We had some simple search and graphing possibilities, but the platform was meant as an alternative to host your own errors logs in SQL Server or similar. In time, elmah.io grew from being a hobby project to an actual company. During those years, we realized that the potential of the platform exceeded the possibilities with ELMAH in many ways. New features not available in ELMAH have been added constantly. A process that would have been nearly impossible with ELMAH's many storage integrations. Today, elmah.io is a full error management system for everything from console applications to web apps and serverless code hosted on Azure or AWS. We've built an entire uptime monitoring system, able to monitor not only if your website fails but also if it even responds to requests. Why not change the name to something else, you may be thinking? That is our wish as well. But changing your SaaS (software-as-a-service) company name isn't exactly easy. We have tried a couple of times, the first time back in 2016. We tried to name the different major features of elmah.io to sea creatures (like Stingray). We failed with the rename and people got confused. In 2017, we started looking at renaming the product again. This time to Unbug. We had learned from our previous mistake and this time silently started changing the name. We quickly realized that the domain change would cause a major risk in regards to SEO (search engine optimization) and confusion. For now, we are elmah.io. The name is not ideal, but it's a lesson learned for another time :)","title":"ELMAH and elmah.io differences"},{"location":"elmah-and-elmah-io-differences/#elmah-and-elmahio-differences","text":"We receive a lot of questions like these: What is the difference between ELMAH and elmah.io? I thought ELMAH was free. Why do you suddenly charge? My ELMAH SQL Server configuration doesn't work. Why not? We understand the confusion. The purpose of this article is to give a bit of background of the differences between ELMAH and elmah.io and why they share similar names.","title":"ELMAH and elmah.io differences"},{"location":"elmah-and-elmah-io-differences/#what-is-elmah","text":"ELMAH is an error logging framework originally developed by Atif Aziz able to log all unhandled exceptions from .NET web applications. Errors can be logged to a variety of destinations through ELMAH\u2019s plugin model called error logs. Plugins for XML, SQL Server, MySQL, Elasticsearch, and many more exists. ELMAH automatically collects a lot of information from the HTTP context when logging the error, giving you the possibility to inspect request parameters, cookies, and much more for the failed request. Custom errors can be logged to ELMAH, by manually calling the error log.","title":"What is ELMAH?"},{"location":"elmah-and-elmah-io-differences/#what-is-elmahio","text":"elmah.io is a cloud-based error management system originally developed on top of ELMAH (see history for details). Besides supporting ELMAH, elmah.io also integrates with popular logging frameworks like log4net , NLog , Serilog , and web frameworks like ASP.NET Core . elmah.io offers a superior notification model to ELMAH, with integrations to mail, Slack, Microsoft Teams, and many others. elmah.io also built a lot of features outside the scope of ELMAH, like a complete uptime monitoring system.","title":"What is elmah.io?"},{"location":"elmah-and-elmah-io-differences/#comparison","text":"Feature ELMAH elmah.io Error Logging \u2705 \u2705 Self-hosted \u2705 \u274c Cloud-hosted \u274c \u2705 Search \u274c \u2705 New error detection \u274c \u2705 Error grouping \u274c \u2705 Issue tracking \u274c \u2705 log4net / NLog / Serilog \u274c \u2705 Clientside error logging \u274c \u2705 Slack/Teams/HipChat/etc. \u274c \u2705 Deployment tracking \u274c \u2705 Uptime monitoring \u274c \u2705 Heartbeats \u274c \u2705 Machine learning \u274c \u2705 Discount on popular software \u274c \u2705","title":"Comparison"},{"location":"elmah-and-elmah-io-differences/#history","text":"So, why name a service elmah.io, when only a minor part of a client integration uses ELMAH? When elmah.io was introduced back in 2013, the intention was to create a cloud-based error logger for ELMAH. We had some simple search and graphing possibilities, but the platform was meant as an alternative to host your own errors logs in SQL Server or similar. In time, elmah.io grew from being a hobby project to an actual company. During those years, we realized that the potential of the platform exceeded the possibilities with ELMAH in many ways. New features not available in ELMAH have been added constantly. A process that would have been nearly impossible with ELMAH's many storage integrations. Today, elmah.io is a full error management system for everything from console applications to web apps and serverless code hosted on Azure or AWS. We've built an entire uptime monitoring system, able to monitor not only if your website fails but also if it even responds to requests. Why not change the name to something else, you may be thinking? That is our wish as well. But changing your SaaS (software-as-a-service) company name isn't exactly easy. We have tried a couple of times, the first time back in 2016. We tried to name the different major features of elmah.io to sea creatures (like Stingray). We failed with the rename and people got confused. In 2017, we started looking at renaming the product again. This time to Unbug. We had learned from our previous mistake and this time silently started changing the name. We quickly realized that the domain change would cause a major risk in regards to SEO (search engine optimization) and confusion. For now, we are elmah.io. The name is not ideal, but it's a lesson learned for another time :)","title":"History"},{"location":"elmah-io-apps-azure-boards/","text":"Install Azure Boards App for elmah.io Get your personal access token To create bugs on Azure Boards, you will need to generate a personal access token. Go to Azure DevOps and click the User settings icon in the top right corner. Select the Personal access tokens menu item in the dropdown. Finally, click the New Token button and fill in the details as shown below: For this example, we have picked 90 days expiration period, but you can decide on a shorter or longer period if you'd like. Remember to enable the Read & write scope under Work Items . Next, click the Create button and copy the generated token. Bugs created by elmah.io will have the CreatedBy set to the user generating the personal access token. If you want to identify bugs created by elmah.io, you should create the token from a new user (like elmahio@yourdomain.com). Install the Azure Boards App on elmah.io Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Azure Boards app and click the Install button: Paste the token copied in the previous step into the Token textbox. In the Organization textbox, input the name of your organization. For https://dev.azure.com/myorg/myproject, the organization name would be myorg . In the Project textbox, input the name of the project containing your board. For https://dev.azure.com/myorg/myproject, the project name would be myproject . If you want to embed all bugs created by the app beneath an overall work item, epic, or similar, fill in the optional ID in the Parent field. Click Save and the app is added to your log. When new errors are logged, bugs are automatically created in the configured Azure Board.","title":"Azure Boards"},{"location":"elmah-io-apps-azure-boards/#install-azure-boards-app-for-elmahio","text":"","title":"Install Azure Boards App for elmah.io"},{"location":"elmah-io-apps-azure-boards/#get-your-personal-access-token","text":"To create bugs on Azure Boards, you will need to generate a personal access token. Go to Azure DevOps and click the User settings icon in the top right corner. Select the Personal access tokens menu item in the dropdown. Finally, click the New Token button and fill in the details as shown below: For this example, we have picked 90 days expiration period, but you can decide on a shorter or longer period if you'd like. Remember to enable the Read & write scope under Work Items . Next, click the Create button and copy the generated token. Bugs created by elmah.io will have the CreatedBy set to the user generating the personal access token. If you want to identify bugs created by elmah.io, you should create the token from a new user (like elmahio@yourdomain.com).","title":"Get your personal access token"},{"location":"elmah-io-apps-azure-boards/#install-the-azure-boards-app-on-elmahio","text":"Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Azure Boards app and click the Install button: Paste the token copied in the previous step into the Token textbox. In the Organization textbox, input the name of your organization. For https://dev.azure.com/myorg/myproject, the organization name would be myorg . In the Project textbox, input the name of the project containing your board. For https://dev.azure.com/myorg/myproject, the project name would be myproject . If you want to embed all bugs created by the app beneath an overall work item, epic, or similar, fill in the optional ID in the Parent field. Click Save and the app is added to your log. When new errors are logged, bugs are automatically created in the configured Azure Board.","title":"Install the Azure Boards App on elmah.io"},{"location":"elmah-io-apps-bitbucket/","text":"Install Bitbucket App for elmah.io Get your App password To allow elmah.io to create issues on Bitbucket, you will need an App password. App passwords can be generated by clicking your user in the top right corner and selecting Personal settings . In the left menu, click the App passwords page ( https://bitbucket.org/account/settings/app-passwords/ ). To create a new password, click the Create app password button and input the following information: elmah.io only need the Issues - Write permission to create issues. To test the inputted values on elmah.io (later step) also check the Repositories - Read permission. After clicking the Create button, copy the generated app password. Install the Bitbucket App on elmah.io Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Bitbucket app and click the Install button: Paste the App password copied in the previous step into the APP PASSWORD textbox. In the TEAM textbox, input the name of the team/workspace owning the repository you want to create issues in. In the REPOSITORY textbox input the name of the repository. In the USERNAME textbox, input the name of the user generating the App password. In older installations, this can also contain the team/workspace name. Click Save and the app is added to your log. When new errors are logged, issues are automatically created in the configured Bitbucket repository.","title":"Bitbucket"},{"location":"elmah-io-apps-bitbucket/#install-bitbucket-app-for-elmahio","text":"","title":"Install Bitbucket App for elmah.io"},{"location":"elmah-io-apps-bitbucket/#get-your-app-password","text":"To allow elmah.io to create issues on Bitbucket, you will need an App password. App passwords can be generated by clicking your user in the top right corner and selecting Personal settings . In the left menu, click the App passwords page ( https://bitbucket.org/account/settings/app-passwords/ ). To create a new password, click the Create app password button and input the following information: elmah.io only need the Issues - Write permission to create issues. To test the inputted values on elmah.io (later step) also check the Repositories - Read permission. After clicking the Create button, copy the generated app password.","title":"Get your App password"},{"location":"elmah-io-apps-bitbucket/#install-the-bitbucket-app-on-elmahio","text":"Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Bitbucket app and click the Install button: Paste the App password copied in the previous step into the APP PASSWORD textbox. In the TEAM textbox, input the name of the team/workspace owning the repository you want to create issues in. In the REPOSITORY textbox input the name of the repository. In the USERNAME textbox, input the name of the user generating the App password. In older installations, this can also contain the team/workspace name. Click Save and the app is added to your log. When new errors are logged, issues are automatically created in the configured Bitbucket repository.","title":"Install the Bitbucket App on elmah.io"},{"location":"elmah-io-apps-botbuster/","text":"Install BotBuster App for elmah.io The BotBuster app is deprecated. Enable the Filter Crawlers toggle on the Filters tab to ignore errors generated by crawlers. The BotBuster app for elmah.io identifies and ignores messages generated by white hat bots like spiders, search engine bots, and similar. Under normal circumstances, you want to allow access for white hat bots, but you don't want to get a notification every time one of them tries to request a resource not found on the server. Installing BotBuster couldn't be simpler. Log into elmah.io and go to the log settings. Click the Apps tab. Locate the BotBuster app and click the Install button.","title":"BotBuster (deprecated)"},{"location":"elmah-io-apps-botbuster/#install-botbuster-app-for-elmahio","text":"The BotBuster app is deprecated. Enable the Filter Crawlers toggle on the Filters tab to ignore errors generated by crawlers. The BotBuster app for elmah.io identifies and ignores messages generated by white hat bots like spiders, search engine bots, and similar. Under normal circumstances, you want to allow access for white hat bots, but you don't want to get a notification every time one of them tries to request a resource not found on the server. Installing BotBuster couldn't be simpler. Log into elmah.io and go to the log settings. Click the Apps tab. Locate the BotBuster app and click the Install button.","title":"Install BotBuster App for elmah.io"},{"location":"elmah-io-apps-chatgpt/","text":"Install ChatGPT for elmah.io Log into elmah.io and go to the log settings. Click the Apps tab. Locate the ChatGPT app and click the Install button: Input your OpenAI API key ( Where do I find my OpenAI API Key? ). Next, select which language model to use. We currently support GPT-3.5-Turbo and GPT-4. As a default, elmah.io will only share the stack trace of an error with ChatGPT when you click the Get suggestion button in the elmah.io UI. If you want to include the source code and/or any SQL attached to the error, you can enable one or both toggles. Sharing the source will require you to bundle your source code alongside errors as documented here: How to include source code in log messages . Click Save and the app is added to your log. When you open errors valid for ChatGPT help, you will see a tab named AI next to Detail , Inspector , etc.","title":"ChatGPT"},{"location":"elmah-io-apps-chatgpt/#install-chatgpt-for-elmahio","text":"Log into elmah.io and go to the log settings. Click the Apps tab. Locate the ChatGPT app and click the Install button: Input your OpenAI API key ( Where do I find my OpenAI API Key? ). Next, select which language model to use. We currently support GPT-3.5-Turbo and GPT-4. As a default, elmah.io will only share the stack trace of an error with ChatGPT when you click the Get suggestion button in the elmah.io UI. If you want to include the source code and/or any SQL attached to the error, you can enable one or both toggles. Sharing the source will require you to bundle your source code alongside errors as documented here: How to include source code in log messages . Click Save and the app is added to your log. When you open errors valid for ChatGPT help, you will see a tab named AI next to Detail , Inspector , etc.","title":"Install ChatGPT for elmah.io"},{"location":"elmah-io-apps-clickup/","text":"Install ClickUp for elmah.io Log into elmah.io and go to the log settings page. Click the Apps tab. Locate the ClickUp app and click the Install button: You will need to input a ClickUp API token and the ID of the list to create tasks. The API token can be generated by navigating to ClickUp, clicking the profile photo in the bottom left corner, and clicking Apps . It is important to click the Apps link beneath your profile and not the ClickApps link beneath the team. On the Apps page, you can generate and copy a new token beneath the API Token section. The list ID can be found by going to the list on the ClickUp app and clicking the list name: When copying the link you will get a link similar to this: https://app.clickup.com/.../v/li/901200300647 The list ID is the last part of the URL ( 901200300647 in the example above). When both the API token and list ID are inputted on elmah.io, click the Test button to test the values. When the Test button turns green, click the Save button, and the app is added to your log. When new errors are logged, tasks are automatically created in the configured ClickUp list. Troubleshooting If errors aren't showing up in ClickUp, please check that the following are all true: When clicking the Test button on the ClickUp app settings screen, the button turns green. There's a message logged in the log where you set up the ClickUp integration. The message is marked as new (yellow star next to the title on the search result). The message is either of severity Error or Fatal . To trigger an error manually, go to https://api.elmah.io/swagger/index.html and input an elmah.io API key with the Messages - Write permission enabled. Expand Messages and the POST node with the URL /v3/messages/{logId} . Input your log ID and the following JSON: { \"title\": \"This is a test\", \"severity\": \"Error\" } Finally, click the Try it out! button and verify that the API returns a status code of 201 . The new error should show up in ClickUp. If testing this multiple times, you will need to make small adjustments to the title field inside the JSON, for additional errors to be marked as new.","title":"ClickUp"},{"location":"elmah-io-apps-clickup/#install-clickup-for-elmahio","text":"Log into elmah.io and go to the log settings page. Click the Apps tab. Locate the ClickUp app and click the Install button: You will need to input a ClickUp API token and the ID of the list to create tasks. The API token can be generated by navigating to ClickUp, clicking the profile photo in the bottom left corner, and clicking Apps . It is important to click the Apps link beneath your profile and not the ClickApps link beneath the team. On the Apps page, you can generate and copy a new token beneath the API Token section. The list ID can be found by going to the list on the ClickUp app and clicking the list name: When copying the link you will get a link similar to this: https://app.clickup.com/.../v/li/901200300647 The list ID is the last part of the URL ( 901200300647 in the example above). When both the API token and list ID are inputted on elmah.io, click the Test button to test the values. When the Test button turns green, click the Save button, and the app is added to your log. When new errors are logged, tasks are automatically created in the configured ClickUp list.","title":"Install ClickUp for elmah.io"},{"location":"elmah-io-apps-clickup/#troubleshooting","text":"If errors aren't showing up in ClickUp, please check that the following are all true: When clicking the Test button on the ClickUp app settings screen, the button turns green. There's a message logged in the log where you set up the ClickUp integration. The message is marked as new (yellow star next to the title on the search result). The message is either of severity Error or Fatal . To trigger an error manually, go to https://api.elmah.io/swagger/index.html and input an elmah.io API key with the Messages - Write permission enabled. Expand Messages and the POST node with the URL /v3/messages/{logId} . Input your log ID and the following JSON: { \"title\": \"This is a test\", \"severity\": \"Error\" } Finally, click the Try it out! button and verify that the API returns a status code of 201 . The new error should show up in ClickUp. If testing this multiple times, you will need to make small adjustments to the title field inside the JSON, for additional errors to be marked as new.","title":"Troubleshooting"},{"location":"elmah-io-apps-github/","text":"Install GitHub App for elmah.io Generate Personal Access Token To allow elmah.io to create issues on GitHub, you need a Personal Access Token. Sign in to GitHub, click your profile photo in the top right corner, and click Settings . On the Settings page click Developer settings followed by Personal access token . Here you can create a new token by clicking the Generate new token (classic) button: Input a token note and select an expiration date. If the repository you want issues created in is public, make sure to check the public_repo checkbox. If the repository is private, check the repo checkbox. Finally, click the Generate token button, and copy the generated token (colored with a green background) GitHub also supports fine-grained personal access tokens. This token can also be used on elmah.io. Make sure to select Read and write in the Issues permission. Install the GitHub App on elmah.io Log into elmah.io and go to the log settings. Click the Apps tab. Locate the GitHub app and click the Install button: Paste the token copied in the previous step into the Token textbox. In the Owner textbox, input the name of the user or organization owning the repository you want to create issues in. In the Repository textbox input the name of the repository. Click Save and the app is added to your log. When new errors are logged, issues are automatically created in the configured GitHub repository.","title":"GitHub"},{"location":"elmah-io-apps-github/#install-github-app-for-elmahio","text":"","title":"Install GitHub App for elmah.io"},{"location":"elmah-io-apps-github/#generate-personal-access-token","text":"To allow elmah.io to create issues on GitHub, you need a Personal Access Token. Sign in to GitHub, click your profile photo in the top right corner, and click Settings . On the Settings page click Developer settings followed by Personal access token . Here you can create a new token by clicking the Generate new token (classic) button: Input a token note and select an expiration date. If the repository you want issues created in is public, make sure to check the public_repo checkbox. If the repository is private, check the repo checkbox. Finally, click the Generate token button, and copy the generated token (colored with a green background) GitHub also supports fine-grained personal access tokens. This token can also be used on elmah.io. Make sure to select Read and write in the Issues permission.","title":"Generate Personal Access Token"},{"location":"elmah-io-apps-github/#install-the-github-app-on-elmahio","text":"Log into elmah.io and go to the log settings. Click the Apps tab. Locate the GitHub app and click the Install button: Paste the token copied in the previous step into the Token textbox. In the Owner textbox, input the name of the user or organization owning the repository you want to create issues in. In the Repository textbox input the name of the repository. Click Save and the app is added to your log. When new errors are logged, issues are automatically created in the configured GitHub repository.","title":"Install the GitHub App on elmah.io"},{"location":"elmah-io-apps-gitlab/","text":"Install GitLab App for elmah.io Generate Personal Access Token To allow elmah.io to create issues on GitLab, you will need to generate a Personal Access Token. To do so, log into GitLab, click your profile photo in the top right corner, and select Preferences . On the Preferences page click the Access Tokens menu item: Input a token name, expiration date, and check the api checkbox. Click the Create personal access token button and copy the generated token. Install the GitLab App on elmah.io Log into elmah.io and go to the log settings. Click the Apps tab. Locate the GitLab app and click the Install button: Paste the token copied in the previous step into the Token textbox. In the Project textbox, input the ID or name of the project you want issues created on. If you are self-hosting GitLab, input your custom URL in the URL textbox (for example https://gitlab.hooli.com). Click the Test button and observe it turn green. When clicking Save , the app is added to your log. When new errors are logged, issues are automatically created in the configured GitLab project.","title":"GitLab"},{"location":"elmah-io-apps-gitlab/#install-gitlab-app-for-elmahio","text":"","title":"Install GitLab App for elmah.io"},{"location":"elmah-io-apps-gitlab/#generate-personal-access-token","text":"To allow elmah.io to create issues on GitLab, you will need to generate a Personal Access Token. To do so, log into GitLab, click your profile photo in the top right corner, and select Preferences . On the Preferences page click the Access Tokens menu item: Input a token name, expiration date, and check the api checkbox. Click the Create personal access token button and copy the generated token.","title":"Generate Personal Access Token"},{"location":"elmah-io-apps-gitlab/#install-the-gitlab-app-on-elmahio","text":"Log into elmah.io and go to the log settings. Click the Apps tab. Locate the GitLab app and click the Install button: Paste the token copied in the previous step into the Token textbox. In the Project textbox, input the ID or name of the project you want issues created on. If you are self-hosting GitLab, input your custom URL in the URL textbox (for example https://gitlab.hooli.com). Click the Test button and observe it turn green. When clicking Save , the app is added to your log. When new errors are logged, issues are automatically created in the configured GitLab project.","title":"Install the GitLab App on elmah.io"},{"location":"elmah-io-apps-hipchat/","text":"Install HipChat App for elmah.io Generate OAuth 2 Token To allow elmah.io to log messages to HipChat, you will need to generate an OAuth 2 token. To do so, log into HipChat and go to the API Access page (replace elmahio with your subdomain). Input a label, click the Create button and copy the generated token. If you want to test your configuration using the Test button on the elmah.io UI, you will need to select both Send Notification and View Room in Scopes . Install the HipChat App on elmah.io Log into elmah.io and go to the log settings. Click the Apps tab. Locate the HipChat app and click the Install button: Paste the token copied in the previous step into the Token textbox. In the Room textbox, input the name of the HipChat chat room you want messages from elmah.io to show up in. Click Save and the app is added to your log. When new errors are logged, messages start appearing in the chat room that you configured. HipChat doesn't allow more than 500 requests per 5 minutes. If you generate more messages to elmah.io, not all of them will show up in HipChat because of this.","title":"HipChat"},{"location":"elmah-io-apps-hipchat/#install-hipchat-app-for-elmahio","text":"","title":"Install HipChat App for elmah.io"},{"location":"elmah-io-apps-hipchat/#generate-oauth-2-token","text":"To allow elmah.io to log messages to HipChat, you will need to generate an OAuth 2 token. To do so, log into HipChat and go to the API Access page (replace elmahio with your subdomain). Input a label, click the Create button and copy the generated token. If you want to test your configuration using the Test button on the elmah.io UI, you will need to select both Send Notification and View Room in Scopes .","title":"Generate OAuth 2 Token"},{"location":"elmah-io-apps-hipchat/#install-the-hipchat-app-on-elmahio","text":"Log into elmah.io and go to the log settings. Click the Apps tab. Locate the HipChat app and click the Install button: Paste the token copied in the previous step into the Token textbox. In the Room textbox, input the name of the HipChat chat room you want messages from elmah.io to show up in. Click Save and the app is added to your log. When new errors are logged, messages start appearing in the chat room that you configured. HipChat doesn't allow more than 500 requests per 5 minutes. If you generate more messages to elmah.io, not all of them will show up in HipChat because of this.","title":"Install the HipChat App on elmah.io"},{"location":"elmah-io-apps-ipfilter/","text":"Install IP Filter App for elmah.io The IP Filter app is deprecated. The IP Filter filter on the Filters tab offers more advanced IP filtering. The IP Filter app for elmah.io automatically ignores messages from one or more IP addresses. This is a great way to ignore errors generated by both crawlers and errors generated by you. To install IP Filter, click the Install button on the Apps tab. This will show the IP Filter settings page: To ignore messages from a single IP address, input the IP in both the From and To fields. To ignore messages from a range of IP addresses, input the start and end IP address in the From and To fields. Both IP addresses are included in the ignored range. The IP Filter app ignores every message matching the specified IP range. This means that if you are logging something like Information messages through Serilog or similar, these messages are also ignored. For a message to have an IP, you will need to specify a server variable named REMOTE_ADDR when creating the message. This variable is automatically added (if available) when using the integration for ELMAH.","title":"IP Filter (deprecated)"},{"location":"elmah-io-apps-ipfilter/#install-ip-filter-app-for-elmahio","text":"The IP Filter app is deprecated. The IP Filter filter on the Filters tab offers more advanced IP filtering. The IP Filter app for elmah.io automatically ignores messages from one or more IP addresses. This is a great way to ignore errors generated by both crawlers and errors generated by you. To install IP Filter, click the Install button on the Apps tab. This will show the IP Filter settings page: To ignore messages from a single IP address, input the IP in both the From and To fields. To ignore messages from a range of IP addresses, input the start and end IP address in the From and To fields. Both IP addresses are included in the ignored range. The IP Filter app ignores every message matching the specified IP range. This means that if you are logging something like Information messages through Serilog or similar, these messages are also ignored. For a message to have an IP, you will need to specify a server variable named REMOTE_ADDR when creating the message. This variable is automatically added (if available) when using the integration for ELMAH.","title":"Install IP Filter App for elmah.io"},{"location":"elmah-io-apps-jira/","text":"Install Jira App for elmah.io Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Jira app and click the Install button: Input your site name, which is the first part of the URL you use to log into Jira. For the URL https://elmahio.atlassian.net/ , the site parameter would be elmahio . In the Project field, input the key of the project. Note that a project has both a display name and a key. The property we are looking for here is the uppercase identifier of the project. To create issues on Jira, you will need to input the username and password of a user with permission to create issues in the project specified above. You can use your user credentials, but we recommend using a combination of your username and an API token. To generate a new token specific for elmah.io, go to the API Tokens page on your Jira account. Then click the Create API token button and input a label of your choice. Finally, click the Create button and an API token is generated for you. Make sure to copy this token, since you won't be able to access it once the dialog is closed. Go back to elmah.io and input your email in the Username field and the API token from the previous step in the Password field. If you don't like to use an existing user account for the integration, you can create a new Atlassian account for elmah.io and generate the API token from that account instead. Click Save and the app is added to your log. When new errors are logged, issues are automatically created in the configured Jira project. Troubleshooting If errors aren't showing up in Jira, please check that the following are all true: When clicking the Test button on the Jira app settings screen, the button turns green. There's a message logged in the log where you set up the Jira integration. The message is marked as new (yellow star next to the title on the search result). The message is either of severity Error or Fatal . To trigger an error manually, go to https://api.elmah.io/swagger/index.html and input an elmah.io API key with the Messages - Write permission enabled. Expand Messages and the POST node with the URL /v3/messages/{logId} . Input your log ID and the following JSON: { \"title\": \"This is a test\", \"severity\": \"Error\" } Finally, click the Try it out! button and verify that the API returns a status code of 201 . The new error should show up in Jira. If testing this multiple times, you will need to make small adjustments to the title field inside the JSON, for additional errors to be marked as new.","title":"Jira"},{"location":"elmah-io-apps-jira/#install-jira-app-for-elmahio","text":"Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Jira app and click the Install button: Input your site name, which is the first part of the URL you use to log into Jira. For the URL https://elmahio.atlassian.net/ , the site parameter would be elmahio . In the Project field, input the key of the project. Note that a project has both a display name and a key. The property we are looking for here is the uppercase identifier of the project. To create issues on Jira, you will need to input the username and password of a user with permission to create issues in the project specified above. You can use your user credentials, but we recommend using a combination of your username and an API token. To generate a new token specific for elmah.io, go to the API Tokens page on your Jira account. Then click the Create API token button and input a label of your choice. Finally, click the Create button and an API token is generated for you. Make sure to copy this token, since you won't be able to access it once the dialog is closed. Go back to elmah.io and input your email in the Username field and the API token from the previous step in the Password field. If you don't like to use an existing user account for the integration, you can create a new Atlassian account for elmah.io and generate the API token from that account instead. Click Save and the app is added to your log. When new errors are logged, issues are automatically created in the configured Jira project.","title":"Install Jira App for elmah.io"},{"location":"elmah-io-apps-jira/#troubleshooting","text":"If errors aren't showing up in Jira, please check that the following are all true: When clicking the Test button on the Jira app settings screen, the button turns green. There's a message logged in the log where you set up the Jira integration. The message is marked as new (yellow star next to the title on the search result). The message is either of severity Error or Fatal . To trigger an error manually, go to https://api.elmah.io/swagger/index.html and input an elmah.io API key with the Messages - Write permission enabled. Expand Messages and the POST node with the URL /v3/messages/{logId} . Input your log ID and the following JSON: { \"title\": \"This is a test\", \"severity\": \"Error\" } Finally, click the Try it out! button and verify that the API returns a status code of 201 . The new error should show up in Jira. If testing this multiple times, you will need to make small adjustments to the title field inside the JSON, for additional errors to be marked as new.","title":"Troubleshooting"},{"location":"elmah-io-apps-mailman/","text":"Install Mailman App for elmah.io The Mailman app is deprecated. Use an email rule available on the Rules tab for a more advanced email integration. The Mailman app for elmah.io sends out an email to an address of your choice, every time a new error is logged. To install Mailman, click the Install button on the Apps tab. This will show the Mailman settings page: Input a valid email address in the Email input box and click Save . The Mailman app will look at new errors only. Errors are defined by messages with a severity of Error or Fatal and with isNew == true . isNew is a field automatically added by elmah.io when indexing each message. isNew is calculated by looking for similarities between the new message and already logged messages.","title":"Mailman (deprecated)"},{"location":"elmah-io-apps-mailman/#install-mailman-app-for-elmahio","text":"The Mailman app is deprecated. Use an email rule available on the Rules tab for a more advanced email integration. The Mailman app for elmah.io sends out an email to an address of your choice, every time a new error is logged. To install Mailman, click the Install button on the Apps tab. This will show the Mailman settings page: Input a valid email address in the Email input box and click Save . The Mailman app will look at new errors only. Errors are defined by messages with a severity of Error or Fatal and with isNew == true . isNew is a field automatically added by elmah.io when indexing each message. isNew is calculated by looking for similarities between the new message and already logged messages.","title":"Install Mailman App for elmah.io"},{"location":"elmah-io-apps-pagerduty/","text":"Install PagerDuty for elmah.io Using the PagerDuty integration for elmah.io, you can set up advanced notification rules in PagerDuty when new errors are logged on elmah.io. Receive a phone call, text message, or one of the other options provided by PagerDuty, the second new errors are introduced on your websites or services. To integrate elmah.io with PagerDuty, you need to set up a new integration on PagerDuty and install the PagerDuty app on elmah.io. Setting up an integration on PagerDuty Sign in to PagerDuty. Navigate to the Services page. Select the service that you want to integrate to from elmah.io in the list of services. On the Integrations tab click the Add an integration button. On the Add Integrations page search for elmah.io and select it in the search result: Click the Add button. Expand the newly created integration: Copy the value in the Integration Key field. Install the PagerDuty app on elmah.io Next, the PagerDuty app needs to be installed on elmah.io. Sign in to elmah.io. Navigate to the Log Settings page of the log you want to integrate with PagerDuty. Go to the Apps tab. Locate the PagerDuty app and click the Install button. Input the Integration Key that you copied in a previous step in the INTEGRATION KEY field: Click the Save button. That's it. New errors stored in the selected log now trigger incidents in PagerDuty. To get help with this integration, make sure to reach out through the support widget on the elmah.io website.","title":"PagerDuty"},{"location":"elmah-io-apps-pagerduty/#install-pagerduty-for-elmahio","text":"Using the PagerDuty integration for elmah.io, you can set up advanced notification rules in PagerDuty when new errors are logged on elmah.io. Receive a phone call, text message, or one of the other options provided by PagerDuty, the second new errors are introduced on your websites or services. To integrate elmah.io with PagerDuty, you need to set up a new integration on PagerDuty and install the PagerDuty app on elmah.io.","title":"Install PagerDuty for elmah.io"},{"location":"elmah-io-apps-pagerduty/#setting-up-an-integration-on-pagerduty","text":"Sign in to PagerDuty. Navigate to the Services page. Select the service that you want to integrate to from elmah.io in the list of services. On the Integrations tab click the Add an integration button. On the Add Integrations page search for elmah.io and select it in the search result: Click the Add button. Expand the newly created integration: Copy the value in the Integration Key field.","title":"Setting up an integration on PagerDuty"},{"location":"elmah-io-apps-pagerduty/#install-the-pagerduty-app-on-elmahio","text":"Next, the PagerDuty app needs to be installed on elmah.io. Sign in to elmah.io. Navigate to the Log Settings page of the log you want to integrate with PagerDuty. Go to the Apps tab. Locate the PagerDuty app and click the Install button. Input the Integration Key that you copied in a previous step in the INTEGRATION KEY field: Click the Save button. That's it. New errors stored in the selected log now trigger incidents in PagerDuty. To get help with this integration, make sure to reach out through the support widget on the elmah.io website.","title":"Install the PagerDuty app on elmah.io"},{"location":"elmah-io-apps-request-a-new-integration/","text":"Request a new integration between elmah.io and another tool We are always on the lookout for creating new and useful integrations. We don't want to integrate with everything (that's what Zapier is for), but commonly used tools by .NET web developers are on our radar. To suggest a new integration feel free to reach out through the support widget in the lower right corner. Not all integration requests are implemented. Don't feel bad if we decide not implement your suggestion. When recommending a new integration we need the following information: Name of the tool. URL of the tool. Why do you need this integration? What do you expect from this integration? Does the tool provide an API? Anything else you think would help? Do you by any chance know anyone working on the tool?","title":"Request a new integration"},{"location":"elmah-io-apps-request-a-new-integration/#request-a-new-integration-between-elmahio-and-another-tool","text":"We are always on the lookout for creating new and useful integrations. We don't want to integrate with everything (that's what Zapier is for), but commonly used tools by .NET web developers are on our radar. To suggest a new integration feel free to reach out through the support widget in the lower right corner. Not all integration requests are implemented. Don't feel bad if we decide not implement your suggestion. When recommending a new integration we need the following information: Name of the tool. URL of the tool. Why do you need this integration? What do you expect from this integration? Does the tool provide an API? Anything else you think would help? Do you by any chance know anyone working on the tool?","title":"Request a new integration between elmah.io and another tool"},{"location":"elmah-io-apps-slack/","text":"Install Slack App for elmah.io Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Slack app and click the Install button. You will be redirected to Slack where you need to log into your workspace if not already. Once logged in, select the channel to send messages to: Click the Allow button and you will be redirected back to elmah.io. The integration to Slack is now installed. Slack doesn't allow more than a single request per second. If you generate more than one message to elmah.io per second, not all of them will show up in Slack because of this. Troubleshooting Errors don't show up in Slack. Here are a few things to try out. Make sure that the Slack app is installed on the log as described above. Only new errors are sent to Slack. A new error has a severity of Error or Fatal and is marked with a yellow star on the search tab. We only send new errors to help you stay out of Slack's API limits. If sending all errors, you could quickly end up in a scenario where the same error is sent multiple times and more important errors get ignored by Slack. Make sure that your token is still valid. The only way to resolve an issue where the token is no longer valid is to re-install the Slack app.","title":"Slack"},{"location":"elmah-io-apps-slack/#install-slack-app-for-elmahio","text":"Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Slack app and click the Install button. You will be redirected to Slack where you need to log into your workspace if not already. Once logged in, select the channel to send messages to: Click the Allow button and you will be redirected back to elmah.io. The integration to Slack is now installed. Slack doesn't allow more than a single request per second. If you generate more than one message to elmah.io per second, not all of them will show up in Slack because of this.","title":"Install Slack App for elmah.io"},{"location":"elmah-io-apps-slack/#troubleshooting","text":"Errors don't show up in Slack. Here are a few things to try out. Make sure that the Slack app is installed on the log as described above. Only new errors are sent to Slack. A new error has a severity of Error or Fatal and is marked with a yellow star on the search tab. We only send new errors to help you stay out of Slack's API limits. If sending all errors, you could quickly end up in a scenario where the same error is sent multiple times and more important errors get ignored by Slack. Make sure that your token is still valid. The only way to resolve an issue where the token is no longer valid is to re-install the Slack app.","title":"Troubleshooting"},{"location":"elmah-io-apps-teams/","text":"Install Microsoft Teams App for elmah.io To install the integration with Microsoft Teams, go to teams and click the Apps menu item. Search for \"elmah.io\" and click the app: Click the Add to a team button. In the dropdown, search for your team and/or channel: Click the Set up a connector button. A new webhook URL is generated. Click the Copy Text button followed by the Save button: The elmah.io integration is now configured on Microsoft Teams and you should see the following screen: The final step is to input the webhook URL that you just copied, into elmah.io. Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Microsoft Teams app and click the Install button. In the overlay, paste the URL from the previous step: Click Save and the app is added to your log. When new errors are logged, messages start appearing in the channel that you configured. The Office 365 API used behind the scenes for this app uses throttling rather than a maximum of allowed requests. This means that you may start experiencing messages not being sent, if you start logging a large amount of messages. We have experienced a lot of weird error codes when communicating with the API. An example of this is an exception while posting data to the API, but the data is successfully shown on Teams. The result of this error is, that elmah.io retries the failing request multiple times, which causes the same message to be shown multiple times on Teams. Troubleshooting Errors don't show up in Teams. Here are a few things to try out. Make sure that the Teams app is installed on the log as described above. Only new errors are sent to Teams. A new error has a severity of Error or Fatal and is marked with a yellow star on the search tab. We only send new errors to help you stay out of Teams' API limits. If sending all errors, you could quickly end up in a scenario where the same error is sent multiple times and more important errors get ignored by Teams. Re-install the app on elmah.io with the webhook URL provided by Teams. Remove the elmah.io configuration from Teams and re-install it. After re-installing the app, you will need to copy the new webhook URL provided by Teams and input it in the elmah.io Teams app as descrived above. Go to the Apps page on Teams and search for 'elmah.io'. Remove the app entirely, click F5 to refresh the page, and install the app again. You may be stuck on an older version of our app, which can be fixed by simply removing and installing the app again.","title":"Microsoft Teams"},{"location":"elmah-io-apps-teams/#install-microsoft-teams-app-for-elmahio","text":"To install the integration with Microsoft Teams, go to teams and click the Apps menu item. Search for \"elmah.io\" and click the app: Click the Add to a team button. In the dropdown, search for your team and/or channel: Click the Set up a connector button. A new webhook URL is generated. Click the Copy Text button followed by the Save button: The elmah.io integration is now configured on Microsoft Teams and you should see the following screen: The final step is to input the webhook URL that you just copied, into elmah.io. Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Microsoft Teams app and click the Install button. In the overlay, paste the URL from the previous step: Click Save and the app is added to your log. When new errors are logged, messages start appearing in the channel that you configured. The Office 365 API used behind the scenes for this app uses throttling rather than a maximum of allowed requests. This means that you may start experiencing messages not being sent, if you start logging a large amount of messages. We have experienced a lot of weird error codes when communicating with the API. An example of this is an exception while posting data to the API, but the data is successfully shown on Teams. The result of this error is, that elmah.io retries the failing request multiple times, which causes the same message to be shown multiple times on Teams.","title":"Install Microsoft Teams App for elmah.io"},{"location":"elmah-io-apps-teams/#troubleshooting","text":"Errors don't show up in Teams. Here are a few things to try out. Make sure that the Teams app is installed on the log as described above. Only new errors are sent to Teams. A new error has a severity of Error or Fatal and is marked with a yellow star on the search tab. We only send new errors to help you stay out of Teams' API limits. If sending all errors, you could quickly end up in a scenario where the same error is sent multiple times and more important errors get ignored by Teams. Re-install the app on elmah.io with the webhook URL provided by Teams. Remove the elmah.io configuration from Teams and re-install it. After re-installing the app, you will need to copy the new webhook URL provided by Teams and input it in the elmah.io Teams app as descrived above. Go to the Apps page on Teams and search for 'elmah.io'. Remove the app entirely, click F5 to refresh the page, and install the app again. You may be stuck on an older version of our app, which can be fixed by simply removing and installing the app again.","title":"Troubleshooting"},{"location":"elmah-io-apps-trello/","text":"Install Trello App for elmah.io For elmah.io to communicate with the Trello API, we will need an API key and token. The API key is available here: https://trello.com/app-key . If you don't have a personal token available on that site, create a new Power-Up as described on the page. When the Power-Up is created, you can create a new API key on that page. To get the token, visit the following URL in your browser: https://trello.com/1/authorize?expiration=never&scope=read,write,account&response_type=token&name=Server%20Token&key=API_KEY . Remember to replace API_KEY with your Trello API key located in the previous step. When clicking the Allow button, Trello will generate a new token for you and show it in the browser window. elmah.io will create cards on a board list of your choice. Unfortunately, Trello didn't provide a way to obtain list IDs. The easiest way is to open Developer Tools in your browser and click an existing card inside the list you want elmah.io to create new cards in. Locate the request for the card details in the Network tab and click the Preview tab. The list id is in the card details: Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Trello app and click the Install button: Input the API key, token, and list ID, all located in the previous steps. Click the Test button to test that everything works and finally, click Save . New errors now trigger elmah.io to create a card with the details of the error in Trello. Troubleshooting If errors aren't showing up in Trello, please check that the following are all true: When clicking the Test button on the Trello app settings screen, the button turns green. There's a message logged in the log where you set up the Trello integration. The message is marked as new (yellow star next to the title on the search result). The message is either of severity Error or Fatal . To trigger an error manually, go to https://api.elmah.io/swagger/index.html and input an elmah.io API key with the Messages - Write permission enabled. Expand Messages and the POST node with the URL /v3/messages/{logId} . Input your log ID and the following JSON: { \"title\": \"This is a test\", \"severity\": \"Error\" } Finally, click the Try it out! button and verify that the API returns a status code of 201 . The new error should show up in Trello. If testing this multiple times, you will need to make small adjustments to the title field inside the JSON, for additional errors to be marked as new.","title":"Trello"},{"location":"elmah-io-apps-trello/#install-trello-app-for-elmahio","text":"For elmah.io to communicate with the Trello API, we will need an API key and token. The API key is available here: https://trello.com/app-key . If you don't have a personal token available on that site, create a new Power-Up as described on the page. When the Power-Up is created, you can create a new API key on that page. To get the token, visit the following URL in your browser: https://trello.com/1/authorize?expiration=never&scope=read,write,account&response_type=token&name=Server%20Token&key=API_KEY . Remember to replace API_KEY with your Trello API key located in the previous step. When clicking the Allow button, Trello will generate a new token for you and show it in the browser window. elmah.io will create cards on a board list of your choice. Unfortunately, Trello didn't provide a way to obtain list IDs. The easiest way is to open Developer Tools in your browser and click an existing card inside the list you want elmah.io to create new cards in. Locate the request for the card details in the Network tab and click the Preview tab. The list id is in the card details: Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Trello app and click the Install button: Input the API key, token, and list ID, all located in the previous steps. Click the Test button to test that everything works and finally, click Save . New errors now trigger elmah.io to create a card with the details of the error in Trello.","title":"Install Trello App for elmah.io"},{"location":"elmah-io-apps-trello/#troubleshooting","text":"If errors aren't showing up in Trello, please check that the following are all true: When clicking the Test button on the Trello app settings screen, the button turns green. There's a message logged in the log where you set up the Trello integration. The message is marked as new (yellow star next to the title on the search result). The message is either of severity Error or Fatal . To trigger an error manually, go to https://api.elmah.io/swagger/index.html and input an elmah.io API key with the Messages - Write permission enabled. Expand Messages and the POST node with the URL /v3/messages/{logId} . Input your log ID and the following JSON: { \"title\": \"This is a test\", \"severity\": \"Error\" } Finally, click the Try it out! button and verify that the API returns a status code of 201 . The new error should show up in Trello. If testing this multiple times, you will need to make small adjustments to the title field inside the JSON, for additional errors to be marked as new.","title":"Troubleshooting"},{"location":"elmah-io-apps-twilio/","text":"Install Twilio for elmah.io To send SMS/Text messages with Twilio, you will need to sign up for Twilio first. Twilio provides a range of good tutorials when signing up, why we don't want to duplicate them here. When signed up, you will have access to a Twilio phone number to send messages from, an Account SID and a token needed to authenticate Twilio. These pieces of information will be used below when installing the Twilio app on elmah.io. Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Twilio app and click the Install button: Input your Twilio phone number (available on https://www.twilio.com/console/phone-numbers/incoming) in the From field. Input the phone number you want receiving error reports from elmah.io in the To field. Remember to fully qualify the number with a plus and the language code (US example: +12025550170 - UK example: +441632960775). Copy your Account SID and Auth Token from the Twilio Dashboard and input them in the fields on elmah.io. Click Save and the app is added to your log. When new errors are logged, an SMS/Text message is automatically sent to the configured phone number. Troubleshooting If errors aren't being sent to your phone, verify that the configured variables work. To do so, replace the four variables in the top of this PowerShell script and execute it: $sid = \"INSERT_SID\" $token = \"INSERT_TOKEN\" $from = \"INSERT_FROM\" $to = \"INSERT_TO\" $url = \"https://api.twilio.com/2010-04-01/Accounts/$sid/Messages.json\" $pair = \"$($sid):$($token)\" $encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair)) $basicAuthValue = \"Basic $encodedCreds\" $Headers = @{ Authorization = $basicAuthValue ContentType = \"application/x-www-form-urlencoded\" } $from = $from.Replace(\"+\", \"%2B\") $to = $to.Replace(\"+\", \"%2B\") $response = Invoke-WebRequest -Uri $url -Method POST -Headers $Headers -Body \"Body=Affirmative&From=$from&To=$to\" You should see a text message on your phone. The script will output any errors from Twilio if something isn't working.","title":"Twilio"},{"location":"elmah-io-apps-twilio/#install-twilio-for-elmahio","text":"To send SMS/Text messages with Twilio, you will need to sign up for Twilio first. Twilio provides a range of good tutorials when signing up, why we don't want to duplicate them here. When signed up, you will have access to a Twilio phone number to send messages from, an Account SID and a token needed to authenticate Twilio. These pieces of information will be used below when installing the Twilio app on elmah.io. Log into elmah.io and go to the log settings. Click the Apps tab. Locate the Twilio app and click the Install button: Input your Twilio phone number (available on https://www.twilio.com/console/phone-numbers/incoming) in the From field. Input the phone number you want receiving error reports from elmah.io in the To field. Remember to fully qualify the number with a plus and the language code (US example: +12025550170 - UK example: +441632960775). Copy your Account SID and Auth Token from the Twilio Dashboard and input them in the fields on elmah.io. Click Save and the app is added to your log. When new errors are logged, an SMS/Text message is automatically sent to the configured phone number.","title":"Install Twilio for elmah.io"},{"location":"elmah-io-apps-twilio/#troubleshooting","text":"If errors aren't being sent to your phone, verify that the configured variables work. To do so, replace the four variables in the top of this PowerShell script and execute it: $sid = \"INSERT_SID\" $token = \"INSERT_TOKEN\" $from = \"INSERT_FROM\" $to = \"INSERT_TO\" $url = \"https://api.twilio.com/2010-04-01/Accounts/$sid/Messages.json\" $pair = \"$($sid):$($token)\" $encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair)) $basicAuthValue = \"Basic $encodedCreds\" $Headers = @{ Authorization = $basicAuthValue ContentType = \"application/x-www-form-urlencoded\" } $from = $from.Replace(\"+\", \"%2B\") $to = $to.Replace(\"+\", \"%2B\") $response = Invoke-WebRequest -Uri $url -Method POST -Headers $Headers -Body \"Body=Affirmative&From=$from&To=$to\" You should see a text message on your phone. The script will output any errors from Twilio if something isn't working.","title":"Troubleshooting"},{"location":"elmah-io-apps-youtrack/","text":"Install YouTrack App for elmah.io Get your token To allow elmah.io to create issues on YouTrack, you will need a permanent token. Go to your YouTrack profile, click the Account Security . Here you can generate a new token: Copy the generated token. Install the YouTrack App on elmah.io Log into elmah.io and go to the log settings. Click the Apps tab. Locate the YouTrack app and click the Install button. Input your token and the base URL of your YouTrack Cloud installation. Next, click the Login button to fetch the list of projects from YouTrack: Click Save and the app is added to your log. When new errors are logged, issues are automatically created in the configured YouTrack project.","title":"YouTrack"},{"location":"elmah-io-apps-youtrack/#install-youtrack-app-for-elmahio","text":"","title":"Install YouTrack App for elmah.io"},{"location":"elmah-io-apps-youtrack/#get-your-token","text":"To allow elmah.io to create issues on YouTrack, you will need a permanent token. Go to your YouTrack profile, click the Account Security . Here you can generate a new token: Copy the generated token.","title":"Get your token"},{"location":"elmah-io-apps-youtrack/#install-the-youtrack-app-on-elmahio","text":"Log into elmah.io and go to the log settings. Click the Apps tab. Locate the YouTrack app and click the Install button. Input your token and the base URL of your YouTrack Cloud installation. Next, click the Login button to fetch the list of projects from YouTrack: Click Save and the app is added to your log. When new errors are logged, issues are automatically created in the configured YouTrack project.","title":"Install the YouTrack App on elmah.io"},{"location":"email-troubleshooting/","text":"Email troubleshooting Email troubleshooting Emails on new errors only Email bounced Invalid email Check your promotional and/or spam folder So, you aren't receiving emails from elmah.io? Here is a collection of things to know/try out. Emails on new errors only The most common reason for not receiving emails when errors are logged is that elmah.io only sends the New Error email when an error that we haven't seen before is logged. New errors are marked with a yellow star next to the log message in the UI and can be searched through either search filters or full-text search: isNew:true The new detection algorithm is implemented by looking at a range of fields like the title, type, and severity. Only severities Error and Fatal marked as isNew trigger an email. Email bounced We use AWS to send out all transactional emails from elmah.io. We get a notification from AWS when an email bounces and we stop sending to that email address, even if any new emails wouldn't cause a bounce. Beneath your profile, you will be able to see if your email caused a bounce: As the error message says, get in contact for us to try and reach the email address again. Invalid email Ok, this may seem obvious. But this happens more often than you would think. Typos are a common cause of invalid emails. Specifying a mailing list or group address doesn't always play nice with elmah.io either. For instance, Office 365 distribution groups block external emails as the default. The easiest way to check your inputted email address is to send a new message to that address from an external email provider. Check your promotional and/or spam folder We do a lot to keep our email reputation high. But some email clients may treat similar-looking emails as promotional or spam. Remember to check those folders and mark messages as important if spotting them in the wrong folder.","title":"Email troubleshooting"},{"location":"email-troubleshooting/#email-troubleshooting","text":"Email troubleshooting Emails on new errors only Email bounced Invalid email Check your promotional and/or spam folder So, you aren't receiving emails from elmah.io? Here is a collection of things to know/try out.","title":"Email troubleshooting"},{"location":"email-troubleshooting/#emails-on-new-errors-only","text":"The most common reason for not receiving emails when errors are logged is that elmah.io only sends the New Error email when an error that we haven't seen before is logged. New errors are marked with a yellow star next to the log message in the UI and can be searched through either search filters or full-text search: isNew:true The new detection algorithm is implemented by looking at a range of fields like the title, type, and severity. Only severities Error and Fatal marked as isNew trigger an email.","title":"Emails on new errors only"},{"location":"email-troubleshooting/#email-bounced","text":"We use AWS to send out all transactional emails from elmah.io. We get a notification from AWS when an email bounces and we stop sending to that email address, even if any new emails wouldn't cause a bounce. Beneath your profile, you will be able to see if your email caused a bounce: As the error message says, get in contact for us to try and reach the email address again.","title":"Email bounced"},{"location":"email-troubleshooting/#invalid-email","text":"Ok, this may seem obvious. But this happens more often than you would think. Typos are a common cause of invalid emails. Specifying a mailing list or group address doesn't always play nice with elmah.io either. For instance, Office 365 distribution groups block external emails as the default. The easiest way to check your inputted email address is to send a new message to that address from an external email provider.","title":"Invalid email"},{"location":"email-troubleshooting/#check-your-promotional-andor-spam-folder","text":"We do a lot to keep our email reputation high. But some email clients may treat similar-looking emails as promotional or spam. Remember to check those folders and mark messages as important if spotting them in the wrong folder.","title":"Check your promotional and/or spam folder"},{"location":"handle-elmah-io-downtime/","text":"Handle elmah.io downtime Like every other SaaS product out there, we cannot promise you 100% uptime on elmah.io. We understand, that your logging data is extremely important for your business and we do everything in our power to secure that elmah.io is running smoothly. To monitor our APIs and websites, check out status.elmah.io . It is our general recommendation to implement code that listens for communication errors with the elmah.io API and log errors elsewhere. How you do this depends on which elmah.io NuGet package you have installed. The documentation for each package will show how to subscribe to errors. For Elmah.Io.Client it would look similar to this: var elmahIo = ElmahioAPI.Create(\"API_KEY\"); elmahIo.Messages.OnMessageFail += (sender, args) => { var message = args.Message; var exception = args.Error; // TODO: log it }; For a logging framework like Serilog, it would look similar to this: Log.Logger = new LoggerConfiguration() .WriteTo.ElmahIo(new ElmahIoSinkOptions(\"API_KEY\", new Guid(\"LOG_ID\")) { OnError = (msg, ex) => { // TODO: log it } }) .CreateLogger(); It is important not to log errors in OnMessageFail and OnError callbacks to elmah.io, since that could cause an infinite loop. Check out the documentation for the package you are using for additional details. Response explanation Here's an overview of the types of errors you can experience from the API: Response Meaning Timeout Something is very wrong with our API or Azure. You can be sure that we are working 24/7 to fix it. 500 The API is reachable, but we have a problem communicating with Azure Service bus. Azure has great uptime and all of our resources are dedicated and replicated. Still, we experience short periods of downtime from time to time. 429 We allow a maximum (per API key) of 500 requests per minute and 3600 per hour. 429 means that you have crossed that line. This status code doesn't indicate that the API is down. 4xx Something is wrong with the request. Check out the API documentation for details. This status code doesn't indicate that the API is down.","title":"Handle elmah.io downtime"},{"location":"handle-elmah-io-downtime/#handle-elmahio-downtime","text":"Like every other SaaS product out there, we cannot promise you 100% uptime on elmah.io. We understand, that your logging data is extremely important for your business and we do everything in our power to secure that elmah.io is running smoothly. To monitor our APIs and websites, check out status.elmah.io . It is our general recommendation to implement code that listens for communication errors with the elmah.io API and log errors elsewhere. How you do this depends on which elmah.io NuGet package you have installed. The documentation for each package will show how to subscribe to errors. For Elmah.Io.Client it would look similar to this: var elmahIo = ElmahioAPI.Create(\"API_KEY\"); elmahIo.Messages.OnMessageFail += (sender, args) => { var message = args.Message; var exception = args.Error; // TODO: log it }; For a logging framework like Serilog, it would look similar to this: Log.Logger = new LoggerConfiguration() .WriteTo.ElmahIo(new ElmahIoSinkOptions(\"API_KEY\", new Guid(\"LOG_ID\")) { OnError = (msg, ex) => { // TODO: log it } }) .CreateLogger(); It is important not to log errors in OnMessageFail and OnError callbacks to elmah.io, since that could cause an infinite loop. Check out the documentation for the package you are using for additional details.","title":"Handle elmah.io downtime"},{"location":"handle-elmah-io-downtime/#response-explanation","text":"Here's an overview of the types of errors you can experience from the API: Response Meaning Timeout Something is very wrong with our API or Azure. You can be sure that we are working 24/7 to fix it. 500 The API is reachable, but we have a problem communicating with Azure Service bus. Azure has great uptime and all of our resources are dedicated and replicated. Still, we experience short periods of downtime from time to time. 429 We allow a maximum (per API key) of 500 requests per minute and 3600 per hour. 429 means that you have crossed that line. This status code doesn't indicate that the API is down. 4xx Something is wrong with the request. Check out the API documentation for details. This status code doesn't indicate that the API is down.","title":"Response explanation"},{"location":"heartbeats-troubleshooting/","text":"Heartbeats Troubleshooting Common problems and how to fix them Here you will a list of common problems and how to solve them. Timeout when creating heartbeats through Elmah.Io.Client If you experience a timeout when calling the Healthy , Degraded , or Unhealthy method, you may want to adjust the default HTTP timeout. Elmah.Io.Client has a default timeout of 5 seconds to make sure that logging to elmah.io from a web application won't slow down the web app too much in case of slow response time from the elmah.io API. While 99.9% of the requests to the elmah.io API finish within this timeout, problems with Azure, the network connection, and a lot of other issues can happen. Since heartbeats typically run outside the scope of a web request, it's safe to increase the default HTTP timeout in this case: var api = ElmahioAPI.Create(\"API_KEY\", new ElmahIoOptions { Timeout = new TimeSpan(0, 0, 30) }); The example set a timeout of 30 seconds. SocketException when creating heartbeats through Elmah.Io.Client A System.Net.Sockets.SocketException when communicating with the elmah.io API can mean multiple things. The API can be down or there's network problems between your machine and the API. Increasing the timeout as shown in the previous section should be step one. If you still experience socket exceptions, it might help to implement retries. This can be done by setting up a custom HttpClient : builder.Services .AddHttpClient(\"elmahio\") .AddPolicyHandler(HttpPolicyExtensions .HandleTransientHttpError() .WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(i))); The AddPolicyHandler is available when installing the Microsoft.Extensions.Http.Polly NuGet package. Next, create the elmah.io client with the custom HttpClient : var httpClient = httpClientFactory.CreateClient(\"elmahio\"); var elmahIoClient = ElmahioAPI.Create(\"API_KEY\", options, httpClient);","title":"Heartbeats Troubleshooting"},{"location":"heartbeats-troubleshooting/#heartbeats-troubleshooting","text":"","title":"Heartbeats Troubleshooting"},{"location":"heartbeats-troubleshooting/#common-problems-and-how-to-fix-them","text":"Here you will a list of common problems and how to solve them.","title":"Common problems and how to fix them"},{"location":"heartbeats-troubleshooting/#timeout-when-creating-heartbeats-through-elmahioclient","text":"If you experience a timeout when calling the Healthy , Degraded , or Unhealthy method, you may want to adjust the default HTTP timeout. Elmah.Io.Client has a default timeout of 5 seconds to make sure that logging to elmah.io from a web application won't slow down the web app too much in case of slow response time from the elmah.io API. While 99.9% of the requests to the elmah.io API finish within this timeout, problems with Azure, the network connection, and a lot of other issues can happen. Since heartbeats typically run outside the scope of a web request, it's safe to increase the default HTTP timeout in this case: var api = ElmahioAPI.Create(\"API_KEY\", new ElmahIoOptions { Timeout = new TimeSpan(0, 0, 30) }); The example set a timeout of 30 seconds.","title":"Timeout when creating heartbeats through Elmah.Io.Client"},{"location":"heartbeats-troubleshooting/#socketexception-when-creating-heartbeats-through-elmahioclient","text":"A System.Net.Sockets.SocketException when communicating with the elmah.io API can mean multiple things. The API can be down or there's network problems between your machine and the API. Increasing the timeout as shown in the previous section should be step one. If you still experience socket exceptions, it might help to implement retries. This can be done by setting up a custom HttpClient : builder.Services .AddHttpClient(\"elmahio\") .AddPolicyHandler(HttpPolicyExtensions .HandleTransientHttpError() .WaitAndRetryAsync(3, i => TimeSpan.FromSeconds(i))); The AddPolicyHandler is available when installing the Microsoft.Extensions.Http.Polly NuGet package. Next, create the elmah.io client with the custom HttpClient : var httpClient = httpClientFactory.CreateClient(\"elmahio\"); var elmahIoClient = ElmahioAPI.Create(\"API_KEY\", options, httpClient);","title":"SocketException when creating heartbeats through Elmah.Io.Client"},{"location":"how-does-the-new-detection-work/","text":"How does the new detection work Being able to identify when a logged error is new or a duplicate of an already logged error, is one of the most important features on elmah.io. A lot of other features are based on this mechanism to help you reduce the number of emails, Slack/Teams messages, and much more. We often get questions about how this works and how to tweak it, why this article should bring some clarity to questions about this feature. When logging messages to elmah.io using either one of the integrations or through the API, we automatically set a flag named isNew on each log message. Calculating the value of this field is based on a rather complex algorithm. The implementation is closed-source but not a huge secret. Each message is assigned a hash value based on a range of fields like the message template, the severity, the URL, and more. Some values are normalized or modified before being sent as input to the hash function. An example of this is removing numbers from the log message which will ensure that Error on product 1 and Error on product 2 will be considered the same log message. When receiving a new log message, we check if an existing message with the same hash is already stored in the log. If not, the new message will be marked as New by setting the isNew flag to true . If we already found one or more log messages with the same hash, the new message will have its isNew flag set to false . Messages and apps Most apps and features around sending messages from elmah.io are based on the isNew flag. This means that only new errors trigger the New Error Email , the Slack and Teams apps, etc. This is done to avoid flooding the recipient system with emails or messages. You typically don't want 1,000 emails if the same error occurs 1,000 times. Error occurrence is still important, why there are other features to help you deal with this like the Error Occurrence Email and spike-based machine learning features. Modifying the hash function We sometimes get requests to modify the hash function, but unfortunately, that's currently not possible. We change the implementation from time to time to improve the uniqueness detection over time. If you have an example of two log messages that should have been considered unique or not considered unique, feel free to reach out. This may or may not result in changes to the hash function. There are still some possibilities to force two log messages to not be unique. The obvious is to include different variables inside the log message. Remember that numbers are removed, why this must consist of letters. Another approach is to put individual values in the source field. This can be done in all integrations by implementing the OnMessage action. Some integrations also support setting the source field by including it as structured properties like Error from {source} . Setting re-occurring messages as New A common request is to get a notification if an error that you believed was fixed re-occur. This scenario is built into elmah.io's issue tracker. When marking a log message as fixed through the UI or API, elmah.io automatically marks all instances of the log message as fixed. If a new log message with the same hash is logged at some point, the isNew flag on this message will be set to true . This will trigger the New Error Email and most of the integrations again. Retention Depending on your current plan, each subscription provides x days of retention for log messages. This means that log messages are automatically deleted after x days in the database. Once all instances of a log message are deleted, a new log message generating the same hash as the deleted messages will be marked as new. To increase the chance of log messages being marked as new, you can lower the retention on each log on the Log Settings page.","title":"How does the new detection work"},{"location":"how-does-the-new-detection-work/#how-does-the-new-detection-work","text":"Being able to identify when a logged error is new or a duplicate of an already logged error, is one of the most important features on elmah.io. A lot of other features are based on this mechanism to help you reduce the number of emails, Slack/Teams messages, and much more. We often get questions about how this works and how to tweak it, why this article should bring some clarity to questions about this feature. When logging messages to elmah.io using either one of the integrations or through the API, we automatically set a flag named isNew on each log message. Calculating the value of this field is based on a rather complex algorithm. The implementation is closed-source but not a huge secret. Each message is assigned a hash value based on a range of fields like the message template, the severity, the URL, and more. Some values are normalized or modified before being sent as input to the hash function. An example of this is removing numbers from the log message which will ensure that Error on product 1 and Error on product 2 will be considered the same log message. When receiving a new log message, we check if an existing message with the same hash is already stored in the log. If not, the new message will be marked as New by setting the isNew flag to true . If we already found one or more log messages with the same hash, the new message will have its isNew flag set to false .","title":"How does the new detection work"},{"location":"how-does-the-new-detection-work/#messages-and-apps","text":"Most apps and features around sending messages from elmah.io are based on the isNew flag. This means that only new errors trigger the New Error Email , the Slack and Teams apps, etc. This is done to avoid flooding the recipient system with emails or messages. You typically don't want 1,000 emails if the same error occurs 1,000 times. Error occurrence is still important, why there are other features to help you deal with this like the Error Occurrence Email and spike-based machine learning features.","title":"Messages and apps"},{"location":"how-does-the-new-detection-work/#modifying-the-hash-function","text":"We sometimes get requests to modify the hash function, but unfortunately, that's currently not possible. We change the implementation from time to time to improve the uniqueness detection over time. If you have an example of two log messages that should have been considered unique or not considered unique, feel free to reach out. This may or may not result in changes to the hash function. There are still some possibilities to force two log messages to not be unique. The obvious is to include different variables inside the log message. Remember that numbers are removed, why this must consist of letters. Another approach is to put individual values in the source field. This can be done in all integrations by implementing the OnMessage action. Some integrations also support setting the source field by including it as structured properties like Error from {source} .","title":"Modifying the hash function"},{"location":"how-does-the-new-detection-work/#setting-re-occurring-messages-as-new","text":"A common request is to get a notification if an error that you believed was fixed re-occur. This scenario is built into elmah.io's issue tracker. When marking a log message as fixed through the UI or API, elmah.io automatically marks all instances of the log message as fixed. If a new log message with the same hash is logged at some point, the isNew flag on this message will be set to true . This will trigger the New Error Email and most of the integrations again.","title":"Setting re-occurring messages as New"},{"location":"how-does-the-new-detection-work/#retention","text":"Depending on your current plan, each subscription provides x days of retention for log messages. This means that log messages are automatically deleted after x days in the database. Once all instances of a log message are deleted, a new log message generating the same hash as the deleted messages will be marked as new. To increase the chance of log messages being marked as new, you can lower the retention on each log on the Log Settings page.","title":"Retention"},{"location":"how-prices-are-calculated/","text":"How prices are calculated When using elmah.io, you may experience the need to switch plans, update your credit card, or do other tasks which will require changes to your subscription. This document explains the different upgrade/downgrade and payment paths. Switch to a higher plan You can switch to a larger plan at all times. If you purchased a Small Business ($29) on June 1 and want to upgrade to Business ($49) on June 15, we charge you ~ $35. You already paid $15 for half of June on the Small Business plan, so the remaining amount is deducted from the $49. Your next payment will be on July 15. Switch to a lower plan You can switch to a lower plan when your current subscription is renewed. If you purchased a Business plan ($49) on June 15. and downgrade to a Small Business plan ($29) on July 1, you would be charged $49 on June 15, and $29 on July 15. You commit to either a month or a year in advance. Update your credit card A new credit card can be inputted at any time during your subscription. The payment provider will automatically charge the new credit card on the next payment. Purchase a top-up If you need additional messages but don't want to upgrade to a larger plan permanently, you can purchase a top-up. The purchase is made on the credit card already configured on the subscription. If you want to buy the top-up on a different credit card, you will need to use the Update credit card feature first. The price of the top-up is always $19, and you can purchase as many top-ups as you want. Switch payment interval Switching from monthly to annual or annual to monthly is possible. If you switch from monthly to annual, you are charged the difference between the two plans as in Switch to a higher plan . If you switch from annual to monthly, one of two scenarios can happen. If you switch to the same plan or lower, your plan will automatically switch when your current subscription renews (like in Switch to a lower plan ). If you switch to a higher plan, your plan will switch immediately as in Switch to a higher plan . If the price of the remaining time on your current plan covers the price of the new plan, the exceeding amount will be credited to your balance and used to pay for the new plan and any upcoming invoices. Paying for top-ups with the balance is currently not supported. Purchase a subscription from Denmark We are based in Denmark, why selling services to another Danish company requires us to include 25% VAT (moms). The price on the various dialogs will automatically show the price including VAT as well as the VAT amount. Your invoices will include the amount in VAT to inform SKAT.","title":"How prices are calculated"},{"location":"how-prices-are-calculated/#how-prices-are-calculated","text":"When using elmah.io, you may experience the need to switch plans, update your credit card, or do other tasks which will require changes to your subscription. This document explains the different upgrade/downgrade and payment paths.","title":"How prices are calculated"},{"location":"how-prices-are-calculated/#switch-to-a-higher-plan","text":"You can switch to a larger plan at all times. If you purchased a Small Business ($29) on June 1 and want to upgrade to Business ($49) on June 15, we charge you ~ $35. You already paid $15 for half of June on the Small Business plan, so the remaining amount is deducted from the $49. Your next payment will be on July 15.","title":"Switch to a higher plan"},{"location":"how-prices-are-calculated/#switch-to-a-lower-plan","text":"You can switch to a lower plan when your current subscription is renewed. If you purchased a Business plan ($49) on June 15. and downgrade to a Small Business plan ($29) on July 1, you would be charged $49 on June 15, and $29 on July 15. You commit to either a month or a year in advance.","title":"Switch to a lower plan"},{"location":"how-prices-are-calculated/#update-your-credit-card","text":"A new credit card can be inputted at any time during your subscription. The payment provider will automatically charge the new credit card on the next payment.","title":"Update your credit card"},{"location":"how-prices-are-calculated/#purchase-a-top-up","text":"If you need additional messages but don't want to upgrade to a larger plan permanently, you can purchase a top-up. The purchase is made on the credit card already configured on the subscription. If you want to buy the top-up on a different credit card, you will need to use the Update credit card feature first. The price of the top-up is always $19, and you can purchase as many top-ups as you want.","title":"Purchase a top-up"},{"location":"how-prices-are-calculated/#switch-payment-interval","text":"Switching from monthly to annual or annual to monthly is possible. If you switch from monthly to annual, you are charged the difference between the two plans as in Switch to a higher plan . If you switch from annual to monthly, one of two scenarios can happen. If you switch to the same plan or lower, your plan will automatically switch when your current subscription renews (like in Switch to a lower plan ). If you switch to a higher plan, your plan will switch immediately as in Switch to a higher plan . If the price of the remaining time on your current plan covers the price of the new plan, the exceeding amount will be credited to your balance and used to pay for the new plan and any upcoming invoices. Paying for top-ups with the balance is currently not supported.","title":"Switch payment interval"},{"location":"how-prices-are-calculated/#purchase-a-subscription-from-denmark","text":"We are based in Denmark, why selling services to another Danish company requires us to include 25% VAT (moms). The price on the various dialogs will automatically show the price including VAT as well as the VAT amount. Your invoices will include the amount in VAT to inform SKAT.","title":"Purchase a subscription from Denmark"},{"location":"how-to-avoid-emails-getting-classified-as-spam/","text":"How to avoid emails getting classified as spam We do everything in our power to maintain a good email domain reputation. Sometimes emails sent from elmah.io may be classified as spam in your email client. The easiest way to avoid this is to inspect the sender in an email received from @elmah.io and add it to your contact list (we primarily send emails from info@elmah.io and noreply@elmah.io ). How you do this depends on your email client but all major clients have this option. In the sections below, there are alternatives to the contact list approach for various email clients. Gmail If you don't want to add elmah.io addresses to your contact list you can use Gmail's Filters feature to always classify *@elmah.io as not spam. To do so, go to Settings | Filters and create a new filter: Click Create filter and check the Never send it to Spam option: Finally, click the Create filter button, and emails from elmah.io will no longer be classified as spam.","title":"How to avoid emails getting classified as spam"},{"location":"how-to-avoid-emails-getting-classified-as-spam/#how-to-avoid-emails-getting-classified-as-spam","text":"We do everything in our power to maintain a good email domain reputation. Sometimes emails sent from elmah.io may be classified as spam in your email client. The easiest way to avoid this is to inspect the sender in an email received from @elmah.io and add it to your contact list (we primarily send emails from info@elmah.io and noreply@elmah.io ). How you do this depends on your email client but all major clients have this option. In the sections below, there are alternatives to the contact list approach for various email clients.","title":"How to avoid emails getting classified as spam"},{"location":"how-to-avoid-emails-getting-classified-as-spam/#gmail","text":"If you don't want to add elmah.io addresses to your contact list you can use Gmail's Filters feature to always classify *@elmah.io as not spam. To do so, go to Settings | Filters and create a new filter: Click Create filter and check the Never send it to Spam option: Finally, click the Create filter button, and emails from elmah.io will no longer be classified as spam.","title":"Gmail"},{"location":"how-to-configure-api-key-permissions/","text":"How to configure API key permissions Security on the elmah.io API is handled through the use of API keys. All requests to the API must be accompanied by an API key. When creating your organization, a default API key was automatically created. API keys can be revoked and you can create multiple API keys for different purposes and projects. Much like a user can be awarded different levels of access on elmah.io, API keys also have a set of permissions. The default created API key for your organization, only have permission to write log messages to elmah.io. To configure permissions for a new or existing API key, either click the Edit or Add API Key button on the API Keys tab of your organization settings. This will show the API key editor view: As mentioned previously, new keys have the messages_write permission enabled only. This permission will cover logging from your application to elmah.io. If your application needs to browse messages from elmah.io, create new logs/applications, etc. you will need to enable the corresponding permission. Notice that read permissions don't need to be enabled, for you to browse logs and log messages on the elmah.io UI. API keys are used by the range of client integrations only. Your API key shouldn't be shared outside your organization. In some situations, you will need to share your API key (like when logging from JavaScript). In these cases, it's essential that your API key only has the messages_write permission enabled. With all permissions enabled, everyone will be able to browse your logs.","title":"How to configure API key permissions"},{"location":"how-to-configure-api-key-permissions/#how-to-configure-api-key-permissions","text":"Security on the elmah.io API is handled through the use of API keys. All requests to the API must be accompanied by an API key. When creating your organization, a default API key was automatically created. API keys can be revoked and you can create multiple API keys for different purposes and projects. Much like a user can be awarded different levels of access on elmah.io, API keys also have a set of permissions. The default created API key for your organization, only have permission to write log messages to elmah.io. To configure permissions for a new or existing API key, either click the Edit or Add API Key button on the API Keys tab of your organization settings. This will show the API key editor view: As mentioned previously, new keys have the messages_write permission enabled only. This permission will cover logging from your application to elmah.io. If your application needs to browse messages from elmah.io, create new logs/applications, etc. you will need to enable the corresponding permission. Notice that read permissions don't need to be enabled, for you to browse logs and log messages on the elmah.io UI. API keys are used by the range of client integrations only. Your API key shouldn't be shared outside your organization. In some situations, you will need to share your API key (like when logging from JavaScript). In these cases, it's essential that your API key only has the messages_write permission enabled. With all permissions enabled, everyone will be able to browse your logs.","title":"How to configure API key permissions"},{"location":"how-to-correlate-messages-across-services/","text":"How to correlate messages across services How to correlate messages across services CorrelationId explained Setting CorrelationId Elmah.Io.Client.Extensions.Correlation ASP.NET Core Microsoft.Extensions.Logging Serilog NLog log4net W3C Trace Context A common architecture is to spread out code across multiple services. Some developers like to split up their code into microservices. Others have requirements for running code asynchronous behind a queue. The common theme here is that code often runs in multiple processes spread across multiple servers. While logging can be easily set up in different projects and services, being able to correlate log messages across multiple services isn't normally available when logging to individual log outputs. Imagine a console application making an HTTP request to your public API. The API calls an internal API, which again puts a message on a queue. A service consumes messages from the queue and ends up logging an error in the error log. Seeing the entire log trace from receiving the request on the API straight down the chain resulting in the error, highly increases the chance of figuring out what went wrong. With the Correlation feature on elmah.io, we want to help you achieve just that. In this article, you will learn how to set up Correlations. Correlation currently requires all of your applications to log to the same log on elmah.io. For you to separate log messages from each service, we recommend setting the Application property on all log messages. Filtering on log messages is available through the elmah.io UI. We may want to explore ways to use Correlation across multiple error logs in the future. CorrelationId explained The way log messages are correlated on elmah.io is based on a property on all log messages named correlationId . The field is available both when creating log messages through our API as well as in the Elmah.Io.Client NuGet package. The property is a string which means that you can use whatever schema you want for the correlation ID. To set the correlation ID using the client, use the following code: var myCorrelationId = \"42\"; var client = ElmahioAPI.Create(\"API_KEY\"); await client.Messages.CreateAndNotifyAsync(new Guid(\"LOG_ID\"), new CreateMessage { Title = \"Hello World\", // ... CorrelationId = myCorrelationId }); In a real-world scenario, myCorrelationId wouldn't be hardcoded but pulled from a shared header, message ID, or similar. As long as all services set CorrelationId to the same ID, log messages within a correlation can be searched on the elmah.io UI: Setting CorrelationId How you set the correlation ID depends on which integration you are using. In some cases, a correlation ID is set automatically while others will require a few lines of code. Elmah.Io.Client.Extensions.Correlation We have developed a NuGet package dedicated to setting the correlation ID from the current activity in an easy way. The package can be used together with all of the various client integrations we offer (like Elmah.Io.AspNetCore and Elmah.Io.NLog ). Start by installing the package: Package Manager .NET CLI PackageReference Paket CLI Install-Package Elmah.Io.Client.Extensions.Correlation dotnet add package Elmah.Io.Client.Extensions.Correlation paket add Elmah.Io.Client.Extensions.Correlation Next, call the WithCorrelationIdFromActivity method as part of the OnMessage action/event. How you do this depends on which of the client integrations you are using. For Elmah.Io.Client it can be done like this: var elmahIo = ElmahioAPI.Create(\"API_KEY\"); elmahIo.Messages.OnMessage += (sender, args) => { args.Message.WithCorrelationIdFromActivity(); }; For Elmah.Io.AspNetCore it can be done like this: builder.Services.AddElmahIo(options => { // ... options.OnMessage = msg => { msg.WithCorrelationIdFromActivity(); }; }); ASP.NET Core When logging uncaught errors using the Elmah.Io.AspNetCore package, we automatically pick up any traceparent header and put the trace ID as part of the error logged to elmah.io. For an overview of wrapping calls to your ASP.NET Core API in an Activity check out the section about W3C Trace Context. If you want to set the correlation ID manually, you can use the OnMessage action: builder.Services.Configure(o => { o.OnMessage = msg => { msg.CorrelationId = \"42\"; }; }); When requested through the browser, a traceparent is not automatically added, unless you manually do so by using an extension as shown in the W3C section. In this case, you can either install the Elmah.Io.Client.Extensions.Correlation package as already explained, or set the correlationId manually by installing the System.Diagnostics.DiagnosticSource NuGet package and adding the following code to the OnMessage action: o.OnMessage = msg => { msg.CorrelationId = System.Diagnostics.Activity.Current?.TraceId.ToString(); }; Microsoft.Extensions.Logging To store a correlation ID when logging through Microsoft.Extensions.Logging you can either set the CorrelationId property (manually or using the Elmah.Io.Client.Extensions.Correlation NuGet package) or rely on the automatic behavior built into Microsoft.Extensions.Logging. To manually set the correlation you can include correlationId as part of the log message: logger.LogInformation(\"A log message with {correlationId}\", \"42\"); Or you can put the correlation ID as part of a logging scope: using (logger.BeginScope(new { CorrelationId = \"42\" })) { logger.LogInformation(\"A log message\"); } In some cases, a correlation ID will be set automatically. If there is a current active Activity (see later), Microsoft.Extensions.Logging automatically decorates all log messages with a custom property named TraceId . The elmah.io backend will pick up any value in the TraceId and use that as the correlation ID. Serilog When logging through Serilog a correlation ID can be added to one or more log messages in multiple ways. The most obvious being on the log message itself: Log.Information(\"A log message with {correlationId}\", \"42\"); If you don't want correlation ID as part of the log message, you can push the property using LogContext : using (LogContext.PushProperty(\"correlationId\", \"42\")) { Log.Information(\"A log message\"); } You will need to install the LogContext enricher for this to work: Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() // ... .CreateLogger(); The elmah.io sink for Serilog is an async batching sync. This means that log messages are not logged in the same millisecond as one of the logging methods on the Log class is called and the current activity is no longer set. When logging from a web application or other project types where the activity is short-lived, you either need to include the correlation ID as part of the message (as shown in the previous examples) or you need to store the correlation ID as part of the request. For ASP.NET Core this can be done using middleware: app.Use(async (ctx, next) => { IDisposable disposeMe = null; var activity = Activity.Current; if (activity != null) { disposeMe = LogContext.PushProperty(\"correlationid\", activity.TraceId.ToString()); } try { await next(); } finally { disposeMe?.Dispose(); } }); All calls to Serilog within the web request will have the correlation ID set to the TraceId of the current activity. Other frameworks support similar features for enriching the current request or invocation with custom properties. NLog Correlation ID can be set on log messages logged through NLog in multiple ways. The first approach is to include the ID directly in the log message: logger.Info(\"A log message with {correlationId}\", \"42\"); If you don't want the ID as part of the log message you can use either NLog's two context objects: using (MappedDiagnosticsLogicalContext.SetScoped(\"correlationId\", \"42\")) { logger.Info(\"A log message\"); } GlobalDiagnosticsContext.Set(\"correlationId\", \"42\"); You can also add the property manually to the log message using the log event builder API available in NLog: var infoMessage = new LogEventInfo(LogLevel.Info, \"\", \"A log message\"); infoMessage.Properties.Add(\"correlationid\", \"42\"); logger.Info(infoMessage); log4net Correlation ID can be set on log messages logged through log4net in multiple ways. You can include it directly on the LoggingEvent : var properties = new PropertiesDictionary(); properties[\"correlationid\"] = \"42\"; log.Logger.Log(new LoggingEvent(new LoggingEventData { Level = Level.Info, TimeStampUtc = DateTime.UtcNow, Properties = properties, Message = \"A log message\", })); You most likely use the Info , Warn , and similar helper methods to store log messages. In this case, you can set the correlation ID on the ThreadContext : ThreadContext.Properties[\"correlationid\"] = \"42\"; log.Info(\"A log message\"); Please notice that correlationid in both examples must be in all lowercase. W3C Trace Context The class Activity has been mentioned a couple of times already. Let's take a look at what that is and how it relates to W3C Trace Context. Trace Context is a specification by W3C for implementing distributed tracing across multiple processes which are already widely adopted. If you generate a trace identifier in a client initiating a chain of events, different Microsoft technologies like ASP.NET Core already pick up the extended set of headers and include those as part of log messages logged through Microsoft.Extensions.Logging. Let's say we have a console application calling an API and we want to log messages in both the console app and in the API and correlate them in elmah.io. In both the console app and in the ASP.NET Core application, you would set up Elmah.Io.Extensions.Logging using the default configuration. Then in the console application, you will wrap the call to the API in an Activity : var httpClient = new HttpClient(); var activity = new Activity(\"ApiCall\").Start(); try { using (logger.BeginScope(new { TraceId = activity.TraceId, ParentId = activity.ParentSpanId, SpanId = activity.SpanId })) { logger.LogInformation(\"Fetching data from the API\"); var result = await httpClient.GetStreamAsync(\"https://localhost:44396/ping\"); // ... } } finally { activity.Stop(); } By creating and starting a new Activity before we call the API, log messages in the console app can be decorated with three additional properties: TraceId , ParentId , and SpanId . This will make sure that all log messages logged within this scope will get the correlation ID set on elmah.io. HttpClient automatically picks up the activity and decorates the request with additional headers. On the API we simply log as normal: [ApiController] [Route(\"[controller]\")] public class PingController : ControllerBase { private readonly ILogger _logger; public PingController(ILogger logger) { _logger = logger; } [HttpGet] public string Get() { _logger.LogInformation(\"Received a ping\"); return \"pong\"; } } Notice that we didn't have to decorate log messages with additional properties. ASP.NET Core automatically picks up the new headers and decorates all log messages with the correct trace ID. If you want to test this through a browser, you'll need to modify the request headers before the request is made to your web application. There is a range of extensions available to help with this. For the following example, we'll use ModHeader : The extension will enrich all requests with a header named traceparent in the format VERSION-TRACE_ID-SPAN_ID-TRACE_FLAGS . elmah.io will automatically pick up this header and set correlationId to the value of TRACE_ID . If you don't get a correlation ID set on your log messages, we recommend installing the Elmah.Io.Client.Extensions.Correlation NuGet package and calling the following method in the OnMessage event/action: msg.WithCorrelationIdFromActivity();","title":"How to correlate messages across services"},{"location":"how-to-correlate-messages-across-services/#how-to-correlate-messages-across-services","text":"How to correlate messages across services CorrelationId explained Setting CorrelationId Elmah.Io.Client.Extensions.Correlation ASP.NET Core Microsoft.Extensions.Logging Serilog NLog log4net W3C Trace Context A common architecture is to spread out code across multiple services. Some developers like to split up their code into microservices. Others have requirements for running code asynchronous behind a queue. The common theme here is that code often runs in multiple processes spread across multiple servers. While logging can be easily set up in different projects and services, being able to correlate log messages across multiple services isn't normally available when logging to individual log outputs. Imagine a console application making an HTTP request to your public API. The API calls an internal API, which again puts a message on a queue. A service consumes messages from the queue and ends up logging an error in the error log. Seeing the entire log trace from receiving the request on the API straight down the chain resulting in the error, highly increases the chance of figuring out what went wrong. With the Correlation feature on elmah.io, we want to help you achieve just that. In this article, you will learn how to set up Correlations. Correlation currently requires all of your applications to log to the same log on elmah.io. For you to separate log messages from each service, we recommend setting the Application property on all log messages. Filtering on log messages is available through the elmah.io UI. We may want to explore ways to use Correlation across multiple error logs in the future.","title":"How to correlate messages across services"},{"location":"how-to-correlate-messages-across-services/#correlationid-explained","text":"The way log messages are correlated on elmah.io is based on a property on all log messages named correlationId . The field is available both when creating log messages through our API as well as in the Elmah.Io.Client NuGet package. The property is a string which means that you can use whatever schema you want for the correlation ID. To set the correlation ID using the client, use the following code: var myCorrelationId = \"42\"; var client = ElmahioAPI.Create(\"API_KEY\"); await client.Messages.CreateAndNotifyAsync(new Guid(\"LOG_ID\"), new CreateMessage { Title = \"Hello World\", // ... CorrelationId = myCorrelationId }); In a real-world scenario, myCorrelationId wouldn't be hardcoded but pulled from a shared header, message ID, or similar. As long as all services set CorrelationId to the same ID, log messages within a correlation can be searched on the elmah.io UI:","title":"CorrelationId explained"},{"location":"how-to-correlate-messages-across-services/#setting-correlationid","text":"How you set the correlation ID depends on which integration you are using. In some cases, a correlation ID is set automatically while others will require a few lines of code.","title":"Setting CorrelationId"},{"location":"how-to-correlate-messages-across-services/#elmahioclientextensionscorrelation","text":"We have developed a NuGet package dedicated to setting the correlation ID from the current activity in an easy way. The package can be used together with all of the various client integrations we offer (like Elmah.Io.AspNetCore and Elmah.Io.NLog ). Start by installing the package: Package Manager .NET CLI PackageReference Paket CLI Install-Package Elmah.Io.Client.Extensions.Correlation dotnet add package Elmah.Io.Client.Extensions.Correlation paket add Elmah.Io.Client.Extensions.Correlation Next, call the WithCorrelationIdFromActivity method as part of the OnMessage action/event. How you do this depends on which of the client integrations you are using. For Elmah.Io.Client it can be done like this: var elmahIo = ElmahioAPI.Create(\"API_KEY\"); elmahIo.Messages.OnMessage += (sender, args) => { args.Message.WithCorrelationIdFromActivity(); }; For Elmah.Io.AspNetCore it can be done like this: builder.Services.AddElmahIo(options => { // ... options.OnMessage = msg => { msg.WithCorrelationIdFromActivity(); }; });","title":"Elmah.Io.Client.Extensions.Correlation"},{"location":"how-to-correlate-messages-across-services/#aspnet-core","text":"When logging uncaught errors using the Elmah.Io.AspNetCore package, we automatically pick up any traceparent header and put the trace ID as part of the error logged to elmah.io. For an overview of wrapping calls to your ASP.NET Core API in an Activity check out the section about W3C Trace Context. If you want to set the correlation ID manually, you can use the OnMessage action: builder.Services.Configure(o => { o.OnMessage = msg => { msg.CorrelationId = \"42\"; }; }); When requested through the browser, a traceparent is not automatically added, unless you manually do so by using an extension as shown in the W3C section. In this case, you can either install the Elmah.Io.Client.Extensions.Correlation package as already explained, or set the correlationId manually by installing the System.Diagnostics.DiagnosticSource NuGet package and adding the following code to the OnMessage action: o.OnMessage = msg => { msg.CorrelationId = System.Diagnostics.Activity.Current?.TraceId.ToString(); };","title":"ASP.NET Core"},{"location":"how-to-correlate-messages-across-services/#microsoftextensionslogging","text":"To store a correlation ID when logging through Microsoft.Extensions.Logging you can either set the CorrelationId property (manually or using the Elmah.Io.Client.Extensions.Correlation NuGet package) or rely on the automatic behavior built into Microsoft.Extensions.Logging. To manually set the correlation you can include correlationId as part of the log message: logger.LogInformation(\"A log message with {correlationId}\", \"42\"); Or you can put the correlation ID as part of a logging scope: using (logger.BeginScope(new { CorrelationId = \"42\" })) { logger.LogInformation(\"A log message\"); } In some cases, a correlation ID will be set automatically. If there is a current active Activity (see later), Microsoft.Extensions.Logging automatically decorates all log messages with a custom property named TraceId . The elmah.io backend will pick up any value in the TraceId and use that as the correlation ID.","title":"Microsoft.Extensions.Logging"},{"location":"how-to-correlate-messages-across-services/#serilog","text":"When logging through Serilog a correlation ID can be added to one or more log messages in multiple ways. The most obvious being on the log message itself: Log.Information(\"A log message with {correlationId}\", \"42\"); If you don't want correlation ID as part of the log message, you can push the property using LogContext : using (LogContext.PushProperty(\"correlationId\", \"42\")) { Log.Information(\"A log message\"); } You will need to install the LogContext enricher for this to work: Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() // ... .CreateLogger(); The elmah.io sink for Serilog is an async batching sync. This means that log messages are not logged in the same millisecond as one of the logging methods on the Log class is called and the current activity is no longer set. When logging from a web application or other project types where the activity is short-lived, you either need to include the correlation ID as part of the message (as shown in the previous examples) or you need to store the correlation ID as part of the request. For ASP.NET Core this can be done using middleware: app.Use(async (ctx, next) => { IDisposable disposeMe = null; var activity = Activity.Current; if (activity != null) { disposeMe = LogContext.PushProperty(\"correlationid\", activity.TraceId.ToString()); } try { await next(); } finally { disposeMe?.Dispose(); } }); All calls to Serilog within the web request will have the correlation ID set to the TraceId of the current activity. Other frameworks support similar features for enriching the current request or invocation with custom properties.","title":"Serilog"},{"location":"how-to-correlate-messages-across-services/#nlog","text":"Correlation ID can be set on log messages logged through NLog in multiple ways. The first approach is to include the ID directly in the log message: logger.Info(\"A log message with {correlationId}\", \"42\"); If you don't want the ID as part of the log message you can use either NLog's two context objects: using (MappedDiagnosticsLogicalContext.SetScoped(\"correlationId\", \"42\")) { logger.Info(\"A log message\"); } GlobalDiagnosticsContext.Set(\"correlationId\", \"42\"); You can also add the property manually to the log message using the log event builder API available in NLog: var infoMessage = new LogEventInfo(LogLevel.Info, \"\", \"A log message\"); infoMessage.Properties.Add(\"correlationid\", \"42\"); logger.Info(infoMessage);","title":"NLog"},{"location":"how-to-correlate-messages-across-services/#log4net","text":"Correlation ID can be set on log messages logged through log4net in multiple ways. You can include it directly on the LoggingEvent : var properties = new PropertiesDictionary(); properties[\"correlationid\"] = \"42\"; log.Logger.Log(new LoggingEvent(new LoggingEventData { Level = Level.Info, TimeStampUtc = DateTime.UtcNow, Properties = properties, Message = \"A log message\", })); You most likely use the Info , Warn , and similar helper methods to store log messages. In this case, you can set the correlation ID on the ThreadContext : ThreadContext.Properties[\"correlationid\"] = \"42\"; log.Info(\"A log message\"); Please notice that correlationid in both examples must be in all lowercase.","title":"log4net"},{"location":"how-to-correlate-messages-across-services/#w3c-trace-context","text":"The class Activity has been mentioned a couple of times already. Let's take a look at what that is and how it relates to W3C Trace Context. Trace Context is a specification by W3C for implementing distributed tracing across multiple processes which are already widely adopted. If you generate a trace identifier in a client initiating a chain of events, different Microsoft technologies like ASP.NET Core already pick up the extended set of headers and include those as part of log messages logged through Microsoft.Extensions.Logging. Let's say we have a console application calling an API and we want to log messages in both the console app and in the API and correlate them in elmah.io. In both the console app and in the ASP.NET Core application, you would set up Elmah.Io.Extensions.Logging using the default configuration. Then in the console application, you will wrap the call to the API in an Activity : var httpClient = new HttpClient(); var activity = new Activity(\"ApiCall\").Start(); try { using (logger.BeginScope(new { TraceId = activity.TraceId, ParentId = activity.ParentSpanId, SpanId = activity.SpanId })) { logger.LogInformation(\"Fetching data from the API\"); var result = await httpClient.GetStreamAsync(\"https://localhost:44396/ping\"); // ... } } finally { activity.Stop(); } By creating and starting a new Activity before we call the API, log messages in the console app can be decorated with three additional properties: TraceId , ParentId , and SpanId . This will make sure that all log messages logged within this scope will get the correlation ID set on elmah.io. HttpClient automatically picks up the activity and decorates the request with additional headers. On the API we simply log as normal: [ApiController] [Route(\"[controller]\")] public class PingController : ControllerBase { private readonly ILogger _logger; public PingController(ILogger logger) { _logger = logger; } [HttpGet] public string Get() { _logger.LogInformation(\"Received a ping\"); return \"pong\"; } } Notice that we didn't have to decorate log messages with additional properties. ASP.NET Core automatically picks up the new headers and decorates all log messages with the correct trace ID. If you want to test this through a browser, you'll need to modify the request headers before the request is made to your web application. There is a range of extensions available to help with this. For the following example, we'll use ModHeader : The extension will enrich all requests with a header named traceparent in the format VERSION-TRACE_ID-SPAN_ID-TRACE_FLAGS . elmah.io will automatically pick up this header and set correlationId to the value of TRACE_ID . If you don't get a correlation ID set on your log messages, we recommend installing the Elmah.Io.Client.Extensions.Correlation NuGet package and calling the following method in the OnMessage event/action: msg.WithCorrelationIdFromActivity();","title":"W3C Trace Context"},{"location":"how-to-enable-two-factor-login/","text":"How to enable two-factor login How to enable two-factor login Two-factor with an elmah.io username and password Two-factor with a social provider elmah.io supports two-factor login through either one of the social providers or a two-factor app like Google Authenticator or Authy. Two-factor with an elmah.io username and password When signing into elmah.io with a username and password, two-factor authentication can be enabled on the Security tab on your profile: Follow the instructions on the page to install either Google Authenticator or Authy. Once you have the app installed, scan the on-screen QR code and input the generated token in the field in step 3. Once two-factor authentication has been successfully set up, the following screen is shown: Two-factor authentication can be disabled at any time by inputting a new code from the authenticator app in the text field and clicking the Deactivate two-factor login button. We recommend that you sign out after enabling two-factor authentication to invalidate the current session. Popular authenticator apps like Google Authenticator and Microsoft Authenticator support cloud backup. Make sure to enable this in case you lose your phone. When cloud backup is enabled, you can sign in with your main account when you get a new phone and all of your stored accounts will be automatically restored. Two-factor with a social provider When using one of the social providers to log in to elmah.io, two-factor authentication can be enabled through either Twitter, Facebook, Microsoft, or Google. Check out the documentation for each authentication mechanism for details on how to enable two-factor authentication.","title":"How to enable two-factor login"},{"location":"how-to-enable-two-factor-login/#how-to-enable-two-factor-login","text":"How to enable two-factor login Two-factor with an elmah.io username and password Two-factor with a social provider elmah.io supports two-factor login through either one of the social providers or a two-factor app like Google Authenticator or Authy.","title":"How to enable two-factor login"},{"location":"how-to-enable-two-factor-login/#two-factor-with-an-elmahio-username-and-password","text":"When signing into elmah.io with a username and password, two-factor authentication can be enabled on the Security tab on your profile: Follow the instructions on the page to install either Google Authenticator or Authy. Once you have the app installed, scan the on-screen QR code and input the generated token in the field in step 3. Once two-factor authentication has been successfully set up, the following screen is shown: Two-factor authentication can be disabled at any time by inputting a new code from the authenticator app in the text field and clicking the Deactivate two-factor login button. We recommend that you sign out after enabling two-factor authentication to invalidate the current session. Popular authenticator apps like Google Authenticator and Microsoft Authenticator support cloud backup. Make sure to enable this in case you lose your phone. When cloud backup is enabled, you can sign in with your main account when you get a new phone and all of your stored accounts will be automatically restored.","title":"Two-factor with an elmah.io username and password"},{"location":"how-to-enable-two-factor-login/#two-factor-with-a-social-provider","text":"When using one of the social providers to log in to elmah.io, two-factor authentication can be enabled through either Twitter, Facebook, Microsoft, or Google. Check out the documentation for each authentication mechanism for details on how to enable two-factor authentication.","title":"Two-factor with a social provider"},{"location":"how-to-get-elmah-io-to-resolve-the-correct-client-ip/","text":"How to get elmah.io to resolve the correct client IP elmah.io try to resolve the IP of the client causing a log message, no matter what severity (Error, Information, etc.) and platform (browser, web-server, etc.) a log message is sent from. This is done by looking at multiple pieces of information provided by the sender. In some cases, elmah.io may not be able to resolve the IP or resolve a wrong IP address. In this document, you will find help getting the right client IP into elmah.io. Missing IP when using a proxy If you are using a proxy layer in between the client and your web server, you may experience log messages without a client IP. This is probably caused by the proxy hiding the original IP from your web server. Most proxies offer an alternative server variable like the X-Forwarded-For header. You can inspect the server variables on the Server Variables tab on elmah.io and check if your proxy includes the original IP in any of the variables. We support custom headers from a range of proxies (like Cloudflare). Most proxies support some kind of settings area where the X-Forwarded-For header can be enabled. If you are using a proxy that uses custom headers, please make sure to reach out and we may want to include the custom header to elmah.io. Wrong IP when using a proxy If the client IP is wrong when behind a proxy, it is typically because the proxy replaces the client IP when calling your server with the IP of its server. This is a poor practice and makes it very hard for elmah.io to figure out which IP belongs to the user and which one to the proxy. Luckily, this is configurable in a lot of proxies through their settings area. Missing IP This can be caused by several issues. In most instances, the client doesn't include any server variables that reveal the IP address. In this case, the client IP will not be available within the elmah.io UI. In some cases, you may know the user's IP from session variables or similar. To include an IP on messages logged to elmah.io, you can implement the OnMessage event or action, depending on which integration you are using. In this example, we use the OnMessage event on the Elmah.Io.Client package to include the user's IP manually: var userIp = \"1.1.1.1\"; // <-- just for the demo var client = ElmahioAPI.Create(\"API_KEY\"); client.Messages.OnMessage += (sender, args) => { var message = args.Message; if (message.ServerVariables == null) message.ServerVariables = new List- (); message.ServerVariables.Add(new Item(\"X-FORWARDED-FOR\", userIp)); };","title":"How to get elmah.io to resolve the correct client IP"},{"location":"how-to-get-elmah-io-to-resolve-the-correct-client-ip/#how-to-get-elmahio-to-resolve-the-correct-client-ip","text":"elmah.io try to resolve the IP of the client causing a log message, no matter what severity (Error, Information, etc.) and platform (browser, web-server, etc.) a log message is sent from. This is done by looking at multiple pieces of information provided by the sender. In some cases, elmah.io may not be able to resolve the IP or resolve a wrong IP address. In this document, you will find help getting the right client IP into elmah.io.","title":"How to get elmah.io to resolve the correct client IP"},{"location":"how-to-get-elmah-io-to-resolve-the-correct-client-ip/#missing-ip-when-using-a-proxy","text":"If you are using a proxy layer in between the client and your web server, you may experience log messages without a client IP. This is probably caused by the proxy hiding the original IP from your web server. Most proxies offer an alternative server variable like the X-Forwarded-For header. You can inspect the server variables on the Server Variables tab on elmah.io and check if your proxy includes the original IP in any of the variables. We support custom headers from a range of proxies (like Cloudflare). Most proxies support some kind of settings area where the X-Forwarded-For header can be enabled. If you are using a proxy that uses custom headers, please make sure to reach out and we may want to include the custom header to elmah.io.","title":"Missing IP when using a proxy"},{"location":"how-to-get-elmah-io-to-resolve-the-correct-client-ip/#wrong-ip-when-using-a-proxy","text":"If the client IP is wrong when behind a proxy, it is typically because the proxy replaces the client IP when calling your server with the IP of its server. This is a poor practice and makes it very hard for elmah.io to figure out which IP belongs to the user and which one to the proxy. Luckily, this is configurable in a lot of proxies through their settings area.","title":"Wrong IP when using a proxy"},{"location":"how-to-get-elmah-io-to-resolve-the-correct-client-ip/#missing-ip","text":"This can be caused by several issues. In most instances, the client doesn't include any server variables that reveal the IP address. In this case, the client IP will not be available within the elmah.io UI. In some cases, you may know the user's IP from session variables or similar. To include an IP on messages logged to elmah.io, you can implement the OnMessage event or action, depending on which integration you are using. In this example, we use the OnMessage event on the Elmah.Io.Client package to include the user's IP manually: var userIp = \"1.1.1.1\"; // <-- just for the demo var client = ElmahioAPI.Create(\"API_KEY\"); client.Messages.OnMessage += (sender, args) => { var message = args.Message; if (message.ServerVariables == null) message.ServerVariables = new List
- (); message.ServerVariables.Add(new Item(\"X-FORWARDED-FOR\", userIp)); };","title":"Missing IP"},{"location":"how-to-get-the-sql-tab-to-show-up/","text":"How to get the SQL tab to show up The elmah.io UI can offer to show any SQL code part of the current context of logging a message. The code will show up in the log message details as a tab named SQL : The tab shows a formatted view of the SQL code including any parameters and/or syntax errors. This can help debug exceptions thrown while executing SQL code against a relational database. To make the SQL tab show up, custom information needs to be included in the Data dictionary of a log message. The following sections will go into detail on how to include the custom information in various ways. Entity Framework Core Entity Framework Core is easy since it already includes any SQL code as part of the messages logged through Microsoft.Extensions.Logging's ILogger . The SQL code and parameters are logged as two properties named commandText and parameters . elmah.io will automatically pick up these properties and show the SQL tab with the formatted results. As a default, all values in parameters are not logged as part of the message. You will see this from values being set to ? in the UI. To have the real values show up, you will need to enable sensitive data logging when setting up EF Core: services.AddDbContext
(options => { // Other code like: options.UseSqlServer(connectionString); options.EnableSensitiveDataLogging(true); // \u2b05\ufe0f Set this to true }); This should not be set if you include sensitive details like social security numbers, passwords, and similar as SQL query parameters. Manually If you want to attach SQL to a log message made manually, you can go one of two ways. The first way is to fill in the commandText and parameters Data entries shown above. When creating a message on Elmah.Io.Client it could look like this: client.Messages.CreateAndNotify(logId, new CreateMessage { Title = \"Log message with SQL attached\", Severity = Severity.Error.ToString(), Data = new List- { new Item { Key = \"commandText\", Value = \"SELECT * FROM USERS WHERE Name = @columnValue OR Name = @columnTwoValue\" }, new Item { Key = \"parameters\", Value = \"columnValue='Eduard' (Nullable = false) (Size = 6), columnTwoValue='Thomas' (Nullable = false) (Size = 6)\" }, }, }); The value of the parameters item needs to correspond to the format of that Entity Framework and the System.Data namespace uses. The second approach is to provide elmah.io with a single Data item named X-ELMAHIO-SQL . The value of this item should be a JSON format as seen in the following example: var sql = new { Raw = \"SELECT * FROM USERS WHERE Name = @columnValue OR Name = @columnTwoValue\", Parameters = new[] { new { IsNullable = false, Size = 6, Name = \"columnValue\", Value = \"Eduard\" }, new { IsNullable = false, Size = 6, Name = \"columnTwoValue\", Value = \"Thomas\" }, }, }; client.Messages.CreateAndNotify(logId, new CreateMessage { Title = \"Log message with SQL attached\", Severity = Severity.Error.ToString(), Data = new List
- { new Item { Key = \"X-ELMAHIO-SQL\", Value = JsonConvert.SerializeObject(sql) }, }, }); The JSON generated by serializing the anonymous object will look like this: { \"Raw\": \"SELECT * FROM USERS WHERE Name = @columnValue OR Name = @columnTwoValue\", \"Parameters\": [ { \"IsNullable\": false, \"Size\": 6, \"Name\": \"columnValue\", \"Value\": \"Eduard\" }, { \"IsNullable\": false, \"Size\": 6, \"Name\": \"columnTwoValue\", \"Value\": \"Thomas\" } ] }","title":"How to get the SQL tab to show up"},{"location":"how-to-get-the-sql-tab-to-show-up/#how-to-get-the-sql-tab-to-show-up","text":"The elmah.io UI can offer to show any SQL code part of the current context of logging a message. The code will show up in the log message details as a tab named SQL : The tab shows a formatted view of the SQL code including any parameters and/or syntax errors. This can help debug exceptions thrown while executing SQL code against a relational database. To make the SQL tab show up, custom information needs to be included in the Data dictionary of a log message. The following sections will go into detail on how to include the custom information in various ways.","title":"How to get the SQL tab to show up"},{"location":"how-to-get-the-sql-tab-to-show-up/#entity-framework-core","text":"Entity Framework Core is easy since it already includes any SQL code as part of the messages logged through Microsoft.Extensions.Logging's ILogger . The SQL code and parameters are logged as two properties named commandText and parameters . elmah.io will automatically pick up these properties and show the SQL tab with the formatted results. As a default, all values in parameters are not logged as part of the message. You will see this from values being set to ? in the UI. To have the real values show up, you will need to enable sensitive data logging when setting up EF Core: services.AddDbContext
(options => { // Other code like: options.UseSqlServer(connectionString); options.EnableSensitiveDataLogging(true); // \u2b05\ufe0f Set this to true }); This should not be set if you include sensitive details like social security numbers, passwords, and similar as SQL query parameters.","title":"Entity Framework Core"},{"location":"how-to-get-the-sql-tab-to-show-up/#manually","text":"If you want to attach SQL to a log message made manually, you can go one of two ways. The first way is to fill in the commandText and parameters Data entries shown above. When creating a message on Elmah.Io.Client it could look like this: client.Messages.CreateAndNotify(logId, new CreateMessage { Title = \"Log message with SQL attached\", Severity = Severity.Error.ToString(), Data = new List- { new Item { Key = \"commandText\", Value = \"SELECT * FROM USERS WHERE Name = @columnValue OR Name = @columnTwoValue\" }, new Item { Key = \"parameters\", Value = \"columnValue='Eduard' (Nullable = false) (Size = 6), columnTwoValue='Thomas' (Nullable = false) (Size = 6)\" }, }, }); The value of the parameters item needs to correspond to the format of that Entity Framework and the System.Data namespace uses. The second approach is to provide elmah.io with a single Data item named X-ELMAHIO-SQL . The value of this item should be a JSON format as seen in the following example: var sql = new { Raw = \"SELECT * FROM USERS WHERE Name = @columnValue OR Name = @columnTwoValue\", Parameters = new[] { new { IsNullable = false, Size = 6, Name = \"columnValue\", Value = \"Eduard\" }, new { IsNullable = false, Size = 6, Name = \"columnTwoValue\", Value = \"Thomas\" }, }, }; client.Messages.CreateAndNotify(logId, new CreateMessage { Title = \"Log message with SQL attached\", Severity = Severity.Error.ToString(), Data = new List