├── .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