├── .gitattributes ├── .gitignore ├── JSNLog.sln ├── LICENSE.md ├── LICENSE.md.template ├── README.md ├── jsnlog.strongname.snk └── jsnlog ├── Constants.cs ├── Infrastructure ├── AspNet5 │ └── HttpExtensions.cs ├── ConfigProcessor.cs ├── ContextWrapper │ ├── ContextWrapperCommon.cs │ ├── ContextWrapperExtensions.cs │ └── CoreContextWrapper.cs ├── Generated.cs ├── Generated.cs.template ├── HostingHelpers.cs ├── HtmlHelpers.cs ├── HttpHelpers.cs ├── JavaScriptHelpers.cs ├── LevelUtils.cs ├── LogMessageHelpers.cs ├── LoggerRequestHelpers.cs ├── LoggingUrlHelpers.cs ├── RequestId.cs ├── ResponseStreamWrapper.cs ├── ScriptInjectionHelper.cs ├── SiteConstants.cs └── Utils.cs ├── JSNLog.ClassLibrary.nuspec ├── JSNLog.nuspec ├── LogHandling ├── LogRequest.cs ├── LogRequestBase.cs ├── LogResponse.cs └── LoggerProcessor.cs ├── NuGet ├── icon.png └── readme.txt ├── Properties └── AssemblyInfo.cs ├── PublicFacing ├── AspNet5 │ ├── Configuration │ │ ├── LoggingAdapter.cs │ │ └── Middleware │ │ │ └── ApplicationBuilderExtensions.cs │ ├── LogRequestHandling │ │ └── Middleware │ │ │ └── JSNLogMiddleware.cs │ └── TagHelpers │ │ └── JlJavascriptLoggerDefinitionsTagHelper.cs ├── Configuration │ ├── FinalLogData.cs │ ├── ILogRequest.cs │ ├── ILoggingAdapter.cs │ ├── JavascriptLogging.cs │ ├── JsnlogConfiguration │ │ ├── AjaxAppender.cs │ │ ├── Appender.cs │ │ ├── ConsoleAppender.cs │ │ ├── FilterOptions.cs │ │ ├── ICanCreateElement.cs │ │ ├── ICanCreateJsonFields.cs │ │ ├── JsnlogConfiguration.cs │ │ ├── Logger.cs │ │ └── OnceOnlyOptions.cs │ ├── LoggingEventArgs.cs │ └── LoggingHandler.cs ├── Exceptions │ ├── ConfigurationException.cs │ ├── ConflictingConfigException.cs │ ├── GeneralAppenderException.cs │ ├── InvalidAttributeException.cs │ ├── JSNLogException.cs │ ├── MissingAttributeException.cs │ ├── MissingRootTagException.cs │ ├── PropertyException.cs │ ├── UnknownAppenderException.cs │ ├── UnknownRootTagException.cs │ └── WebConfigException.cs └── Level.cs ├── ValueInfos ├── AppendersValue.cs ├── IValueInfo.cs ├── LevelValue.cs ├── StringValue.cs └── UrlValue.cs ├── jsnlog.csproj ├── jsnlog.strongname.PublicKey └── jsnlog.strongname.snk /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## NuGet packages 37 | .nupkg 38 | .vs 39 | UpgradeLog.htm 40 | ## Ignore Visual Studio temporary files, build results, and 41 | ## files generated by popular Visual Studio add-ons. 42 | 43 | project.lock.json 44 | 45 | # User-specific files 46 | *.suo 47 | *.user 48 | *.sln.docstates 49 | 50 | # Build results 51 | [Dd]ebug/ 52 | [Rr]elease/ 53 | *_i.c 54 | *_p.c 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.vspscc 69 | .builds 70 | *.dotCover 71 | 72 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 73 | packages/ 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opensdf 80 | *.sdf 81 | 82 | # Visual Studio profiler 83 | *.psess 84 | *.vsp 85 | 86 | # ReSharper is a .NET coding add-in 87 | _ReSharper* 88 | 89 | # Installshield output folder 90 | [Ee]xpress 91 | 92 | # DocProject is a documentation generator add-in 93 | DocProject/buildhelp/ 94 | DocProject/Help/*.HxT 95 | DocProject/Help/*.HxC 96 | DocProject/Help/*.hhc 97 | DocProject/Help/*.hhk 98 | DocProject/Help/*.hhp 99 | DocProject/Help/Html2 100 | DocProject/Help/html 101 | 102 | # Click-Once directory 103 | publish 104 | 105 | # Others 106 | [Bb]in 107 | [Oo]bj 108 | sql 109 | TestResults 110 | *.Cache 111 | ClientBin 112 | stylecop.* 113 | ~$* 114 | *.dbmdl 115 | Generated_Code #added for RIA/Silverlight projects 116 | 117 | # Backup & report files from converting an old project file to a newer 118 | # Visual Studio version. Backup files are not needed, because we have git ;-) 119 | _UpgradeReport_Files/ 120 | Backup*/ 121 | UpgradeLog*.XML 122 | 123 | 124 | 125 | ############ 126 | ## Windows 127 | ############ 128 | 129 | # Windows image file caches 130 | Thumbs.db 131 | 132 | # Folder config file 133 | Desktop.ini 134 | 135 | 136 | ############# 137 | ## Python 138 | ############# 139 | 140 | *.py[co] 141 | 142 | # Packages 143 | *.egg 144 | *.egg-info 145 | dist 146 | build 147 | eggs 148 | parts 149 | bin 150 | var 151 | sdist 152 | develop-eggs 153 | .installed.cfg 154 | 155 | # Installer logs 156 | pip-log.txt 157 | 158 | # Unit test / coverage reports 159 | .coverage 160 | .tox 161 | 162 | #Translations 163 | *.mo 164 | 165 | #Mr Developer 166 | .mr.developer.cfg 167 | 168 | # Mac crap 169 | .DS_Store 170 | 171 | # Included files 172 | !chromedriver.exe 173 | 174 | background 175 | 176 | NuGet/content/Scripts/jsnlog.js 177 | NuGet/content/Scripts/jsnlog.js.map 178 | NuGet/content/Scripts/jsnlog.min.js 179 | 180 | 181 | -------------------------------------------------------------------------------- /JSNLog.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31702.278 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "jsnlog", "jsnlog\jsnlog.csproj", "{11B79AEC-E8C7-4E36-BBE9-ED1496336E3D}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {11B79AEC-E8C7-4E36-BBE9-ED1496336E3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {11B79AEC-E8C7-4E36-BBE9-ED1496336E3D}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {11B79AEC-E8C7-4E36-BBE9-ED1496336E3D}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {11B79AEC-E8C7-4E36-BBE9-ED1496336E3D}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {31A58FC8-E407-41F3-A7EE-A19303059A91} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2024 Mattijs Perdeck 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /LICENSE.md.template: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-__YEAR__ Mattijs Perdeck 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSNLog 2 | 3 | This JavaScript logging package logs JavaScript exceptions, AJAX timeouts and other *client* side events in your *server* side log. 4 | 5 | Editions for: 6 | 7 | * **.Net Core** - logs client side events to loggers added during startup ([Documentation](http://jsnlog.com?version=netjs); [NuGet](https://www.nuget.org/packages/JSNLog/)) 8 | 9 | * **.Net Framework** - logs client side events to Elmah, Log4Net, NLog, Serilog and Common.Logging ([Documentation](http://jsnlog.com?version=netframeworkjs); [NuGet](https://www.nuget.org/packages?q=jsnlog)) 10 | 11 | * **Node.Js** - logs both client side and server side events to any Winston transport ([Documentation](http://jsnlog.com?version=nodejs); [npm (client side)](https://www.npmjs.com/package/jsnlog); [npm (server side)](https://www.npmjs.com/package/jsnlog-nodejs)). 12 | 13 | * **Plain JavaScript** - logs client side events to a server ([Documentation](http://jsnlog.com?version=js); [npm](https://www.npmjs.com/package/jsnlog)). 14 | 15 | [License: MIT](https://github.com/mperdeck/jsnlog/blob/master/LICENSE.md) 16 | 17 | -------------------------------------------------------------------------------- /jsnlog.strongname.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mperdeck/jsnlog/bc6cfc091c4baa1574bfdc680baba7621fd69164/jsnlog.strongname.snk -------------------------------------------------------------------------------- /jsnlog/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using JSNLog.Infrastructure; 6 | using JSNLog.ValueInfos; 7 | 8 | namespace JSNLog 9 | { 10 | // make Constants public, so the web site project can access this info 11 | public class Constants 12 | { 13 | public const string PackageName = "JSNLog"; 14 | public const string ContextItemRequestIdName = "__JSNLog_RequestId"; 15 | public const string HttpHeaderRequestIdName = "JSNLog-RequestId"; 16 | public const string HttpHeaderXForwardedFor = "X-Forwarded-For"; 17 | public const string GlobalMethodCalledAfterJsnlogJsLoaded = "__jsnlog_configure"; 18 | public const string ConfigRootName = "jsnlog"; 19 | public const string RegexBool = "^(true|false)$"; 20 | public const string RegexPositiveInteger = "^[0-9]+$"; 21 | public const string RegexIntegerGreaterZero = "^[1-9][0-9]*$"; 22 | public const string RegexUrl = @"^[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\-._~:/?#[\]@!$&'()*+,;=]+$"; 23 | public const char AppenderNameSeparator = ';'; 24 | public const string RootLoggerNameServerSide = "ClientRoot"; // Cannot use empty logger name server side, so use this instead. 25 | public const string JSNLogInternalErrorLoggerName = "JSNLogInternalError"; // Used when logging internal errors to Common.Logging 26 | public const int CorsAccessControlMaxAgeInSeconds = 3600; // Note that many browsers enforce their own max age, so you may not get 3600 seconds. 27 | 28 | // The default for the defaultAjaxUrl attribute. 29 | // It starts with a ~, which must be resolved. Issue is that in most sites, the home page is at 30 | // http://domain.com 31 | // in which case you want the default to be /jsnlog.logger. 32 | // 33 | // However, the user could put the site in a Virtual Directory that they made into an Application, resulting 34 | // in a home page at 35 | // http://domain.com/virtdir 36 | // In that case, the default Ajax url has to be /virtdir/jsnlog.logger 37 | 38 | public const string DefaultDefaultAjaxUrl = "~/jsnlog.logger"; 39 | 40 | // There is no RegexLevels, because that regex gets generated by a method in LevelUtils 41 | 42 | public const string JsLogObjectName = "JL"; 43 | public const string JsLogObjectClientIpOption = "clientIP"; 44 | public const string JsLogObjectRequestIdOption = "requestId"; 45 | public const string JsAppenderVariablePrefix = "a"; 46 | public const string JsLoggerVariablePrefix = "logger"; 47 | 48 | public static readonly Level DefaultAppenderLevel = Level.TRACE; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/AspNet5/HttpExtensions.cs: -------------------------------------------------------------------------------- 1 | // AspNetCore is set in the .csproj file, based on an MSBuild property set in the build script 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNetCore.Http; 9 | 10 | namespace JSNLog.Infrastructure.AspNet5 11 | { 12 | public static class HttpExtensions 13 | { 14 | // This method copied from 15 | // https://github.com/aspnet/HttpAbstractions/blob/dev/src/Microsoft.AspNet.Http.Extensions/UriHelper.cs 16 | // Once you can install package Microsoft.AspNet.Http.Extensions again, you can use the version in there. 17 | 18 | private const string SchemeDelimiter = "://"; 19 | public static string GetDisplayUrl(this HttpRequest request) 20 | { 21 | var host = request.Host.Value; 22 | var pathBase = request.PathBase.Value; 23 | var path = request.Path.Value; 24 | var queryString = request.QueryString.Value; 25 | 26 | // PERF: Calculate string length to allocate correct buffer size for StringBuilder. 27 | var length = request.Scheme.Length + SchemeDelimiter.Length + (host?.Length ?? 0) 28 | + pathBase.Length + path.Length + queryString.Length; 29 | 30 | return new StringBuilder(length) 31 | .Append(request.Scheme) 32 | .Append(SchemeDelimiter) 33 | .Append(host) 34 | .Append(pathBase) 35 | .Append(path) 36 | .Append(queryString) 37 | .ToString(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/ConfigProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using JSNLog.ValueInfos; 5 | 6 | namespace JSNLog.Infrastructure 7 | { 8 | internal class ConfigProcessor 9 | { 10 | /// 11 | /// Processes a configuration (such as the contents of the jsnlog element in web.config). 12 | /// 13 | /// The configuration is processed into JavaScript, which configures the jsnlog client side library. 14 | /// 15 | /// 16 | /// requestId to be passed to the JSNLog library when setting its options. 17 | /// Could be null (when user didn't provide a request id). 18 | /// In that case, this method creates a request id itself. 19 | /// 20 | /// 21 | /// All JavaScript needs to be written to this string builder. 22 | /// 23 | public void ProcessRoot(string requestId, StringBuilder sb, string userIp) 24 | { 25 | ProcessRootExec(sb, HostingHelpers.VirtualToAbsolutePath, userIp, requestId, true); 26 | } 27 | 28 | // This version is not reliant on sitting in a web site, so can be unit tested. 29 | // generateClosure - if false, no function closure is generated around the generated JS code. Only set to false when unit testing. 30 | // Doing this assumes that jsnlog.js has loaded before the code generated by the method is executed. 31 | // 32 | // You want to set this to false during unit testing, because then you need direct access outside the closure of variables 33 | // that are private to the closure, specifically dummyappenders that store the log messages you receive, so you can unit test them. 34 | public void ProcessRootExec(StringBuilder sb, Func virtualToAbsoluteFunc, 35 | string userIp, string requestId, bool generateClosure) 36 | { 37 | Dictionary appenderNames = new Dictionary(); 38 | JsnlogConfiguration jsnlogConfiguration = JavascriptLogging.GetJsnlogConfiguration(); 39 | 40 | string loggerProductionLibraryVirtualPath = jsnlogConfiguration.productionLibraryPath; 41 | bool loggerEnabled = jsnlogConfiguration.enabled; 42 | 43 | string loggerProductionLibraryPath = null; 44 | if (!string.IsNullOrEmpty(loggerProductionLibraryVirtualPath)) 45 | { 46 | // Every hard coded path must be resolved. See the declaration of DefaultDefaultAjaxUrl 47 | loggerProductionLibraryPath = Utils.AbsoluteUrl(loggerProductionLibraryVirtualPath, virtualToAbsoluteFunc); 48 | } 49 | 50 | JavaScriptHelpers.WriteJavaScriptBeginTag(sb); 51 | if (generateClosure) 52 | { 53 | JavaScriptHelpers.WriteLine(string.Format("var {0} = function ({1}) {{", Constants.GlobalMethodCalledAfterJsnlogJsLoaded, Constants.JsLogObjectName), sb); 54 | } 55 | 56 | // Generate setOptions for JSNLog object itself 57 | 58 | var jsonFields = new List(); 59 | JavaScriptHelpers.AddJsonField(jsonFields, Constants.JsLogObjectClientIpOption, userIp, new StringValue()); 60 | JavaScriptHelpers.AddJsonField(jsonFields, Constants.JsLogObjectRequestIdOption, requestId, new StringValue()); 61 | 62 | JavaScriptHelpers.GenerateSetOptions(Constants.JsLogObjectName, jsnlogConfiguration, 63 | appenderNames, virtualToAbsoluteFunc, sb, jsonFields); 64 | 65 | if (loggerEnabled) 66 | { 67 | // Process all loggers and appenders. First process the appenders, because the loggers can be 68 | // dependent on the appenders, and will use appenderNames to translate configuration appender names 69 | // to JavaScript names. 70 | 71 | int sequence = 0; 72 | 73 | GenerateCreateJavaScript(jsnlogConfiguration.ajaxAppenders, sb, virtualToAbsoluteFunc, appenderNames, ref sequence); 74 | GenerateCreateJavaScript(jsnlogConfiguration.consoleAppenders, sb, virtualToAbsoluteFunc, appenderNames, ref sequence); 75 | GenerateCreateJavaScript(jsnlogConfiguration.loggers, sb, virtualToAbsoluteFunc, appenderNames, ref sequence); 76 | } 77 | 78 | // ------------- 79 | 80 | if (generateClosure) 81 | { 82 | // Generate code to execute the function, in case jsnlog.js has already been loaded. 83 | // Wrap in try catch, so if jsnlog.js hasn't been loaded, the resulting exception will be swallowed. 84 | JavaScriptHelpers.WriteLine(string.Format("}}; try {{ {0}({1}); }} catch(e) {{}};", Constants.GlobalMethodCalledAfterJsnlogJsLoaded, Constants.JsLogObjectName), sb); 85 | } 86 | JavaScriptHelpers.WriteJavaScriptEndTag(sb); 87 | 88 | // Write the script tag that loads jsnlog.js after the code generated from the web.config. 89 | // When using jsnlog.js as an AMD module or in a bundle, jsnlog.js will be loaded after that code as well, 90 | // and creating a similar situation in the default out of the box loading option makes it more likely 91 | // you pick up bugs during testing. 92 | if (!string.IsNullOrWhiteSpace(loggerProductionLibraryPath)) 93 | { 94 | JavaScriptHelpers.WriteScriptTag(loggerProductionLibraryPath, sb); 95 | } 96 | } 97 | 98 | /// 99 | /// Generates JavaScript code for all passed in elements. 100 | /// 101 | /// 102 | /// 103 | /// The JavaScript code is added here. 104 | /// 105 | /// 106 | /// 107 | /// 108 | /// When the method is called, this number is not used with any element. 109 | /// When the method returns, it ensures that a number is returned that is not used with any element. 110 | /// 111 | private void GenerateCreateJavaScript(IEnumerable elements, StringBuilder sb, 112 | Func virtualToAbsoluteFunc, Dictionary appenderNames, ref int sequence) 113 | { 114 | if (elements == null) { return; } 115 | 116 | foreach(var element in elements) 117 | { 118 | element.CreateElement(sb, appenderNames, sequence, virtualToAbsoluteFunc); 119 | sequence++; 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/ContextWrapper/ContextWrapperCommon.cs: -------------------------------------------------------------------------------- 1 | using JSNLog; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace JSNLog 9 | { 10 | public abstract class ContextWrapperCommon 11 | { 12 | public abstract string GetRequestUserIp(); 13 | public abstract string GetRequestHeader(string requestHeaderName); 14 | public abstract string GetRequestIdFromContext(); 15 | public abstract void SetRequestIdInContext(string requestId); 16 | public abstract string IISRequestId(); 17 | 18 | public string GetUserIp() 19 | { 20 | string userIp = GetRequestUserIp(); 21 | 22 | string xForwardedFor = GetRequestHeader(Constants.HttpHeaderXForwardedFor); 23 | if (!string.IsNullOrEmpty(xForwardedFor)) 24 | { 25 | userIp = xForwardedFor + ", " + userIp; 26 | } 27 | 28 | return userIp; 29 | } 30 | 31 | /// 32 | /// Gets the request id from an HTTP header in the request. 33 | /// Every log request sent by jsnlog.js should have such a header. 34 | /// However, requests not sent by jsnlog.js will not have this header obviously. 35 | /// 36 | /// If the request id cannot be found, returns null. 37 | /// 38 | /// 39 | public string GetLogRequestId() 40 | { 41 | return GetRequestHeader(Constants.HttpHeaderRequestIdName); 42 | } 43 | 44 | /// 45 | /// Gets an id, that is unique to this request. 46 | /// That is, for the same request, this method always returns the same string. 47 | /// 48 | /// 49 | public string GetRequestId() 50 | { 51 | string requestId = GetRequestIdFromContext(); 52 | 53 | if (requestId == null) 54 | { 55 | requestId = IISRequestId(); 56 | 57 | if (requestId == null) 58 | { 59 | requestId = CreateNewRequestId(); 60 | } 61 | 62 | SetRequestIdInContext(requestId); 63 | } 64 | 65 | return requestId; 66 | } 67 | 68 | public string RequestId() 69 | { 70 | string requestId = GetLogRequestId(); 71 | 72 | // If requestId is empty string, regard that as a valid requestId. 73 | // jsnlog.js will send such request ids when the request id has not been 74 | // set. In that case, you don't want to generate a new request id for 75 | // a log request, because that would be confusing. 76 | if (requestId == null) 77 | { 78 | requestId = GetRequestId(); 79 | } 80 | 81 | return requestId; 82 | } 83 | 84 | private string CreateNewRequestId() 85 | { 86 | return Guid.NewGuid().ToString(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/ContextWrapper/ContextWrapperExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace JSNLog 8 | { 9 | public static class ContextWrapperExtensions 10 | { 11 | public static CoreContextWrapper Wrapper(this Microsoft.AspNetCore.Http.HttpContext httpContext) 12 | { 13 | return new CoreContextWrapper(httpContext); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/ContextWrapper/CoreContextWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using JSNLog.Infrastructure; 7 | using Microsoft.AspNetCore.Http; 8 | 9 | namespace JSNLog 10 | { 11 | public class CoreContextWrapper : ContextWrapperCommon 12 | { 13 | HttpContext _httpContext; 14 | 15 | public CoreContextWrapper(HttpContext httpContext) 16 | { 17 | _httpContext = httpContext; 18 | } 19 | 20 | public override string GetRequestUserIp() 21 | { 22 | return Utils.SafeToString(_httpContext.Connection.RemoteIpAddress); 23 | } 24 | 25 | public override string GetRequestHeader(string requestHeaderName) 26 | { 27 | var headers = _httpContext.Request.Headers; 28 | return headers[requestHeaderName]; 29 | } 30 | 31 | public override string GetRequestIdFromContext() 32 | { 33 | return _httpContext.TraceIdentifier; 34 | } 35 | 36 | public override void SetRequestIdInContext(string requestId) 37 | { 38 | _httpContext.TraceIdentifier = requestId; 39 | } 40 | 41 | 42 | /// 43 | /// Creates an ID that is unique hopefully. 44 | /// 45 | /// This method initially tries to use the request id that IIS already uses internally. This allows us to correlate across even more log files. 46 | /// If this fails, for example if this is not part of a web request, than it uses a random GUID. 47 | /// 48 | /// See 49 | /// http://blog.tatham.oddie.com.au/2012/02/07/code-request-correlation-in-asp-net/ 50 | /// 51 | /// 52 | public override string IISRequestId() 53 | { 54 | // Core versions always return null 55 | return null; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/Generated.cs: -------------------------------------------------------------------------------- 1 | // Generated.cs gets copied from Generated.cs.template 2 | 3 | namespace JSNLog.Infrastructure 4 | { 5 | public static class Generated 6 | { 7 | public static string Version { get { return "3.0.3"; } } 8 | public static string FrameworkVersion { get { return "2.30.0"; } } 9 | public static string JSNLogJsVersion { get { return "2.30.0"; } } 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/Generated.cs.template: -------------------------------------------------------------------------------- 1 | // Generated.cs gets copied from Generated.cs.template 2 | 3 | namespace JSNLog.Infrastructure 4 | { 5 | public static class Generated 6 | { 7 | public static string Version { get { return "__Version__"; } } 8 | public static string FrameworkVersion { get { return "__FrameworkVersion__"; } } 9 | public static string JSNLogJsVersion { get { return "__JSNLogJsVersion__"; } } 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/HostingHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace JSNLog.Infrastructure 7 | { 8 | public class HostingHelpers 9 | { 10 | public static string VirtualToAbsolutePath(string virtualPath) 11 | { 12 | //TODO: virtual path translation for DNX. 13 | // Probably get an instance of IApplicationEnvironment and then get the info from there. 14 | // If the application is in a virtual directory, 15 | // ~/myfile.jpg may have absolute url /myapp/myfile.jpg. 16 | // See 17 | // http://stackoverflow.com/questions/28082869/how-to-map-virtual-path-to-physical-path 18 | // http://stackoverflow.com/questions/33253608/asp-net-5-beta8-app-with-virtual-directories-applications 19 | // http://stackoverflow.com/questions/32631066/how-to-consistently-get-application-base-path-for-asp-net-5-dnx-project-on-both 20 | // http://stackoverflow.com/questions/30111920/how-do-i-access-the-iapplicationenvironment-from-a-unit-test 21 | 22 | // For now, just remove ~ from the left of the url 23 | return virtualPath.TrimStart('~'); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/HtmlHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace JSNLog.Infrastructure 5 | { 6 | public class HtmlHelpers 7 | { 8 | public static string JavaScriptStringEncode(string value, bool addDoubleQuotes) 9 | { 10 | #if NETFRAMEWORK 11 | return System.Web.HttpUtility.JavaScriptStringEncode(value, addDoubleQuotes); 12 | #else 13 | // copied from https://github.com/mono/mono/blob/master/mcs/class/System.Web/System.Web/HttpUtility.cs 14 | 15 | if (String.IsNullOrEmpty(value)) 16 | return addDoubleQuotes ? "\"\"" : String.Empty; 17 | 18 | int len = value.Length; 19 | bool needEncode = false; 20 | char c; 21 | for (int i = 0; i < len; i++) 22 | { 23 | c = value[i]; 24 | 25 | if (c >= 0 && c <= 31 || c == 34 || c == 39 || c == 60 || c == 62 || c == 92) 26 | { 27 | needEncode = true; 28 | break; 29 | } 30 | } 31 | 32 | if (!needEncode) 33 | return addDoubleQuotes ? "\"" + value + "\"" : value; 34 | 35 | var sb = new StringBuilder(); 36 | if (addDoubleQuotes) 37 | sb.Append('"'); 38 | 39 | for (int i = 0; i < len; i++) 40 | { 41 | c = value[i]; 42 | if (c >= 0 && c <= 7 || c == 11 || c >= 14 && c <= 31 || c == 39 || c == 60 || c == 62) 43 | sb.AppendFormat("\\u{0:x4}", (int)c); 44 | else switch ((int)c) 45 | { 46 | case 8: 47 | sb.Append("\\b"); 48 | break; 49 | 50 | case 9: 51 | sb.Append("\\t"); 52 | break; 53 | 54 | case 10: 55 | sb.Append("\\n"); 56 | break; 57 | 58 | case 12: 59 | sb.Append("\\f"); 60 | break; 61 | 62 | case 13: 63 | sb.Append("\\r"); 64 | break; 65 | 66 | case 34: 67 | sb.Append("\\\""); 68 | break; 69 | 70 | case 92: 71 | sb.Append("\\\\"); 72 | break; 73 | 74 | default: 75 | sb.Append(c); 76 | break; 77 | } 78 | } 79 | 80 | if (addDoubleQuotes) 81 | sb.Append('"'); 82 | 83 | return sb.ToString(); 84 | #endif 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/HttpHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace JSNLog.Infrastructure 8 | { 9 | internal static class HttpHelpers 10 | { 11 | private static Regex _regex = new Regex(@";\s*charset=(?[^\s;]+)"); 12 | 13 | public static Encoding GetEncoding(string contentType) 14 | { 15 | if (string.IsNullOrEmpty(contentType)) 16 | { 17 | return Encoding.UTF8; 18 | } 19 | 20 | string charset = "utf-8"; 21 | var match = _regex.Match(contentType); 22 | if (match.Success) 23 | { 24 | charset = match.Groups["charset"].Value; 25 | } 26 | 27 | try 28 | { 29 | return Encoding.GetEncoding(charset); 30 | } 31 | catch 32 | { 33 | return Encoding.UTF8; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/JavaScriptHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Xml; 6 | using JSNLog.ValueInfos; 7 | using JSNLog.Exceptions; 8 | 9 | namespace JSNLog.Infrastructure 10 | { 11 | internal class JavaScriptHelpers 12 | { 13 | public static void WriteScriptTag(string url, StringBuilder sb) 14 | { 15 | sb.AppendLine(""); 16 | } 17 | 18 | public static void WriteJavaScriptBeginTag(StringBuilder sb) 19 | { 20 | sb.AppendLine(""); 28 | } 29 | 30 | public static void WriteLine(string content, StringBuilder sb) 31 | { 32 | sb.AppendLine(content); 33 | } 34 | 35 | /// 36 | /// Generates the JavaScript to set options on an object 37 | /// 38 | /// 39 | /// Name of the JavaScript variable that holds the object. 40 | /// 41 | /// 42 | /// The object (logger, etc.) whose fields are to be converted to options. 43 | /// 44 | /// 45 | /// The JavaScript code is added to this StringBuilder. 46 | /// 47 | /// 48 | /// If not null, the fields in this array will be included in the JSON object passed to the setOptions method. 49 | /// 50 | internal static void GenerateSetOptions(string parentName, ICanCreateJsonFields element, 51 | Dictionary appenderNames, Func virtualToAbsoluteFunc, 52 | StringBuilder sb, IList initialJsonFields = null) 53 | { 54 | var jsonFields = new List(); 55 | 56 | if (initialJsonFields != null) 57 | { 58 | jsonFields.AddRange(initialJsonFields); 59 | } 60 | 61 | element.AddJsonFields(jsonFields, appenderNames, virtualToAbsoluteFunc); 62 | 63 | string setOptionsJS = string.Format("{0}.setOptions({{{1}}});", parentName, string.Join(",\n", jsonFields)); 64 | sb.AppendLine(setOptionsJS); 65 | } 66 | 67 | internal static string ToJavaScript(bool b) 68 | { 69 | if (b) { return "true"; } 70 | return "false"; 71 | } 72 | 73 | internal static string ToJavaScript(uint i) 74 | { 75 | return i.ToString(); 76 | } 77 | 78 | internal static string ToJavaScript(IEnumerable jsValues) 79 | { 80 | return "[" + string.Join(", ", jsValues) + "]"; 81 | } 82 | 83 | /// 84 | /// Creates a JSON field of the form: 85 | /// "field name": "field value" 86 | /// 87 | /// This string is added to jsonFields. 88 | /// 89 | /// If value is null or empty, nothing is added. 90 | /// 91 | /// 92 | /// 93 | /// Name of the field, without quotes. Will not be escaped. 94 | /// 95 | /// 96 | /// The unescaped value. 97 | /// 98 | /// 99 | /// Used to validate the value, and to convert it to proper JavaScript. 100 | /// 101 | internal static void AddJsonField(IList jsonFields, string jsonFieldName, string value, IValueInfo valueInfo) 102 | { 103 | // null - the string has not been set in the config 104 | // "" - the string has been set in the config to "" (a valid value) 105 | if (value == null) { return; } 106 | 107 | try 108 | { 109 | AddJsonField(jsonFields, jsonFieldName, valueInfo.ToJavaScript(value)); 110 | } 111 | catch (Exception e) 112 | { 113 | throw new PropertyException(jsonFieldName, e); 114 | } 115 | } 116 | 117 | internal static void AddJsonField(IList jsonFields, string jsonFieldName, uint value) 118 | { 119 | AddJsonField(jsonFields, jsonFieldName, ToJavaScript(value)); 120 | } 121 | 122 | internal static void AddJsonField(IList jsonFields, string jsonFieldName, bool value) 123 | { 124 | AddJsonField(jsonFields, jsonFieldName, ToJavaScript(value)); 125 | } 126 | 127 | // valueInfo will be applied to each individual string in value 128 | internal static void AddJsonField(IList jsonFields, string jsonFieldName, IEnumerable value, IValueInfo valueInfo) 129 | { 130 | try 131 | { 132 | // If an element in value is null, don't include it in the field. This can be used to create an empty array. 133 | AddJsonField(jsonFields, jsonFieldName, ToJavaScript(value.Where(v=>v != null).Select(v=>valueInfo.ToJavaScript(v)))); 134 | } 135 | catch (Exception e) 136 | { 137 | throw new PropertyException(jsonFieldName, e); 138 | } 139 | } 140 | 141 | internal static void AddJsonField(IList jsonFields, string jsonFieldName, string jsValue) 142 | { 143 | // Note: no quotes around {1}. If jsValue represents a string, it will already be quoted. 144 | string jsonField = string.Format("\"{0}\": {1}", jsonFieldName, jsValue); 145 | jsonFields.Add(jsonField); 146 | } 147 | 148 | /// 149 | /// Generates the JavaScript create an object. 150 | /// 151 | /// 152 | /// 153 | /// 154 | /// Name of the object as known to the user. For example the appender name. 155 | /// 156 | /// 157 | public static void GenerateCreate(string objectVariableName, string createMethodName, string name, StringBuilder sb) 158 | { 159 | JavaScriptHelpers.WriteLine(string.Format("var {0}=JL.{1}('{2}');", objectVariableName, createMethodName, name), sb); 160 | } 161 | 162 | /// 163 | /// Generate the JavaScript to create a logger. 164 | /// 165 | /// 166 | /// New logger object will be assigned to this JS variable. 167 | /// 168 | /// 169 | /// Name of the logger. Could be null (for the root logger). 170 | /// 171 | /// 172 | /// JS code will be appended to this. 173 | /// 174 | public static void GenerateLogger(string loggerVariableName, string loggerName, StringBuilder sb) 175 | { 176 | string quotedLoggerName = 177 | loggerName == null ? "" : @"""" + loggerName + @""""; 178 | JavaScriptHelpers.WriteLine(string.Format("var {0}=JL({1});", loggerVariableName, quotedLoggerName), sb); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/LevelUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog.Infrastructure 7 | { 8 | // make LevelUtils public, so the web site project can access this info 9 | public class LevelUtils 10 | { 11 | /// 12 | /// Converts a number to a level. 13 | /// 14 | /// Each level is associated with a value. If the number is equal to or lower than a level, but higher than the previous level, than 15 | /// that level is used. So if: 16 | /// 17 | /// TRACE = 1000, 18 | /// DEBUG = 2000, 19 | /// INFO = 3000, 20 | /// WARN = 4000, 21 | /// ERROR = 5000, 22 | /// FATAL = 6000 23 | /// 24 | /// And the number is: 2500, than this method returns INFO. 25 | /// 26 | /// If the number is greater than FATAL (highest level), than FATAL is returned. 27 | /// If the number is lower than TRACE, than TRACE is returned. 28 | /// 29 | /// This method assumes that the Level enum is sorted by value! 30 | /// 31 | /// 32 | /// 33 | public static Level IntToLevel(int i) 34 | { 35 | Array values = Enum.GetValues(typeof(Level)); 36 | int nbrItems = values.Length; 37 | 38 | for(int j = 0; j < nbrItems; j++) 39 | { 40 | int value = (int)values.GetValue(j); 41 | if (value >= i) 42 | { 43 | Level level = (Level)Enum.Parse(typeof(Level), value.ToString()); 44 | return level; 45 | } 46 | } 47 | 48 | // No level found. Return the highest level. 49 | return HighestLevel(); 50 | } 51 | 52 | /// 53 | /// Returns the highest level 54 | /// as given in Level enum. 55 | /// 56 | /// 57 | public static Level HighestLevel() 58 | { 59 | Array values = Enum.GetValues(typeof(Level)); 60 | int value = (int)values.GetValue(values.Length - 1); 61 | 62 | Level level = (Level)Enum.Parse(typeof(Level), value.ToString()); 63 | return level; 64 | } 65 | 66 | /// 67 | /// Parses a string with the name or value of a level. 68 | /// 69 | /// 70 | /// 71 | /// null if levelString is null. 72 | /// Otherwise, the actual level. 73 | /// 74 | public static Level? ParseLevel(string levelString) 75 | { 76 | if (levelString == null) 77 | { 78 | return null; 79 | } 80 | 81 | // See if levelString contains the name of a level. If so, Enum.Parse it. 82 | if (Enum.IsDefined(typeof(Level), levelString)) 83 | { 84 | Level level = (Level)Enum.Parse(typeof(Level), levelString); 85 | return level; 86 | } 87 | 88 | // If levelString contains a number, parse that 89 | int levelInt; 90 | bool isInt = int.TryParse(levelString, out levelInt); 91 | 92 | if (isInt) 93 | { 94 | return IntToLevel(levelInt); 95 | } 96 | 97 | throw new Exception(string.Format("Unknown level {0}", levelString)); 98 | } 99 | 100 | /// 101 | /// Returns the friendly name of the given level if possible. 102 | /// 103 | /// If level is a number matching one of the predefined levels, that level's name is returned. 104 | /// Otherwise, level is returned. 105 | /// 106 | /// 107 | /// 108 | public static string PredefinedName(string level) 109 | { 110 | int levelInt; 111 | if (!int.TryParse(level, out levelInt)) 112 | { 113 | return level; 114 | } 115 | 116 | Array values = Enum.GetValues(typeof(Level)); 117 | Array names = Enum.GetNames(typeof(Level)); 118 | int nbrItems = values.Length; 119 | 120 | for (int j = 0; j < nbrItems; j++) 121 | { 122 | int value = (int)values.GetValue(j); 123 | if (value == levelInt) 124 | { 125 | return (string)names.GetValue(j); 126 | } 127 | } 128 | 129 | return level; 130 | } 131 | 132 | /// 133 | /// Determines the numeric value of a level. 134 | /// If level is a number, returns the number. 135 | /// If level is a predefined level name, returns number corresponding to that level. 136 | /// Otherwise throws exception. 137 | /// 138 | /// 139 | /// 140 | public static int LevelNumber(string level) 141 | { 142 | int levelInt; 143 | if (int.TryParse(level, out levelInt)) 144 | { 145 | return levelInt; 146 | } 147 | 148 | // See if levelString contains the name of a level. If so, Enum.Parse it and returns its number. 149 | if (Enum.IsDefined(typeof(Level), level)) 150 | { 151 | Level levelEnum = (Level)Enum.Parse(typeof(Level), level); 152 | return (int)levelEnum; 153 | } 154 | 155 | throw new Exception(string.Format("Unknown level {0}", level)); 156 | } 157 | 158 | /// 159 | /// Throws exception if level is not a valid level. 160 | /// 161 | /// 162 | public static void ValidateLevel(string level) 163 | { 164 | if (string.IsNullOrEmpty(level)) { return; } 165 | 166 | LevelNumber(level); 167 | } 168 | 169 | /// 170 | /// Returns a string with all named levels, separated by | 171 | /// 172 | /// This is used by the web site project. 173 | /// 174 | /// 175 | public static string NamedLevels() 176 | { 177 | string[] names = Enum.GetNames(typeof(Level)); 178 | 179 | string namedLevels = string.Join("|", names); 180 | return namedLevels; 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/LogMessageHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | 5 | namespace JSNLog.Infrastructure 6 | { 7 | internal class LogMessageHelpers 8 | { 9 | public static T DeserializeJson(string json) 10 | { 11 | T result = JsonConvert.DeserializeObject(json); 12 | return result; 13 | } 14 | 15 | public static bool IsPotentialJson(string msg) 16 | { 17 | string trimmedMsg = msg.Trim(); 18 | return (trimmedMsg.StartsWith("{") && trimmedMsg.EndsWith("}")); 19 | } 20 | 21 | /// 22 | /// Tries to deserialize msg. 23 | /// If that works, returns the resulting object. 24 | /// Otherwise returns msg itself (which is a string). 25 | /// 26 | /// 27 | /// 28 | public static Object DeserializeIfPossible(string msg) 29 | { 30 | try 31 | { 32 | if (IsPotentialJson(msg)) 33 | { 34 | Object result = DeserializeJson(msg); 35 | return result; 36 | } 37 | } 38 | catch 39 | { 40 | } 41 | 42 | return msg; 43 | } 44 | 45 | /// 46 | /// Returns true if the msg contains a valid JSON string. 47 | /// 48 | /// 49 | /// 50 | public static bool IsJsonString(string msg) 51 | { 52 | try 53 | { 54 | if (IsPotentialJson(msg)) 55 | { 56 | // Try to deserialise the msg. If that does not throw an exception, 57 | // decide that msg is a good JSON string. 58 | 59 | DeserializeJson>(msg); 60 | 61 | return true; 62 | } 63 | } 64 | catch 65 | { 66 | } 67 | 68 | return false; 69 | } 70 | 71 | /// 72 | /// Takes a log message and finds out if it contains a valid JSON string. 73 | /// If so, returns it unchanged. 74 | /// 75 | /// Otherwise, surrounds the string with quotes (") and escapes the string for JavaScript. 76 | /// 77 | /// 78 | /// 79 | public static string EnsureValidJson(string msg) 80 | { 81 | if (IsJsonString(msg)) 82 | { 83 | return msg; 84 | } 85 | 86 | return HtmlHelpers.JavaScriptStringEncode(msg, true); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/LoggerRequestHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO; 5 | using System.Threading.Tasks; 6 | using JSNLog; 7 | using Microsoft.AspNetCore.Http; 8 | using JSNLog.Infrastructure.AspNet5; 9 | using Microsoft.Extensions.Primitives; 10 | using JSNLog.Infrastructure; 11 | using JSNLog.LogHandling; 12 | using Microsoft.Extensions.Logging; 13 | 14 | namespace jsnlog.Infrastructure 15 | { 16 | internal class LoggerRequestHelpers 17 | { 18 | public static async Task ProcessLoggerRequestAsync(HttpContext context, ILogger logger) 19 | { 20 | // If there is an exception whilst processing the log request (for example when the connection with the 21 | // Internet disappears), try to log that exception. If that goes wrong too, fail silently. 22 | 23 | try 24 | { 25 | await ProcessRequestAsync(context); 26 | } 27 | catch (Exception e) 28 | { 29 | try 30 | { 31 | logger.LogInformation($"JSNLog: Exception while processing log request - {e}"); 32 | } 33 | catch 34 | { 35 | } 36 | } 37 | 38 | return; 39 | } 40 | 41 | private static async Task ProcessRequestAsync(HttpContext context) 42 | { 43 | var headers = context.Request.Headers.ToDictionary(); 44 | string urlReferrer = headers.SafeGet("Referer"); 45 | string url = context.Request.GetDisplayUrl(); 46 | 47 | var logRequestBase = new LogRequestBase( 48 | userAgent: headers.SafeGet("User-Agent"), 49 | userHostAddress: context.Wrapper().GetUserIp(), 50 | requestId: context.Wrapper().GetLogRequestId(), 51 | url: (urlReferrer ?? url).ToString(), 52 | queryParameters: context.Request.Query.ToDictionary(), 53 | cookies: context.Request.Cookies.ToDictionary(), 54 | headers: headers); 55 | 56 | DateTime serverSideTimeUtc = DateTime.UtcNow; 57 | string httpMethod = context.Request.Method; 58 | string origin = headers.SafeGet("Origin"); 59 | 60 | Encoding encoding = HttpHelpers.GetEncoding(headers.SafeGet("Content-Type")); 61 | 62 | string json; 63 | using (var reader = new StreamReader(context.Request.Body, encoding)) 64 | { 65 | json = await reader.ReadToEndAsync(); 66 | } 67 | 68 | var response = new LogResponse(); 69 | 70 | LoggerProcessor.ProcessLogRequest(json, logRequestBase, 71 | serverSideTimeUtc, 72 | httpMethod, origin, response); 73 | 74 | // Send dummy response. That way, the log request will not remain "pending" 75 | // in eg. Chrome dev tools. 76 | // 77 | // This must be given a MIME type of "text/plain" 78 | // Otherwise, the browser may try to interpret the empty string as XML. 79 | // When the user uses Firefox, and right clicks on the page and chooses "Inspect Element", 80 | // then in that debugger's console it will say "no element found". 81 | // See 82 | // http://www.acnenomor.com/307387p1/how-do-i-setup-my-ajax-post-request-to-prevent-no-element-found-on-empty-response 83 | // http://stackoverflow.com/questions/975929/firefox-error-no-element-found/976200#976200 84 | 85 | ToAspNet5Response(response, context.Response); 86 | context.Response.ContentType = "text/plain"; 87 | context.Response.ContentLength = 0; 88 | } 89 | 90 | private static void ToAspNet5Response(LogResponse logResponse, HttpResponse owinResponse) 91 | { 92 | owinResponse.StatusCode = logResponse.StatusCode; 93 | 94 | foreach (KeyValuePair kvp in logResponse.Headers) 95 | { 96 | owinResponse.Headers[kvp.Key] = kvp.Value; 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/LoggingUrlHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog.Infrastructure 7 | { 8 | public class LoggingUrlHelpers 9 | { 10 | 11 | /// 12 | /// Returns true if a request with the given url is a logging request that should be handled 13 | /// by JSNLog. 14 | /// 15 | /// Note that the user may have set the defaultUrl or an appender url because they want to use 16 | /// the standard url /jsnlog.logger for something else. So you don't want to always allow 17 | /// /jsnlog.logger. 18 | /// 19 | /// On the other hand, it is impossible to figure out exactly what urls the user has used, because you 20 | /// don't know which loggers are being used in the user's code. 21 | /// So you can't only match against the urls specified with the appenders. 22 | /// 23 | /// So this method assumes that in addition to the appenders that have been configured, 24 | /// the default appender is also used. That is, it also passes the defaultUrl set by the user 25 | /// (and if that is not set, the defaultDefaultUrl). 26 | /// 27 | /// 28 | /// 29 | public static bool IsLoggingUrl(string url) 30 | { 31 | if (string.IsNullOrEmpty(url)) 32 | { 33 | throw new ArgumentNullException(url); 34 | } 35 | 36 | JsnlogConfiguration jsnlogConfiguration = JavascriptLogging.GetJsnlogConfiguration(); 37 | 38 | if (jsnlogConfiguration == null) 39 | { 40 | return UrlMatchesAppenderUrl(Constants.DefaultDefaultAjaxUrl, url); 41 | } 42 | 43 | string resolvedDefaultUrl = ResolvedAppenderUrl(Constants.DefaultDefaultAjaxUrl, jsnlogConfiguration.defaultAjaxUrl, null); 44 | if (UrlMatchesAppenderUrl(resolvedDefaultUrl, url)) 45 | { 46 | return true; 47 | } 48 | 49 | if (jsnlogConfiguration.ajaxAppenders != null) 50 | { 51 | foreach (AjaxAppender ajaxAppender in jsnlogConfiguration.ajaxAppenders) 52 | { 53 | string resolvedAppenderUrl = ResolvedAppenderUrl( 54 | Constants.DefaultDefaultAjaxUrl, jsnlogConfiguration.defaultAjaxUrl, ajaxAppender.url); 55 | if (UrlMatchesAppenderUrl(resolvedAppenderUrl, url)) 56 | { 57 | return true; 58 | } 59 | } 60 | } 61 | 62 | return false; 63 | } 64 | 65 | /// 66 | /// Returns the end of the url that an AjaxAppender will send its log requests to. 67 | /// Note that this is not the complete url, it may not have the domain, etc. 68 | /// 69 | /// 70 | /// The default url when user does not provide a url at all. 71 | /// 72 | /// 73 | /// DefaultUrl given to the library 74 | /// 75 | /// 76 | /// Url given to the appender. 77 | /// 78 | /// 79 | public static string ResolvedAppenderUrl(string defaultDefaultUrl, string defaultUrl, string appenderUrl) 80 | { 81 | if (!string.IsNullOrEmpty(appenderUrl)) { return appenderUrl; } 82 | if (!string.IsNullOrEmpty(defaultUrl)) { return defaultUrl; } 83 | 84 | return defaultDefaultUrl; 85 | } 86 | 87 | private static string TrimmedUrl(string url) 88 | { 89 | return url.TrimStart('~'); 90 | } 91 | 92 | private static bool UrlMatchesAppenderUrl(string appenderUrl, string url) 93 | { 94 | // The appender url may be prefixed with ~ (for virtual directory). 95 | 96 | string[] urlParts = url.Split(new char[] { '?' }); 97 | string urlWithoutQuery = urlParts[0]; 98 | 99 | bool result = urlWithoutQuery.EndsWith(TrimmedUrl(appenderUrl)); 100 | return result; 101 | } 102 | 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/RequestId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace JSNLog.Infrastructure 5 | { 6 | static class RequestId 7 | { 8 | 9 | public static string GetLogRequestId(this Dictionary headers) 10 | { 11 | string requestId = headers.SafeGet(Constants.HttpHeaderRequestIdName); 12 | return requestId; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/ResponseStreamWrapper.cs: -------------------------------------------------------------------------------- 1 | #if !NETFRAMEWORK 2 | 3 | using System; 4 | using System.IO; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNetCore.Http; 9 | 10 | /// 11 | /// This class based on 12 | /// https://weblog.west-wind.com/posts/2020/Mar/29/Content-Injection-with-Response-Rewriting-in-ASPNET-Core-3x 13 | /// 14 | namespace JSNLog.Infrastructure 15 | { 16 | /// 17 | /// Wraps the Response Stream to inject the JSNLog config script into all HTML pages just before the tag. 18 | /// 19 | /// This class based on 20 | /// https://weblog.west-wind.com/posts/2020/Mar/29/Content-Injection-with-Response-Rewriting-in-ASPNET-Core-3x 21 | /// 22 | public class ResponseStreamWrapper : Stream 23 | { 24 | private Stream _baseStream; 25 | private HttpContext _context; 26 | private bool _isContentLengthSet = false; 27 | 28 | public ResponseStreamWrapper(Stream baseStream, HttpContext context) 29 | { 30 | _baseStream = baseStream; 31 | _context = context; 32 | CanWrite = true; 33 | } 34 | 35 | public override void Flush() 36 | { 37 | _baseStream.Flush(); 38 | } 39 | 40 | public override Task FlushAsync(CancellationToken cancellationToken) 41 | { 42 | // this is called at the beginning of a request in 3.x and so 43 | // we have to set the ContentLength here as the flush/write locks headers 44 | if (!_isContentLengthSet && IsHtmlResponse()) 45 | { 46 | _context.Response.Headers.ContentLength = null; 47 | _isContentLengthSet = true; 48 | } 49 | 50 | return _baseStream.FlushAsync(cancellationToken); 51 | } 52 | 53 | 54 | public override int Read(byte[] buffer, int offset, int count) 55 | { 56 | return _baseStream.Read(buffer, offset, count); 57 | } 58 | 59 | public override long Seek(long offset, SeekOrigin origin) => _baseStream.Seek(offset, origin); 60 | 61 | public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 62 | { 63 | return _baseStream.ReadAsync(buffer, offset, count, cancellationToken); 64 | } 65 | 66 | public override void SetLength(long value) 67 | { 68 | _baseStream.SetLength(value); 69 | IsHtmlResponse(forceReCheck: true); 70 | } 71 | 72 | public override void Write(ReadOnlySpan buffer) 73 | { 74 | _baseStream.Write(buffer); 75 | } 76 | 77 | public override void WriteByte(byte value) 78 | { 79 | _baseStream.WriteByte(value); 80 | } 81 | 82 | public override void Write(byte[] buffer, int offset, int count) 83 | { 84 | if (IsHtmlResponse()) 85 | { 86 | ScriptInjectionHelper.InjectScriptAsync(buffer.AsMemory(offset, count), _context, _baseStream) 87 | .GetAwaiter() 88 | .GetResult(); 89 | } 90 | else 91 | { 92 | _baseStream?.Write(buffer, offset, count); 93 | } 94 | } 95 | 96 | public override Task WriteAsync(byte[] buffer, int offset, int count, 97 | CancellationToken cancellationToken) 98 | { 99 | return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask(); 100 | } 101 | 102 | public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) 103 | { 104 | if (IsHtmlResponse()) 105 | { 106 | await ScriptInjectionHelper.InjectScriptAsync(buffer, _context, _baseStream); 107 | } 108 | else 109 | { 110 | if (_baseStream != null) 111 | await _baseStream.WriteAsync(buffer, cancellationToken); 112 | } 113 | } 114 | 115 | private bool? _isHtmlResponse = null; 116 | 117 | private bool IsHtmlResponse(bool forceReCheck = false) 118 | { 119 | if (!forceReCheck && _isHtmlResponse != null) 120 | return _isHtmlResponse.Value; 121 | 122 | // we need to check if the active request is still valid 123 | // this can fail if we're in the middle of an error response 124 | // or url rewrite in which case we can't intercept 125 | if (_context?.Response == null) 126 | return false; 127 | 128 | // Requirements for script injection: 129 | // * has to have result body 130 | // * 200 or 500 response 131 | // * text/html response 132 | // * UTF-8 formatted (explicit or no charset) 133 | 134 | _isHtmlResponse = 135 | _context.Response?.Body != null && 136 | (_context.Response.StatusCode == 200 || _context.Response.StatusCode == 500) && 137 | _context.Response.ContentType != null && 138 | _context.Response.ContentType.Contains("text/html", StringComparison.OrdinalIgnoreCase) && 139 | (_context.Response.ContentType.Contains("utf-8", StringComparison.OrdinalIgnoreCase) || 140 | !_context.Response.ContentType.Contains("charset=", StringComparison.OrdinalIgnoreCase)); 141 | 142 | if (!_isHtmlResponse.Value) 143 | return false; 144 | 145 | // Make sure we force dynamic content type since we're 146 | // rewriting the content - static content will set the header explicitly 147 | // and fail when it doesn't matchif (_isHtmlResponse.Value) 148 | if (!_isContentLengthSet && _context.Response.ContentLength != null) 149 | { 150 | _context.Response.Headers.ContentLength = null; 151 | _isContentLengthSet = true; 152 | } 153 | 154 | return _isHtmlResponse.Value; 155 | } 156 | 157 | protected override void Dispose(bool disposing) 158 | { 159 | //_baseStream?.Dispose(); 160 | _baseStream = null; 161 | _context = null; 162 | 163 | base.Dispose(disposing); 164 | } 165 | 166 | #region Byte Helpers 167 | /// 168 | /// Tries to find a 169 | /// 170 | /// byte array to be searched 171 | /// byte to find 172 | /// 173 | public static int IndexOfByteArray(byte[] buffer, byte[] bufferToFind) 174 | { 175 | if (buffer.Length == 0 || bufferToFind.Length == 0) 176 | return -1; 177 | 178 | for (int i = 0; i < buffer.Length; i++) 179 | { 180 | if (buffer[i] == bufferToFind[0]) 181 | { 182 | bool innerMatch = true; 183 | for (int j = 1; j < bufferToFind.Length; j++) 184 | { 185 | if (buffer[i + j] != bufferToFind[j]) 186 | { 187 | innerMatch = false; 188 | break; 189 | } 190 | } 191 | if (innerMatch) 192 | return i; 193 | } 194 | } 195 | 196 | return -1; 197 | } 198 | 199 | /// 200 | /// Returns an index into a byte array to find a string in the byte array. 201 | /// Exact match using the encoding provided or UTF-8 by default. 202 | /// 203 | /// 204 | /// 205 | /// 206 | /// 207 | public static int IndexOfByteArray(byte[] buffer, string stringToFind, Encoding encoding = null) 208 | { 209 | if (encoding == null) 210 | encoding = Encoding.UTF8; 211 | 212 | if (buffer.Length == 0 || string.IsNullOrEmpty(stringToFind)) 213 | return -1; 214 | 215 | var bytes = encoding.GetBytes(stringToFind); 216 | 217 | return IndexOfByteArray(buffer, bytes); 218 | } 219 | #endregion 220 | 221 | public override bool CanRead { get; } 222 | public override bool CanSeek { get; } 223 | public override bool CanWrite { get; } 224 | public override long Length { get; } 225 | public override long Position { get; set; } 226 | } 227 | } 228 | 229 | #endif 230 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/ScriptInjectionHelper.cs: -------------------------------------------------------------------------------- 1 | #if !NETFRAMEWORK 2 | 3 | using System; 4 | using System.IO; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Http; 8 | 9 | /// 10 | /// This class based on 11 | /// https://weblog.west-wind.com/posts/2020/Mar/29/Content-Injection-with-Response-Rewriting-in-ASPNET-Core-3x 12 | /// 13 | namespace JSNLog.Infrastructure 14 | { 15 | public static class ScriptInjectionHelper 16 | { 17 | private const string _jsnLogStartMarker = ""; 18 | private const string _jsnLogEndMarker = ""; 19 | private const string _bodyMarker = ""; 20 | 21 | private static readonly byte[] _bodyBytes = Encoding.UTF8.GetBytes(_bodyMarker); 22 | private static readonly byte[] _markerBytes = Encoding.UTF8.GetBytes(_jsnLogStartMarker); 23 | 24 | /// 25 | /// Adds a script block with JSNLog configuration code before the body tag. 26 | /// 27 | /// 28 | /// 29 | /// The raw Response Stream 30 | /// 31 | public static Task InjectScriptAsync(ReadOnlyMemory buffer, HttpContext context, Stream baseStream) 32 | { 33 | return InjectScriptAsync(buffer.ToArray(), context, baseStream); 34 | } 35 | 36 | public static async Task InjectScriptAsync(byte[] buffer, HttpContext context, Stream baseStream) 37 | { 38 | var index = buffer.LastIndexOf(_markerBytes); 39 | 40 | if (index > -1) 41 | { 42 | await baseStream.WriteAsync(buffer, 0, buffer.Length); 43 | return; 44 | } 45 | 46 | index = buffer.LastIndexOf(_bodyBytes); 47 | if (index == -1) 48 | { 49 | await baseStream.WriteAsync(buffer, 0, buffer.Length); 50 | return; 51 | } 52 | 53 | var endIndex = index + _bodyBytes.Length; 54 | 55 | // Write pre-marker buffer 56 | await baseStream.WriteAsync(buffer, 0, index - 1); 57 | 58 | // Write the injected script 59 | var scriptBytes = Encoding.UTF8.GetBytes(GetJsnLogConfigurationScript(context)); 60 | await baseStream.WriteAsync(scriptBytes, 0, scriptBytes.Length); 61 | 62 | // Write the rest of the buffer/HTML doc 63 | await baseStream.WriteAsync(buffer, endIndex, buffer.Length - endIndex); 64 | } 65 | 66 | static int LastIndexOf(this T[] array, T[] sought) where T : IEquatable => 67 | array.AsSpan().LastIndexOf(sought); 68 | 69 | private static string GetJsnLogConfigurationScript(HttpContext context) 70 | { 71 | string script = 72 | _jsnLogStartMarker + 73 | context.Configure(null) + 74 | _jsnLogEndMarker + 75 | _bodyMarker; 76 | 77 | return script; 78 | } 79 | } 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/SiteConstants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | 6 | namespace JSNLog.Infrastructure 7 | { 8 | // Moved from web site project. 9 | //TODO: move web site project to jsnlog solution, have only one copy of this file 10 | public static class SiteConstants 11 | { 12 | public static string CurrentVersion = Generated.Version; 13 | public static string CurrentFrameworkVersion = Generated.FrameworkVersion; 14 | public static string CurrentJSNLogJsVersion = Generated.JSNLogJsVersion; 15 | public const string JsnlogJsFileSize = "2kb"; 16 | public const string HttpHeaderRequestIdName = "JSNLog-RequestId"; 17 | public const string GlobalMethodCalledAfterJsnlogJsLoaded = "__jsnlog_configure"; 18 | 19 | public const string HandlerExtension = ".logger"; 20 | public const string DefaultDefaultAjaxUrl = "/jsnlog" + HandlerExtension; 21 | 22 | public const string DemoGithubUrlNetFramework = "https://github.com/mperdeck/jsnlogSimpleWorkingDemos.NetFramework/tree/main"; 23 | public const string DemoGithubUrl = "https://github.com/mperdeck/jsnlogSimpleWorkingDemos/tree/master"; 24 | public const string DemoGithubUrlNetCore = DemoGithubUrl + "/jsnlogSimpleWorkingDemos/NetCore"; 25 | public const string DemoAspNetCoreGithubUrl = DemoGithubUrlNetCore + "/JSNLogDemo_Core_NetCoreApp2"; 26 | public const string AngularJsDemoGithubUrl = "https://github.com/mperdeck/JSNLog.AngularJS"; 27 | public const string Angular2CoreDemoGithubUrl = "https://github.com/mperdeck/jsnlog.AngularCoreDemo"; 28 | public const string LicenceUrl = "https://github.com/mperdeck/jsnlog/blob/master/LICENSE.md"; 29 | public const string LicenceName = "MIT"; 30 | public const string CdnJsUrl = "https://cdnjs.com/libraries/jsnlog"; 31 | public static string CdnJsDownloadUrl = "https://cdnjs.cloudflare.com/ajax/libs/jsnlog/" + CurrentJSNLogJsVersion + "/jsnlog.min.js"; 32 | public static string CdnJsScriptTag = @""; 33 | 34 | public const string JsnlogHost = "https://jsnlog.com"; 35 | public const string InstallPageUrl = JsnlogHost + "/Documentation/DownloadInstall"; 36 | 37 | 38 | // This causes NuGet to search for all packages with "JSNLog" - so the user will see 39 | // JSNLog.NLog, etc. as well. 40 | public const string NugetDownloadUrl = "https://www.nuget.org/packages?q=jsnlog"; 41 | 42 | public static string CurrentYear 43 | { 44 | get { return DateTime.Now.ToString("yyyy"); } 45 | } 46 | 47 | public static string DownloadLinkJsnlogJs 48 | { 49 | get { return "https://raw.githubusercontent.com/mperdeck/jsnlog.js/master/jsnlog.min.js"; } 50 | } 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /jsnlog/Infrastructure/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Xml; 6 | using System.Collections.Specialized; 7 | using Microsoft.Extensions.Primitives; 8 | 9 | namespace JSNLog.Infrastructure 10 | { 11 | internal static class Utils 12 | { 13 | /// 14 | /// Takes a DateTime in UTC and returns the same timestamp in local time. 15 | /// 16 | /// 17 | /// 18 | public static DateTime UtcToLocalDateTime(DateTime utcTime) 19 | { 20 | DateTime localTime = utcTime.ToLocalTime(); 21 | return localTime; 22 | } 23 | 24 | #if NETFRAMEWORK 25 | // NameValueCollection is unknown in DNX environments 26 | 27 | public static Dictionary ToDictionary(NameValueCollection nameValueCollection) 28 | { 29 | var result = new Dictionary(); 30 | 31 | foreach (string key in nameValueCollection.AllKeys) 32 | { 33 | result[key] = nameValueCollection[key]; 34 | } 35 | 36 | return result; 37 | } 38 | #endif 39 | 40 | public static Dictionary ToDictionary(this IEnumerable> nameValueCollection) 41 | { 42 | var result = new Dictionary(); 43 | 44 | foreach (var kvp in nameValueCollection) 45 | { 46 | result[kvp.Key] = kvp.Value; 47 | } 48 | 49 | return result; 50 | } 51 | 52 | public static T SafeGet(this IDictionary dictionary, K key) 53 | { 54 | T value; 55 | dictionary.TryGetValue(key, out value); 56 | return value; 57 | } 58 | 59 | public static Dictionary ToDictionary(this IEnumerable> values) 60 | { 61 | var result = new Dictionary(); 62 | foreach (var kvp in values) 63 | { 64 | result[kvp.Key] = kvp.Value.ToString(); 65 | } 66 | 67 | return result; 68 | } 69 | 70 | public static string SafeToString(this T obj) 71 | { 72 | if (obj == null) 73 | { 74 | return ""; 75 | } 76 | 77 | return obj.ToString(); 78 | } 79 | 80 | /// 81 | /// The given url may be virtual (starts with ~). This method returns a version of the url that is not virtual. 82 | /// 83 | /// 84 | /// 85 | public static string AbsoluteUrl(string url, Func virtualToAbsoluteFunc) 86 | { 87 | string urlLc = url.ToLower(); 88 | if (urlLc.StartsWith("//") || urlLc.StartsWith("http://") || urlLc.StartsWith("https://")) 89 | { 90 | return url; 91 | } 92 | 93 | string absoluteUrl = virtualToAbsoluteFunc(url); 94 | return absoluteUrl; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /jsnlog/JSNLog.ClassLibrary.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JSNLog.ClassLibrary 5 | 0.0.0 6 | JSNLog.ClassLibrary - Class library for JSNLog 7 | Matt Perdeck 8 | Matt Perdeck 9 | http://jsnlog.com 10 | http://jsnlog.com/Nuget/icon.png 11 | false 12 | Equal to JSNLog package, except that this does not modify your web.config, and does not install the JavaScript library and the MVC route for logging requests. For use in class libraries instead of web sites. If in doubt, just use the JSNLog package. 13 | Copyright 2012-2017 14 | JavaScript logging exceptions ajax log4net nlog elmah serilog 15 | 16 | 17 | 31 | 32 | 33 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /jsnlog/JSNLog.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JSNLog 5 | 0.0.0 6 | JSNLog - JavaScript Logging Package 7 | Matt Perdeck 8 | Matt Perdeck 9 | http://jsnlog.com 10 | http://jsnlog.com/Nuget/icon.png 11 | false 12 | JavaScript logging package that lets you log exceptions, AJAX timeouts and other client side events in your server side log. 13 | Copyright 2012-2017 14 | JavaScript logging exceptions ajax log4net nlog elmah serilog 15 | 16 | 17 | 31 | 32 | 33 | 34 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /jsnlog/LogHandling/LogRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace JSNLog 5 | { 6 | internal class LogRequest : LogRequestBase, ILogRequest 7 | { 8 | public string Message { get; private set; } 9 | public string Logger { get; private set; } 10 | public string Level { get; private set; } 11 | public DateTime UtcDate { get; private set; } 12 | public string EntryId { get; private set; } 13 | public string JsonMessage { get; private set; } 14 | 15 | public LogRequest(string message, string logger, string level, 16 | DateTime utcDate, string entryId, string jsonMessage, LogRequestBase logRequestBase) 17 | : base(logRequestBase) 18 | { 19 | Message = message; 20 | Logger = logger; 21 | Level = level; 22 | UtcDate = utcDate; 23 | EntryId = entryId; 24 | JsonMessage = jsonMessage; 25 | } 26 | 27 | public override string ToString() 28 | { 29 | return string.Format( 30 | "Message: {0}, Logger: {1}, Level: {2}, UtcDate: {3}, EntryId: {4}, JsonMessage: {5}, logRequestBase: {{{6}}}", 31 | Message, Logger, Level, UtcDate, EntryId, JsonMessage, base.ToString()); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /jsnlog/LogHandling/LogRequestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace JSNLog 5 | { 6 | internal class LogRequestBase 7 | { 8 | public string UserAgent { get; private set; } 9 | public string UserHostAddress { get; private set; } 10 | public string RequestId { get; private set; } 11 | public string Url { get; private set; } 12 | 13 | // Not used by JSNLog, only by onLogging 14 | public Dictionary QueryParameters { get; private set; } 15 | public Dictionary Cookies { get; private set; } 16 | public Dictionary Headers { get; private set; } 17 | 18 | public LogRequestBase(string userAgent, string userHostAddress, string requestId, 19 | string url, 20 | Dictionary queryParameters, Dictionary cookies, 21 | Dictionary headers) 22 | { 23 | UserAgent = userAgent; 24 | UserHostAddress = userHostAddress; 25 | RequestId = requestId; 26 | Url = url; 27 | QueryParameters = queryParameters; 28 | Cookies = cookies; 29 | Headers = headers; 30 | } 31 | 32 | public LogRequestBase(LogRequestBase other) 33 | { 34 | UserAgent = other.UserAgent; 35 | UserHostAddress = other.UserHostAddress; 36 | RequestId = other.RequestId; 37 | Url = other.Url; 38 | QueryParameters = other.QueryParameters; 39 | Cookies = other.Cookies; 40 | Headers = other.Headers; 41 | } 42 | 43 | public override string ToString() 44 | { 45 | return string.Format( 46 | "UserAgent: {0}, UserHostAddress: {1}, RequestId: {2}, Url: {3}, QueryParameters: {4}, Cookies: {5}, Headers: {6}", 47 | UserAgent, UserHostAddress, RequestId, Url, 48 | QueryParameters == null ? "null" : "not null", 49 | Cookies == null ? "null" : "not null", 50 | Headers == null ? "null" : "not null"); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /jsnlog/LogHandling/LogResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog.LogHandling 7 | { 8 | internal class LogResponse 9 | { 10 | private Dictionary _headers = new Dictionary(); 11 | 12 | public Dictionary Headers 13 | { 14 | get { return _headers; } 15 | } 16 | 17 | public int StatusCode { get; set; } 18 | 19 | public void AppendHeader(string name, string value) 20 | { 21 | _headers[name] = value; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /jsnlog/LogHandling/LoggerProcessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using JSNLog.Infrastructure; 4 | using System.Net; 5 | using System.Text.RegularExpressions; 6 | using JSNLog.Exceptions; 7 | 8 | namespace JSNLog.LogHandling 9 | { 10 | internal class LoggerProcessor 11 | { 12 | /// 13 | /// The log data sent in a single log request from the client. 14 | /// It is expected that this list has 2 items: 15 | /// * the requestId (key: r) 16 | /// * the array with log items (key: lg) 17 | /// 18 | 19 | private class LogRequestSingleMsg 20 | { 21 | public string m {get;set;} 22 | public string n {get;set;} 23 | public string l {get;set;} 24 | public string t {get;set;} 25 | public string u { get; set; } 26 | } 27 | 28 | private class LogRequestData 29 | { 30 | public string r {get;set;} 31 | public ICollection lg {get;set;} 32 | } 33 | 34 | /// 35 | /// Processes the incoming request. This method is not depended on the environment and so can be unit tested. 36 | /// Note that the incoming request may not be the POST request with log data. 37 | /// 38 | /// Effect of this method: 39 | /// * setting StatusCode on the response parameter 40 | /// * adding headers to the response parameter 41 | /// * logging contents of log request 42 | /// 43 | /// 44 | /// JSON payload in the incoming request 45 | /// 46 | /// 47 | /// * Type of browser that sent the request 48 | /// * IP address that sent the address 49 | /// * Url that the request was sent to 50 | /// * Request Id sent as part of the request 51 | /// * Request data to be given to onLogging event handler 52 | /// 53 | /// Current time in UTC 54 | /// HTTP method of the request 55 | /// Value of the Origin request header 56 | /// 57 | /// Empty response object. This method can add headers, etc. 58 | /// 59 | internal static void ProcessLogRequest(string json, LogRequestBase logRequestBase, 60 | DateTime serverSideTimeUtc, 61 | string httpMethod, string origin, LogResponse response) 62 | { 63 | JsnlogConfiguration jsnlogConfiguration = JavascriptLogging.GetJsnlogConfiguration(); 64 | 65 | ILoggingAdapter logger = JavascriptLogging.GetLogger(); 66 | 67 | if ((httpMethod != "POST") && (httpMethod != "OPTIONS")) 68 | { 69 | response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; 70 | return; 71 | } 72 | 73 | string corsAllowedOriginsRegex = jsnlogConfiguration.corsAllowedOriginsRegex; 74 | bool originIsAllowed = ((!string.IsNullOrEmpty(corsAllowedOriginsRegex)) && (!string.IsNullOrEmpty(origin)) && Regex.IsMatch(origin, corsAllowedOriginsRegex)); 75 | 76 | if (originIsAllowed) 77 | { 78 | response.AppendHeader("Access-Control-Allow-Origin", origin); 79 | } 80 | 81 | response.StatusCode = (int)HttpStatusCode.OK; 82 | 83 | if (httpMethod == "OPTIONS") 84 | { 85 | // Standard HTTP response (not related to CORS) 86 | response.AppendHeader("Allow", "POST"); 87 | 88 | // Only if the origin is allowed send CORS headers 89 | if (originIsAllowed) 90 | { 91 | string allowedHeaders = "jsnlog-requestid, content-type"; 92 | if (!string.IsNullOrWhiteSpace(jsnlogConfiguration.corsAllowedHeaders)) 93 | { 94 | allowedHeaders += "," + jsnlogConfiguration.corsAllowedHeaders.Trim().Trim(','); 95 | } 96 | 97 | response.AppendHeader("Access-Control-Max-Age", Constants.CorsAccessControlMaxAgeInSeconds.ToString()); 98 | response.AppendHeader("Access-Control-Allow-Methods", "POST"); 99 | response.AppendHeader("Access-Control-Allow-Headers", allowedHeaders); 100 | } 101 | 102 | return; 103 | } 104 | 105 | // httpMethod must be POST 106 | 107 | List logDatas = 108 | ProcessLogRequestExec(json, logRequestBase, serverSideTimeUtc, jsnlogConfiguration); 109 | 110 | // --------------------------------- 111 | // Pass log data to Common Logging 112 | 113 | foreach (FinalLogData logData in logDatas) 114 | { 115 | logger.Log(logData); 116 | } 117 | } 118 | 119 | /// 120 | /// Processes a request with logging info. Unit testable. 121 | /// 122 | /// Returns log info in easily digestable format. 123 | /// 124 | /// JSON sent from client by AjaxAppender 125 | /// Current time in UTC 126 | /// Contains all config info 127 | internal static List ProcessLogRequestExec(string json, LogRequestBase logRequestBase, 128 | DateTime serverSideTimeUtc, JsnlogConfiguration jsnlogConfiguration) 129 | { 130 | var logDatas = new List(); 131 | FinalLogData logData = null; 132 | 133 | try 134 | { 135 | LogRequestData logRequestData = LogMessageHelpers.DeserializeJson(json); 136 | 137 | foreach (var logItem in logRequestData.lg) 138 | { 139 | logData = null; // in case ProcessLogItem throws exception 140 | logData = ProcessLogItem(logItem, 141 | logRequestBase, serverSideTimeUtc, jsnlogConfiguration); 142 | 143 | if (logData != null) 144 | { 145 | logDatas.Add(logData); 146 | } 147 | } 148 | } 149 | catch (Exception e) 150 | { 151 | try 152 | { 153 | string message = string.Format("Exception: {0}, json: {1}, FinalLogData: {{{2}}}, logRequestBase: {{{3}}}", e, json, logData, logRequestBase); 154 | 155 | var internalErrorFinalLogData = new FinalLogData(null) 156 | { 157 | FinalMessage = message, 158 | FinalLogger = Constants.JSNLogInternalErrorLoggerName, 159 | FinalLevel = Level.ERROR 160 | }; 161 | 162 | logDatas.Add(internalErrorFinalLogData); 163 | } 164 | catch 165 | { 166 | } 167 | } 168 | 169 | return logDatas; 170 | } 171 | 172 | private static FinalLogData ProcessLogItem(LogRequestSingleMsg logItem, LogRequestBase logRequestBase, 173 | DateTime serverSideTimeUtc, JsnlogConfiguration jsnlogConfiguration) 174 | { 175 | string serversideLoggerNameOverride = jsnlogConfiguration.serverSideLogger; 176 | string messageFormat = jsnlogConfiguration.serverSideMessageFormat; 177 | string levelOverride = jsnlogConfiguration.serverSideLevel; 178 | string dateFormat = jsnlogConfiguration.dateFormat; 179 | 180 | try 181 | { 182 | LevelUtils.ValidateLevel(levelOverride); 183 | } 184 | catch (Exception e) 185 | { 186 | throw new PropertyException("levelOverride", e); 187 | } 188 | 189 | // ---------------- 190 | 191 | string message = logItem.m; 192 | string logger = logItem.n; 193 | string level = logItem.l; // note that level as sent by the javascript is a number 194 | string entryId = logItem.u; 195 | 196 | DateTime utcDate = DateTime.UtcNow; 197 | string timestampMs = logItem.t; 198 | try 199 | { 200 | double ms = double.Parse(timestampMs); 201 | utcDate = DateTime.SpecifyKind((new DateTime(1970, 1, 1)).AddMilliseconds(ms), DateTimeKind.Utc); 202 | } 203 | catch 204 | { 205 | } 206 | 207 | // ---------------- 208 | 209 | string jsonmessage = ""; 210 | if (messageFormat.Contains("%jsonmessage")) 211 | { 212 | jsonmessage = LogMessageHelpers.EnsureValidJson(message); 213 | } 214 | 215 | // ---------------- 216 | 217 | var logRequest = new LogRequest(message, logger, level, utcDate, entryId, jsonmessage, logRequestBase); 218 | var loggingEventArgs = new LoggingEventArgs(logRequest) 219 | { 220 | Cancel = false, 221 | ServerSideMessageFormat = messageFormat 222 | }; 223 | 224 | // ---------------- 225 | 226 | if (string.IsNullOrWhiteSpace(logger)) { logger = Constants.RootLoggerNameServerSide; } 227 | loggingEventArgs.FinalLogger = serversideLoggerNameOverride ?? logger; 228 | 229 | string consolidatedLevel = levelOverride ?? level; 230 | loggingEventArgs.FinalLevel = LevelUtils.ParseLevel(consolidatedLevel).Value; 231 | 232 | // ---------------- 233 | 234 | loggingEventArgs.FinalMessage = messageFormat 235 | .Replace("%message", message) 236 | .Replace("%entryId", entryId) 237 | .Replace("%jsonmessage", jsonmessage) 238 | .Replace("%utcDateServer", serverSideTimeUtc.ToString(dateFormat)) 239 | .Replace("%utcDate", utcDate.ToString(dateFormat)) 240 | .Replace("%dateServer", Utils.UtcToLocalDateTime(serverSideTimeUtc).ToString(dateFormat)) 241 | .Replace("%date", Utils.UtcToLocalDateTime(utcDate).ToString(dateFormat)) 242 | .Replace("%level", level) 243 | .Replace("%newline", System.Environment.NewLine) 244 | .Replace("%userAgent", logRequestBase.UserAgent) 245 | .Replace("%userHostAddress", logRequestBase.UserHostAddress) 246 | .Replace("%requestId", logRequestBase.RequestId ?? "") 247 | .Replace("%url", logRequestBase.Url) 248 | .Replace("%logger", logger); 249 | 250 | // ---------------- 251 | 252 | JavascriptLogging.RaiseLoggingEvent(loggingEventArgs); 253 | 254 | // If user wrote event handler that decided not to log the message, return null 255 | if (loggingEventArgs.Cancel) { return null; } 256 | 257 | return loggingEventArgs; 258 | } 259 | 260 | /// 261 | /// Returns the value associated with a key in a dictionary. 262 | /// If the key is not present, returns the default value - rather than throwing an exception. 263 | /// 264 | /// 265 | /// 266 | /// 267 | /// 268 | private static string SafeGet(Dictionary dictionary, string key, string defaultValue) 269 | { 270 | if (dictionary.ContainsKey(key)) 271 | { 272 | return dictionary[key].ToString(); 273 | } 274 | 275 | return defaultValue; 276 | } 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /jsnlog/NuGet/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mperdeck/jsnlog/bc6cfc091c4baa1574bfdc680baba7621fd69164/jsnlog/NuGet/icon.png -------------------------------------------------------------------------------- /jsnlog/NuGet/readme.txt: -------------------------------------------------------------------------------- 1 | ================================== 2 | JSNLog is not yet fully installed 3 | ================================== 4 | 5 | To finish the installation, visit: 6 | 7 | http://jsnlog.com/Documentation/DownloadInstall 8 | -------------------------------------------------------------------------------- /jsnlog/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | 9 | // Setting ComVisible to false makes the types in this assembly not visible 10 | // to COM components. If you need to access a type in this assembly from 11 | // COM, set the ComVisible attribute to true on that type. 12 | [assembly: ComVisible(false)] 13 | 14 | // The following GUID is for the ID of the typelib if this project is exposed to COM 15 | [assembly: Guid("dc2f1e35-c7cc-4054-8671-424e801a104e")] 16 | 17 | // Get the compiler to create a strong named assembly, using the key pair in this file. 18 | // Note that they key file gets checked into Github, as per 19 | // http://stackoverflow.com/questions/36141302/why-is-it-recommended-to-include-the-private-key-used-for-assembly-signing-in-op 20 | [assembly: AssemblyKeyFileAttribute("../jsnlog.strongname.snk")] 21 | 22 | // Allow project JSNLog.Tests to access the internals of this project. 23 | // Have to add the public key of JSNLog.Tests (which lives in the JSNLog.Tests solution), 24 | // because this JSNLog project is strongly signed. 25 | // 26 | // See 27 | // https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.internalsvisibletoattribute?view=net-8.0&redirectedfrom=MSDN 28 | // https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-runtime-compilerservices-internalsvisibletoattribute 29 | // To get the public key, in Visual Studio command window, run: 30 | // sn -p jsnlog.tests.strongname.snk public.out 31 | // sn -tp public.out 32 | // 33 | [assembly: InternalsVisibleTo("JSNLog.Tests, PublicKey=" + 34 | "002400000480000094000000060200000024000052534131000400000100010089dd0d93cfa65f" + 35 | "cebdc52181975e43cbe31d26ed803efeaa3ba5df99b602c5ca0dfc5131d14f15f8e57845171632" + 36 | "53ffb1f070476dd13062903dda4fa93deb0982bf3e7956da923e94f40ac22b42c356d8c4b9434f" + 37 | "a81d2c528afa798d7ff7ff3691a19e270acc184ed7c77a0c74991558be3e06ce8a08e8169d1ba3" + 38 | "78d17283")] 39 | 40 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/AspNet5/Configuration/LoggingAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.Extensions.Logging; 6 | using JSNLog.Infrastructure; 7 | 8 | namespace JSNLog 9 | { 10 | // Unlike CommonLoggingAdapter, LoggingAdapter has to be public, so the user can instantiate it to make ILoggingFactory 11 | // available in an ASP.NET 5 environment. 12 | 13 | public class LoggingAdapter : ILoggingAdapter 14 | { 15 | private ILoggerFactory _loggerFactory; 16 | 17 | public LoggingAdapter(ILoggerFactory loggerFactory) 18 | { 19 | _loggerFactory = loggerFactory; 20 | } 21 | 22 | public void Log(FinalLogData finalLogData) 23 | { 24 | ILogger logger = _loggerFactory.CreateLogger(finalLogData.FinalLogger); 25 | 26 | Object message = LogMessageHelpers.DeserializeIfPossible(finalLogData.FinalMessage); 27 | 28 | switch (finalLogData.FinalLevel) 29 | { 30 | case Level.TRACE: logger.LogTrace("{logMessage}", message); break; 31 | case Level.DEBUG: logger.LogDebug("{logMessage}", message); break; 32 | case Level.INFO: logger.LogInformation("{logMessage}", message); break; 33 | case Level.WARN: logger.LogWarning("{logMessage}", message); break; 34 | case Level.ERROR: logger.LogError("{logMessage}", message); break; 35 | case Level.FATAL: logger.LogCritical("{logMessage}", message); break; 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/AspNet5/Configuration/Middleware/ApplicationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace JSNLog 9 | { 10 | public static class ApplicationBuilderExtensions 11 | { 12 | /// 13 | /// Normally, an ASP.NET 5 app would simply call this to insert JSNLog middleware into the pipeline. 14 | /// Note that the loggingAdapter is required, otherwise JSNLog can't hand off log messages. 15 | /// It can live without a configuration though (it will use default settings). 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// Note that if jsnlogConfiguration is set to null, the script tag and JavaScript config code will 21 | /// automatically be inserted in html responses. 22 | /// 23 | /// The configured IApplicationBuilder instance 24 | public static IApplicationBuilder UseJSNLog(this IApplicationBuilder builder, 25 | ILoggingAdapter loggingAdapter, JsnlogConfiguration jsnlogConfiguration = null) 26 | { 27 | JavascriptLogging.SetJsnlogConfiguration(jsnlogConfiguration, loggingAdapter); 28 | return builder.UseMiddleware(); 29 | } 30 | 31 | public static void UseJSNLog(this IApplicationBuilder builder, 32 | ILoggerFactory loggerFactory, JsnlogConfiguration jsnlogConfiguration = null) 33 | { 34 | var loggingAdapter = new LoggingAdapter(loggerFactory); 35 | UseJSNLog(builder, loggingAdapter, jsnlogConfiguration); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/AspNet5/LogRequestHandling/Middleware/JSNLogMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Http; 8 | using JSNLog.Infrastructure.AspNet5; 9 | using Microsoft.Extensions.Primitives; 10 | using JSNLog.Infrastructure; 11 | using JSNLog.LogHandling; 12 | using Microsoft.Extensions.Logging; 13 | using jsnlog.Infrastructure; 14 | 15 | #if !NETCORE2 16 | using Microsoft.AspNetCore.Http.Features; 17 | #endif 18 | 19 | /// 20 | /// This class based on 21 | /// https://weblog.west-wind.com/posts/2020/Mar/29/Content-Injection-with-Response-Rewriting-in-ASPNET-Core-3x 22 | /// 23 | 24 | // Be sure to leave the namespace at JSNLog. 25 | namespace JSNLog 26 | { 27 | /// 28 | /// Note that the OWIN counterpart of this (also in namespace JSNLog) 29 | /// already has name JSNLogMiddlewareComponent 30 | /// 31 | public class JSNLogMiddleware 32 | { 33 | private readonly RequestDelegate _next; 34 | private readonly ILogger _logger; 35 | 36 | public JSNLogMiddleware(RequestDelegate next, ILoggerFactory loggerFactory) 37 | { 38 | _next = next; 39 | _logger = loggerFactory.CreateLogger(); 40 | } 41 | 42 | public async Task Invoke(HttpContext context) 43 | { 44 | // If this is a logging request (based on its url), do the logging and don't pass on the request 45 | // to the rest of the pipeline. 46 | string url = context.Request.GetDisplayUrl(); 47 | if (LoggingUrlHelpers.IsLoggingUrl(url)) 48 | { 49 | await LoggerRequestHelpers.ProcessLoggerRequestAsync(context, _logger); 50 | return; 51 | } 52 | 53 | // It was not a logging request 54 | 55 | JsnlogConfiguration jsnlogConfiguration = JavascriptLogging.GetJsnlogConfiguration(); 56 | if (!jsnlogConfiguration.insertJsnlogInHtmlResponses) 57 | { 58 | // If automatic insertion is not enabled, simply call the rest of the pipeline and return. 59 | await _next(context); 60 | return; 61 | } 62 | 63 | #if NETFRAMEWORK 64 | throw new Exception( 65 | "Automatic insertion of JSNLog into HTML pages is not supported in netstandard2.0. " + 66 | $"Upgrade to netstandard2.1 or for other options see {SiteConstants.InstallPageUrl}"); 67 | #else 68 | // Check other content for HTML 69 | await HandleHtmlInjection(context); 70 | #endif 71 | } 72 | 73 | #if !NETFRAMEWORK 74 | /// 75 | /// Inspects the responses for all requests for HTML documents 76 | /// and injects the JavaScript to configure JSNLog client side. 77 | /// 78 | /// Uses a wrapper stream to wrap the response and examine 79 | /// only text/html requests - other content is passed through 80 | /// as is. 81 | /// 82 | /// 83 | /// 84 | private async Task HandleHtmlInjection(HttpContext context) 85 | { 86 | var path = context.Request.Path.Value; 87 | 88 | // Use a custom StreamWrapper to rewrite output on Write/WriteAsync 89 | using (var filteredResponse = new ResponseStreamWrapper(context.Response.Body, context)) 90 | { 91 | #if !NETCORE2 92 | // Use new IHttpResponseBodyFeature for abstractions of pilelines/streams etc. 93 | // For 3.x this works reliably while direct Response.Body was causing random HTTP failures 94 | context.Features.Set(new StreamResponseBodyFeature(filteredResponse)); 95 | #else 96 | context.Response.Body = filteredResponse; 97 | #endif 98 | 99 | await _next(context); 100 | } 101 | } 102 | #endif 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/AspNet5/TagHelpers/JlJavascriptLoggerDefinitionsTagHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Mvc.Rendering; 8 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 9 | using Microsoft.AspNetCore.Razor.TagHelpers; 10 | 11 | namespace JSNLog 12 | { 13 | // HtmlTargetElementAttribute is needed because this tag helper will be used in other projects 14 | [HtmlTargetElement("jl-javascript-logger-definitions")] 15 | public class JlJavascriptLoggerDefinitionsTagHelper : TagHelper 16 | { 17 | // Can be passed via . 18 | // Pascal case gets translated into lower-kebab-case. 19 | public string RequestId { get; set; } 20 | 21 | // See https://www.jerriepelser.com/blog/accessing-request-object-inside-tag-helper-aspnet-core/ 22 | 23 | [ViewContext] 24 | public ViewContext ViewContext { get; set; } 25 | 26 | public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) 27 | { 28 | return base.ProcessAsync(context, output); 29 | } 30 | 31 | public override void Process(TagHelperContext context, TagHelperOutput output) 32 | { 33 | output.TagName = ""; // Remove the jl-javascript-logger-definitions tag completely 34 | 35 | HttpContext httpContext = ViewContext.HttpContext; 36 | string JSCode = httpContext.Configure(RequestId); 37 | 38 | output.Content.SetHtmlContent(JSCode); 39 | 40 | output.TagMode = TagMode.StartTagAndEndTag; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/FinalLogData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog 7 | { 8 | public class FinalLogData 9 | { 10 | public ILogRequest LogRequest { get; private set; } 11 | 12 | public string FinalLogger { get; set; } 13 | public Level FinalLevel { get; set; } 14 | public string FinalMessage { get; set; } 15 | public string ServerSideMessageFormat { get; internal set; } 16 | 17 | public FinalLogData(ILogRequest logRequest) 18 | { 19 | LogRequest = logRequest; 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return string.Format( 25 | "FinalLogger: {0}, FinalLevel: {1}, FinalMessage: {2}, ServerSideMessageFormat: {3}, LogRequest: {{{4}}}", 26 | FinalLogger, FinalLevel, FinalMessage, ServerSideMessageFormat, LogRequest); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/ILogRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace JSNLog 5 | { 6 | public interface ILogRequest 7 | { 8 | string Message { get; } 9 | string Logger { get; } 10 | string Level { get; } 11 | DateTime UtcDate { get; } 12 | string JsonMessage { get; } 13 | 14 | string UserAgent { get; } 15 | string UserHostAddress { get; } 16 | string RequestId { get; } 17 | string Url { get; } 18 | 19 | Dictionary QueryParameters { get; } 20 | Dictionary Cookies { get; } 21 | Dictionary Headers { get; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/ILoggingAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using JSNLog; 6 | 7 | namespace JSNLog 8 | { 9 | public interface ILoggingAdapter 10 | { 11 | void Log(FinalLogData finalLogData); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/JavascriptLogging.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Xml; 4 | using JSNLog.Exceptions; 5 | using JSNLog.Infrastructure; 6 | using JSNLog.LogHandling; 7 | #if NETFRAMEWORK 8 | using System.Web; 9 | #endif 10 | using Microsoft.AspNetCore.Http; 11 | 12 | namespace JSNLog 13 | { 14 | public static class JavascriptLogging 15 | { 16 | public static string Configure(this Microsoft.AspNetCore.Http.HttpContext httpContext, string requestId = null) 17 | { 18 | return Configure( 19 | httpContext.Wrapper().GetUserIp(), 20 | string.IsNullOrEmpty(requestId) ? httpContext.Wrapper().GetRequestId() : requestId); 21 | } 22 | 23 | public static string Configure(string userIp, string requestId = null) 24 | { 25 | StringBuilder sb = new StringBuilder(); 26 | 27 | var configProcessor = new ConfigProcessor(); 28 | 29 | // If someone passes in a null requestId via a ViewBag, then it may be converted to empty string 30 | configProcessor.ProcessRoot( 31 | requestId, 32 | sb, userIp); 33 | 34 | return sb.ToString(); 35 | } 36 | 37 | /// 38 | /// Returns a request id that is unique to this request. 39 | /// 40 | /// However, if the request is a log request from jsnlog.js, than this method returns the requestId travelling 41 | /// in the request. 42 | /// 43 | /// The site can call this method to get the request id for use in server side logging. 44 | /// 45 | /// 46 | public static string RequestId(this Microsoft.AspNetCore.Http.HttpContext httpContext) 47 | { 48 | return httpContext.Wrapper().RequestId(); 49 | } 50 | 51 | // Definitions for the OnLogging event. Search for OnLogging to see how it is used. 52 | public static event LoggingHandler OnLogging; 53 | 54 | internal static void RaiseLoggingEvent(LoggingEventArgs loggingEventArgs) 55 | { 56 | if (OnLogging != null) 57 | { 58 | OnLogging(loggingEventArgs); 59 | } 60 | } 61 | 62 | #region JsnlogConfiguration 63 | 64 | private static JsnlogConfiguration _jsnlogConfiguration = null; 65 | 66 | private static ILoggingAdapter _logger = null; 67 | 68 | internal static JsnlogConfiguration GetJsnlogConfigurationWithoutWebConfig() 69 | { 70 | // If there is no configuration, return the default configuration. 71 | // In that case, assume the user is after the minimal configuration, where the JSNLog middleware 72 | // automatically inserts the jsnlog script tag and config js code in all html responses from the server. 73 | 74 | if (_jsnlogConfiguration == null) 75 | { 76 | return new JsnlogConfiguration() 77 | { 78 | insertJsnlogInHtmlResponses = true, 79 | productionLibraryPath = SiteConstants.CdnJsDownloadUrl 80 | }; 81 | } 82 | 83 | _jsnlogConfiguration.Validate(); 84 | 85 | return _jsnlogConfiguration; 86 | } 87 | 88 | // All unit tests run under DOTNETCLI 89 | public static JsnlogConfiguration GetJsnlogConfiguration() 90 | { 91 | return GetJsnlogConfigurationWithoutWebConfig(); 92 | } 93 | 94 | internal static ILoggingAdapter GetLogger() 95 | { 96 | return _logger; 97 | } 98 | 99 | internal static void SetJsnlogConfigurationWithoutWebConfig( 100 | JsnlogConfiguration jsnlogConfiguration, ILoggingAdapter loggingAdapter = null) 101 | { 102 | _jsnlogConfiguration = jsnlogConfiguration; 103 | 104 | // Never allow setting the logger to null. 105 | // If user only set the configuration (leaving logger at null), don't change the logger. 106 | 107 | if (loggingAdapter != null) 108 | { 109 | _logger = loggingAdapter; 110 | } 111 | } 112 | 113 | // All unit tests run under DOTNETCLI 114 | #if NETFRAMEWORK 115 | internal static void SetJsnlogConfiguration( 116 | Func lxe, JsnlogConfiguration jsnlogConfiguration, ILoggingAdapter logger = null) 117 | { 118 | // Always allow setting the config to null, because GetJsnlogConfiguration retrieves web.config when config is null. 119 | if (jsnlogConfiguration != null) 120 | { 121 | XmlElement xe = lxe(); 122 | if (xe != null) 123 | { 124 | throw new ConflictingConfigException(); 125 | } 126 | } 127 | 128 | SetJsnlogConfigurationWithoutWebConfig(jsnlogConfiguration, logger); 129 | } 130 | #endif 131 | 132 | public static void SetJsnlogConfiguration( 133 | JsnlogConfiguration jsnlogConfiguration, ILoggingAdapter loggingAdapter = null) 134 | { 135 | SetJsnlogConfigurationWithoutWebConfig(jsnlogConfiguration, loggingAdapter); 136 | } 137 | 138 | #endregion 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/JsnlogConfiguration/AjaxAppender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using System.Xml.Serialization; 8 | using JSNLog.Infrastructure; 9 | using JSNLog.ValueInfos; 10 | 11 | namespace JSNLog 12 | { 13 | public class AjaxAppender : Appender, ICanCreateJsonFields, ICanCreateElement 14 | { 15 | [XmlAttribute] 16 | public string url { get; set; } 17 | 18 | public AjaxAppender() 19 | { 20 | // You can set default values here. If an element is not given in the XML or JSON, 21 | // the deserializer will simply not set it. 22 | 23 | // Do not set default for url here. Its default is set in jsnlog.js. 24 | } 25 | 26 | // -------------------------------------------------------- 27 | 28 | protected string FieldUrl { get { return "url"; } } 29 | 30 | public void CreateElement(StringBuilder sb, Dictionary appenderNames, int sequence, Func virtualToAbsoluteFunc) 31 | { 32 | CreateAppender(sb, appenderNames, sequence, virtualToAbsoluteFunc, "createAjaxAppender", "ajaxAppender"); 33 | } 34 | 35 | // Implement ICanCreateJsonFields 36 | public override void AddJsonFields(IList jsonFields, Dictionary appenderNames, Func virtualToAbsoluteFunc) 37 | { 38 | JavaScriptHelpers.AddJsonField(jsonFields, FieldUrl, url, new UrlValue(virtualToAbsoluteFunc)); 39 | 40 | base.AddJsonFields(jsonFields, appenderNames, virtualToAbsoluteFunc); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/JsnlogConfiguration/Appender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using System.Xml.Serialization; 8 | using JSNLog.Exceptions; 9 | using JSNLog.Infrastructure; 10 | using JSNLog.ValueInfos; 11 | 12 | namespace JSNLog 13 | { 14 | public class Appender : FilterOptions, ICanCreateJsonFields 15 | { 16 | [XmlAttribute] 17 | public string name { get; set; } 18 | 19 | [XmlAttribute] 20 | public string sendWithBufferLevel { get; set; } 21 | 22 | [XmlAttribute] 23 | public string storeInBufferLevel { get; set; } 24 | 25 | [XmlAttribute] 26 | public uint bufferSize { get; set; } 27 | 28 | [XmlAttribute] 29 | public uint batchSize { get; set; } 30 | 31 | [XmlAttribute] 32 | public uint maxBatchSize { get; set; } 33 | 34 | [XmlAttribute] 35 | public uint batchTimeout { get; set; } 36 | 37 | [XmlAttribute] 38 | public uint sendTimeout { get; set; } 39 | 40 | public Appender() 41 | { 42 | // Do NOT set defaults for level, storeInBufferLevel, sendWithBufferLevel. 43 | // Method ValidateAppender checks if all of these have been given by the user 44 | // when at least one has been given (you must set either none or all). 45 | // It can't distinguish between user supplied values and defaults. 46 | // Note that jsnlog.js sets defaults itself. 47 | 48 | bufferSize = 0; 49 | batchSize = 1; 50 | maxBatchSize = 20; 51 | batchTimeout = 2147483647; 52 | sendTimeout = 5000; 53 | } 54 | 55 | // -------------------------------------------------------- 56 | 57 | protected string FieldName { get { return "name"; } } 58 | protected string FieldSendWithBufferLevel { get { return "sendWithBufferLevel"; } } 59 | protected string FieldStoreInBufferLevel { get { return "storeInBufferLevel"; } } 60 | protected string FieldBufferSize { get { return "bufferSize"; } } 61 | protected string FieldBatchSize { get { return "batchSize"; } } 62 | protected string FieldMaxBatchSize { get { return "maxBatchSize"; } } 63 | protected string FieldBatchTimeout { get { return "batchTimeout"; } } 64 | protected string FieldSendTimeout { get { return "sendTimeout"; } } 65 | 66 | protected void CreateAppender(StringBuilder sb, Dictionary appenderNames, int sequence, 67 | Func virtualToAbsoluteFunc, string jsCreateMethodName, string configurationObjectName) 68 | { 69 | try 70 | { 71 | ValidateAppender(configurationObjectName); 72 | 73 | string appenderVariableName = string.Format("{0}{1}", Constants.JsAppenderVariablePrefix, sequence); 74 | appenderNames[name] = appenderVariableName; 75 | 76 | JavaScriptHelpers.GenerateCreate(appenderVariableName, jsCreateMethodName, name, sb); 77 | 78 | JavaScriptHelpers.GenerateSetOptions(appenderVariableName, this, appenderNames, virtualToAbsoluteFunc, 79 | sb, null); 80 | } 81 | catch (Exception e) 82 | { 83 | throw new ConfigurationException(name, e); 84 | } 85 | } 86 | 87 | private void ValidateAppender(string configurationObjectName) 88 | { 89 | if (string.IsNullOrEmpty(name)) 90 | { 91 | throw new MissingAttributeException(configurationObjectName, FieldName); 92 | } 93 | 94 | if (maxBatchSize < batchSize) 95 | { 96 | throw new GeneralAppenderException(name, 97 | string.Format("maxBatchSize ({0}) is smaller than batchSize ({1})", maxBatchSize, batchSize)); 98 | } 99 | 100 | // Ensure that if any of the buffer specific attributes are provided, they are all provided, and that they make sense. 101 | 102 | bool levelGiven = !string.IsNullOrEmpty(level); 103 | bool sendWithBufferLevelGiven = !string.IsNullOrEmpty(sendWithBufferLevel); 104 | bool storeInBufferLevelGiven = !string.IsNullOrEmpty(storeInBufferLevel); 105 | bool bufferSizeGiven = (bufferSize > 0); 106 | 107 | if (sendWithBufferLevelGiven || 108 | storeInBufferLevelGiven || 109 | bufferSizeGiven) 110 | { 111 | if ((!sendWithBufferLevelGiven) || 112 | (!storeInBufferLevelGiven) || 113 | (!bufferSizeGiven)) 114 | { 115 | throw new GeneralAppenderException(name, string.Format( 116 | "If any of {0}, {1} or {2} is specified, than the other two need to be specified as well", 117 | FieldSendWithBufferLevel, FieldStoreInBufferLevel, FieldBufferSize)); 118 | } 119 | 120 | int levelNumber = 121 | levelGiven ? 122 | LevelUtils.LevelNumber(level) : 123 | (int)Constants.DefaultAppenderLevel; 124 | 125 | int storeInBufferLevelNumber = LevelUtils.LevelNumber(storeInBufferLevel); 126 | int sendWithBufferLevelNumber = LevelUtils.LevelNumber(sendWithBufferLevel); 127 | 128 | if ((storeInBufferLevelNumber > levelNumber) || (levelNumber > sendWithBufferLevelNumber)) 129 | { 130 | throw new GeneralAppenderException(name, string.Format( 131 | "{0} must be equal or greater than {1} and equal or smaller than {2}", 132 | FieldLevel, FieldStoreInBufferLevel, FieldSendWithBufferLevel)); 133 | } 134 | } 135 | } 136 | 137 | // Implement ICanCreateJsonFields 138 | public override void AddJsonFields(IList jsonFields, Dictionary appenderNames, Func virtualToAbsoluteFunc) 139 | { 140 | var levelValue = new LevelValue(); 141 | 142 | JavaScriptHelpers.AddJsonField(jsonFields, FieldSendWithBufferLevel, sendWithBufferLevel, levelValue); 143 | JavaScriptHelpers.AddJsonField(jsonFields, FieldStoreInBufferLevel, storeInBufferLevel, levelValue); 144 | JavaScriptHelpers.AddJsonField(jsonFields, FieldBufferSize, bufferSize); 145 | JavaScriptHelpers.AddJsonField(jsonFields, FieldBatchSize, batchSize); 146 | JavaScriptHelpers.AddJsonField(jsonFields, FieldMaxBatchSize, maxBatchSize); 147 | JavaScriptHelpers.AddJsonField(jsonFields, FieldBatchTimeout, batchTimeout); 148 | JavaScriptHelpers.AddJsonField(jsonFields, FieldSendTimeout, sendTimeout); 149 | 150 | base.AddJsonFields(jsonFields, appenderNames, virtualToAbsoluteFunc); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/JsnlogConfiguration/ConsoleAppender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using System.Xml.Serialization; 8 | 9 | namespace JSNLog 10 | { 11 | public class ConsoleAppender : Appender, ICanCreateJsonFields, ICanCreateElement 12 | { 13 | // Implement ICanCreateElement 14 | public void CreateElement(StringBuilder sb, Dictionary appenderNames, int sequence, Func virtualToAbsoluteFunc) 15 | { 16 | CreateAppender(sb, appenderNames, sequence, virtualToAbsoluteFunc, "createConsoleAppender", "consoleAppender"); 17 | } 18 | 19 | // Implement ICanCreateJsonFields 20 | public override void AddJsonFields(IList jsonFields, Dictionary appenderNames, Func virtualToAbsoluteFunc) 21 | { 22 | base.AddJsonFields(jsonFields, appenderNames, virtualToAbsoluteFunc); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/JsnlogConfiguration/FilterOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using System.Xml.Serialization; 8 | using JSNLog.Infrastructure; 9 | using JSNLog.ValueInfos; 10 | 11 | namespace JSNLog 12 | { 13 | public class FilterOptions: ICanCreateJsonFields 14 | { 15 | [XmlAttribute] 16 | public string level { get; set; } 17 | 18 | [XmlAttribute] 19 | public string ipRegex { get; set; } 20 | 21 | [XmlAttribute] 22 | public string userAgentRegex { get; set; } 23 | 24 | [XmlAttribute] 25 | public string disallow { get; set; } 26 | 27 | // -------------------------------------------------------- 28 | 29 | protected string FieldLevel { get { return "level"; } } 30 | 31 | // Implement ICanCreateJsonFields 32 | public virtual void AddJsonFields(IList jsonFields, Dictionary appenderNames, 33 | Func virtualToAbsoluteFunc) 34 | { 35 | var stringValue = new StringValue(); 36 | 37 | JavaScriptHelpers.AddJsonField(jsonFields, FieldLevel, level, new LevelValue()); 38 | JavaScriptHelpers.AddJsonField(jsonFields, "ipRegex", ipRegex, stringValue); 39 | JavaScriptHelpers.AddJsonField(jsonFields, "userAgentRegex", userAgentRegex, stringValue); 40 | JavaScriptHelpers.AddJsonField(jsonFields, "disallow", disallow, stringValue); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/JsnlogConfiguration/ICanCreateElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog 7 | { 8 | /// 9 | /// Elements (loggers, etc.) that implement this interface can generate JavaScript that 10 | /// creates the element. 11 | /// 12 | /// These elements must have a display name that can be used in exceptions. 13 | /// 14 | public interface ICanCreateElement 15 | { 16 | /// 17 | /// Creates JavaScript code that creates the element. For example JL("loggername").setOptions(...); 18 | /// for a logger. 19 | /// 20 | /// 21 | /// The JavaScript code is added to this. 22 | /// 23 | /// 24 | /// Provides mapping from appender configuration names to their JavaScript names. 25 | /// Appenders add their details to this. 26 | /// 27 | /// 28 | /// Every call to this method receives a unique sequence number. 29 | /// Used to create unique JavaScript names. 30 | /// 31 | /// 32 | /// Used to translate virtual paths. 33 | /// 34 | void CreateElement(StringBuilder sb, Dictionary appenderNames, 35 | int sequence, Func virtualToAbsoluteFunc); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/JsnlogConfiguration/ICanCreateJsonFields.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog 7 | { 8 | /// 9 | /// Classes (loggers, etc.) that implement this interface can generate 10 | /// JSON name-value fields based on their properties. These fields are used in the 11 | /// JSON object passed to setOptions. 12 | /// 13 | public interface ICanCreateJsonFields 14 | { 15 | /// 16 | /// Creates JSON fields for a JSON object that will be passed to setOptions 17 | /// for the element (logger, etc.) that implementes this interface. 18 | /// 19 | /// 20 | /// The JSON fields are added to this. 21 | /// 22 | /// 23 | /// 24 | void AddJsonFields(IList jsonFields, Dictionary appenderNames, 25 | Func virtualToAbsoluteFunc); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/JsnlogConfiguration/JsnlogConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using System.Xml.Serialization; 8 | using JSNLog.Infrastructure; 9 | using JSNLog.ValueInfos; 10 | using JSNLog.Exceptions; 11 | 12 | namespace JSNLog 13 | { 14 | // JsnlogConfiguration has to still have the XmlAttribute attributes, because the unit tests 15 | // still use XML definitions. Once those are replaced by C# definitions, the Xml attributes 16 | // can go. 17 | [XmlRoot("jsnlog")] 18 | public class JsnlogConfiguration : ICanCreateJsonFields 19 | { 20 | [XmlAttribute] 21 | public bool enabled { get; set; } 22 | 23 | [XmlAttribute] 24 | public uint maxMessages { get; set; } 25 | 26 | [XmlAttribute] 27 | public string defaultAjaxUrl { get; set; } 28 | 29 | [XmlAttribute] 30 | public string corsAllowedOriginsRegex { get; set; } 31 | 32 | [XmlAttribute] 33 | public string corsAllowedHeaders { get; set; } 34 | 35 | [XmlAttribute] 36 | public string serverSideLogger { get; set; } 37 | 38 | [XmlAttribute] 39 | public string serverSideLevel { get; set; } 40 | 41 | [XmlAttribute] 42 | public string serverSideMessageFormat { get; set; } 43 | 44 | [XmlAttribute] 45 | public string dateFormat { get; set; } 46 | 47 | [XmlAttribute] 48 | public string productionLibraryPath { get; set; } 49 | 50 | [XmlAttribute] 51 | private bool _insertJsnlogInHtmlResponses = false; 52 | 53 | public bool insertJsnlogInHtmlResponses 54 | { 55 | get 56 | { 57 | return _insertJsnlogInHtmlResponses; 58 | } 59 | set 60 | { 61 | #if NETFRAMEWORK 62 | if (value) 63 | { 64 | throw new Exception( 65 | "The JsnlogConfiguration.insertJsnlogInHtmlResponses property cannot be set to true in netstandard2.0. " + 66 | $"Upgrade to netstandard2.1 or for other options see {SiteConstants.InstallPageUrl}"); 67 | } 68 | #endif 69 | 70 | _insertJsnlogInHtmlResponses = value; 71 | } 72 | } 73 | 74 | // Be sure to make everything Properties. While the XML serializer handles fields ok, 75 | // the JSON serializer used in ASP.NET 5 doesn't. 76 | 77 | [XmlElement("logger")] 78 | public List loggers { get; set; } 79 | 80 | [XmlElement("ajaxAppender")] 81 | public List ajaxAppenders { get; set; } 82 | 83 | [XmlElement("consoleAppender")] 84 | public List consoleAppenders { get; set; } 85 | 86 | public JsnlogConfiguration() 87 | { 88 | // Set default values. If an element is not given in the XML or JSON, 89 | // the deserializer will simply not set it. 90 | enabled = true; 91 | maxMessages = int.MaxValue; 92 | serverSideMessageFormat = "%message"; 93 | dateFormat = "o"; 94 | 95 | // Do not set default for defaultAjaxUrl here. Its default is set in jsnlog.js. 96 | } 97 | 98 | // -------------------------------------------------------- 99 | 100 | protected string FieldEnabled { get { return "enabled"; } } 101 | protected string FieldMaxMessages { get { return "maxMessages"; } } 102 | protected string FieldDefaultAjaxUrl { get { return "defaultAjaxUrl"; } } 103 | 104 | // Implement ICanCreateJsonFields 105 | public void AddJsonFields(IList jsonFields, Dictionary appenderNames, Func virtualToAbsoluteFunc) 106 | { 107 | try 108 | { 109 | JavaScriptHelpers.AddJsonField(jsonFields, FieldEnabled, enabled); 110 | JavaScriptHelpers.AddJsonField(jsonFields, FieldMaxMessages, maxMessages); 111 | JavaScriptHelpers.AddJsonField(jsonFields, FieldDefaultAjaxUrl, defaultAjaxUrl, new UrlValue(virtualToAbsoluteFunc)); 112 | } 113 | catch (Exception e) 114 | { 115 | string displayName = "jsnlog library"; 116 | throw new ConfigurationException(displayName, e); 117 | } 118 | } 119 | 120 | public void Validate() 121 | { 122 | var appenders = new List(); 123 | 124 | if (ajaxAppenders != null) 125 | { 126 | appenders.AddRange(ajaxAppenders); 127 | } 128 | 129 | if (consoleAppenders != null) 130 | { 131 | appenders.AddRange(consoleAppenders); 132 | } 133 | 134 | if (appenders.Any(a=>string.IsNullOrWhiteSpace(a.name))) 135 | { 136 | throw new GeneralAppenderException(@"""""", "Appenders have to have a name, and it must not be empty."); 137 | } 138 | 139 | var appendersNames = appenders.Select(a => a.name); 140 | 141 | string duplicateName = appendersNames.GroupBy(a => a).Where(g => g.Skip(1).Any()).SelectMany(a => a).FirstOrDefault(); 142 | if (duplicateName != null) 143 | { 144 | throw new GeneralAppenderException(duplicateName, "There are multiple appenders with this name. However, appender names must be unique."); 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/JsnlogConfiguration/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using System.Xml.Serialization; 8 | using JSNLog.Infrastructure; 9 | using JSNLog.Exceptions; 10 | using JSNLog.ValueInfos; 11 | 12 | namespace JSNLog 13 | { 14 | public class Logger : FilterOptions, ICanCreateJsonFields, ICanCreateElement 15 | { 16 | [XmlAttribute] 17 | public string appenders { get; set; } 18 | 19 | [XmlAttribute] 20 | public string name { get; set; } 21 | 22 | [XmlElement("onceOnly")] 23 | public List onceOnlies { get; set; } 24 | 25 | // -------------------------------------------------------- 26 | 27 | protected string ElementOnceOnly { get { return "onceOnly"; } } 28 | protected string FieldRegex { get { return "regex"; } } 29 | 30 | protected string FieldAppenders { get { return "appenders"; } } 31 | protected string FieldOnceOnly { get { return "onceOnly"; } } 32 | 33 | // Implement ICanCreateElement 34 | public void CreateElement(StringBuilder sb, Dictionary appenderNames, int sequence, 35 | Func virtualToAbsoluteFunc) 36 | { 37 | try 38 | { 39 | string jsVariableName = string.Format("{0}{1}", Constants.JsLoggerVariablePrefix, sequence); 40 | JavaScriptHelpers.GenerateLogger(jsVariableName, name, sb); 41 | 42 | JavaScriptHelpers.GenerateSetOptions(jsVariableName, this, appenderNames, virtualToAbsoluteFunc, 43 | sb, null); 44 | } 45 | catch (Exception e) 46 | { 47 | string displayName = String.IsNullOrEmpty(name) ? "" : name; ; 48 | throw new ConfigurationException(displayName, e); 49 | } 50 | } 51 | 52 | // Implement ICanCreateJsonFields 53 | public override void AddJsonFields(IList jsonFields, Dictionary appenderNames, Func virtualToAbsoluteFunc) 54 | { 55 | JavaScriptHelpers.AddJsonField(jsonFields, FieldAppenders, appenders, new AppendersValue(appenderNames)); 56 | 57 | if (onceOnlies != null) 58 | { 59 | // Note that regex on a onceOnly object can be null (that is, not given in the config). 60 | // This allows user to specify an empty list of onceOnlies, to override the onceOnlies of 61 | // the parent logger. See http://jsnlog.com/Documentation/WebConfig/JSNLog/Logger 62 | JavaScriptHelpers.AddJsonField(jsonFields, FieldOnceOnly, 63 | onceOnlies.Select(o=>o.regex), new StringValue()); 64 | } 65 | 66 | base.AddJsonFields(jsonFields, appenderNames, virtualToAbsoluteFunc); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/JsnlogConfiguration/OnceOnlyOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | using System.Xml.Serialization; 8 | 9 | namespace JSNLog 10 | { 11 | public class OnceOnlyOptions 12 | { 13 | #if NETFRAMEWORK 14 | [XmlAttribute] 15 | #endif 16 | public string regex { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/LoggingEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog 7 | { 8 | public class LoggingEventArgs : FinalLogData 9 | { 10 | public bool Cancel { get; set; } 11 | 12 | public LoggingEventArgs(ILogRequest logRequest) 13 | : base(logRequest) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Configuration/LoggingHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog 7 | { 8 | public delegate void LoggingHandler(LoggingEventArgs e); 9 | } 10 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Exceptions/ConfigurationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog.Exceptions 7 | { 8 | public class ConfigurationException : JSNLogException 9 | { 10 | public ConfigurationException(string elementName, Exception innerException) : 11 | base(string.Format("Error in configuration for {0}. See inner exception", elementName), innerException) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Exceptions/ConflictingConfigException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog.Exceptions 7 | { 8 | public class ConflictingConfigException : JSNLogException 9 | { 10 | public ConflictingConfigException() : 11 | base("To prevent conflicting configurations, you cannot set the JsnlogConfiguration property if there is a jsnlog element in your web.config.", null) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Exceptions/GeneralAppenderException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog.Exceptions 7 | { 8 | public class GeneralAppenderException : JSNLogException 9 | { 10 | public GeneralAppenderException(string appenderName, string message) : 11 | base(string.Format("Appender {0} - {1}", 12 | appenderName, message)) 13 | { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Exceptions/InvalidAttributeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Xml; 5 | 6 | namespace JSNLog.Exceptions 7 | { 8 | public class InvalidAttributeException : JSNLogException 9 | { 10 | public InvalidAttributeException(string invalidValue) : 11 | base(string.Format("Invalid value {0}", invalidValue)) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Exceptions/JSNLogException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog.Exceptions 7 | { 8 | public class JSNLogException : Exception 9 | { 10 | public JSNLogException(string message, Exception innerException = null) : 11 | base(string.Format("{0} - {1}", Constants.PackageName, message), innerException) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Exceptions/MissingAttributeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Xml; 6 | 7 | namespace JSNLog.Exceptions 8 | { 9 | public class MissingAttributeException : JSNLogException 10 | { 11 | public MissingAttributeException(string className, string missingPropertyName): 12 | base(string.Format("Missing attribute {0} in {1}", 13 | missingPropertyName, className)) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Exceptions/MissingRootTagException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog.Exceptions 7 | { 8 | public class MissingRootTagException : JSNLogException 9 | { 10 | public MissingRootTagException(): 11 | base(string.Format( 12 | "Missing root tag - In web.config, there is no {0} tag.", Constants.ConfigRootName)) 13 | { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Exceptions/PropertyException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog.Exceptions 7 | { 8 | public class PropertyException : JSNLogException 9 | { 10 | public PropertyException(string propertyName, Exception innerException) : 11 | base(string.Format("Bad value for property {0}. See inner exception", propertyName), innerException) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Exceptions/UnknownAppenderException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog.Exceptions 7 | { 8 | public class UnknownAppenderException : JSNLogException 9 | { 10 | public UnknownAppenderException(string unknownAppender) : 11 | base(string.Format("Unknown appender {0} - In web.config, there is a reference to an appender with name '{0}', but that appender is not defined", 12 | unknownAppender)) 13 | { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Exceptions/UnknownRootTagException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog.Exceptions 7 | { 8 | public class UnknownRootTagException : JSNLogException 9 | { 10 | public UnknownRootTagException(string unknownTag): 11 | base(string.Format( 12 | "Unknown root tag {0} - In web.config, an element with name {1} should be used to configure {2}. " + 13 | "Instead, {0} is used.", unknownTag, Constants.ConfigRootName, Constants.PackageName)) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Exceptions/WebConfigException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog.Exceptions 7 | { 8 | public class WebConfigException : JSNLogException 9 | { 10 | public WebConfigException(Exception innerException) : 11 | base(string.Format("Error in {0} element or one of its children in your web.config. See inner exception", Constants.ConfigRootName), innerException) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jsnlog/PublicFacing/Level.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog 7 | { 8 | public enum Level 9 | { 10 | ALL = -2147483648, 11 | TRACE = 1000, 12 | DEBUG = 2000, 13 | INFO = 3000, 14 | WARN = 4000, 15 | ERROR = 5000, 16 | FATAL = 6000, 17 | OFF = 2147483647 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /jsnlog/ValueInfos/AppendersValue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using JSNLog.Exceptions; 4 | 5 | namespace JSNLog.ValueInfos 6 | { 7 | internal class AppendersValue : IValueInfo 8 | { 9 | private Dictionary _appenderNames = null; 10 | 11 | public AppendersValue(Dictionary appenderNames) 12 | { 13 | _appenderNames = appenderNames; 14 | } 15 | 16 | public string ToJavaScript(string text) 17 | { 18 | if (string.IsNullOrEmpty(text)) 19 | { 20 | return "[]"; 21 | } 22 | 23 | string[] appenderNames = text.Split(new[] { Constants.AppenderNameSeparator }); 24 | string[] appenderVariableNames = appenderNames.Select(a => { 25 | string trimmed = a.Trim(); 26 | if (!_appenderNames.ContainsKey(trimmed)) {throw new UnknownAppenderException(trimmed);} 27 | string appenderVariable = _appenderNames[trimmed]; 28 | return appenderVariable; 29 | }).ToArray(); 30 | 31 | string js = "[" + string.Join(",", appenderVariableNames) + "]"; 32 | return js; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /jsnlog/ValueInfos/IValueInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace JSNLog.ValueInfos 7 | { 8 | /// 9 | /// This describes a class that describes how to handle values. 10 | /// 11 | internal interface IValueInfo 12 | { 13 | /// 14 | /// Takes a value and converts it to a JavaScript value. 15 | /// Note that this method takes care of quoting strings (and not quoting numbers and booleans). 16 | /// 17 | /// 18 | /// 19 | string ToJavaScript(string text); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jsnlog/ValueInfos/LevelValue.cs: -------------------------------------------------------------------------------- 1 | using JSNLog.Infrastructure; 2 | 3 | namespace JSNLog.ValueInfos 4 | { 5 | internal class LevelValue : IValueInfo 6 | { 7 | public string ToJavaScript(string text) 8 | { 9 | string js = LevelUtils.LevelNumber(text).ToString(); 10 | return js; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jsnlog/ValueInfos/StringValue.cs: -------------------------------------------------------------------------------- 1 |  2 | using JSNLog.Infrastructure; 3 | 4 | namespace JSNLog.ValueInfos 5 | { 6 | internal class StringValue : IValueInfo 7 | { 8 | public string ToJavaScript(string text) 9 | { 10 | return HtmlHelpers.JavaScriptStringEncode(text, true); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jsnlog/ValueInfos/UrlValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.RegularExpressions; 3 | using JSNLog.Exceptions; 4 | using JSNLog.Infrastructure; 5 | 6 | namespace JSNLog.ValueInfos 7 | { 8 | internal class UrlValue : IValueInfo 9 | { 10 | private static Regex regexUrl = new Regex(Constants.RegexUrl); 11 | 12 | Func _virtualToAbsoluteFunc = null; 13 | public UrlValue(Func virtualToAbsoluteFunc) 14 | { 15 | _virtualToAbsoluteFunc = virtualToAbsoluteFunc; 16 | } 17 | 18 | public string ToJavaScript(string text) 19 | { 20 | if (!regexUrl.IsMatch(text)) 21 | { 22 | throw new InvalidAttributeException(text); 23 | } 24 | 25 | string resolvedUrl = Utils.AbsoluteUrl(text, _virtualToAbsoluteFunc); 26 | return HtmlHelpers.JavaScriptStringEncode(resolvedUrl, true); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /jsnlog/jsnlog.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0;netstandard2.1;netcoreapp3.1;net5.0 4 | JSNLog - JavaScript Logging Package for ASP.NET CORE 2.0+ 5 | JSNLog 6 | MIT 7 | 0.0.0 8 | Mattijs Perdeck 9 | 10 | JavaScript logging package that lets you log exceptions, AJAX timeouts and other client side events in your server side log. Supports .Net Core 2.0+ only. 11 | 12 | For ASP.NET 4.x (.Net 4.5.2+), install version 2.30.0 of this package. 13 | 14 | false 15 | http://jsnlog.com 16 | icon.png 17 | https://github.com/mperdeck?tab=repositories&q=jsnlog&type=&language= 18 | Copyright 2023 Mattijs Perdeck 19 | JavaScript logging exceptions ajax ASP.NET CORE .NETStandard 20 | True 21 | jsnlog.strongname.snk 22 | False 23 | 24 | 25 | 26 | NETFRAMEWORK 27 | 28 | 29 | 30 | NETCORE2 31 | 32 | 33 | 34 | NETCORE3 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | true 55 | readme.txt 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /jsnlog/jsnlog.strongname.PublicKey: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mperdeck/jsnlog/bc6cfc091c4baa1574bfdc680baba7621fd69164/jsnlog/jsnlog.strongname.PublicKey -------------------------------------------------------------------------------- /jsnlog/jsnlog.strongname.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mperdeck/jsnlog/bc6cfc091c4baa1574bfdc680baba7621fd69164/jsnlog/jsnlog.strongname.snk --------------------------------------------------------------------------------