diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..5e50e15 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,72 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '41 18 * * 4' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'csharp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/README.md b/README.md index 1d03ba2..4e73f10 100644 --- a/README.md +++ b/README.md @@ -51,5 +51,29 @@ This can let you specify a filter on an appender in Log4Net. The example below, ``` +#### Preserving Stacktrace information +Passing `3 (Default is 3)` for skipFrames, will load the relative number of frames from the call stack. This is required to be configurable to allow the caller class, method name, line number to be pulled. This is best used in scenarios where you have your own custom wrapper over Serilog or log4net. + +e.g. + +```csharp +var log = new LoggerConfiguration() + .WriteTo.Log4Net(skipFrames: 3) + .CreateLogger(); +``` +This change allows the correct values returned for log4net conversion template. + +```xml + +``` +Output received in logs when skipFrames is 3 +```txt +[2017-12-19T16:51:53Z}][INFO][1][Serilog.Sinks.Log4Net.Sample.Program(Main),39][SERILOG-custom property added for "ikson01" { firstname: "john", lastname: "doe" }] +``` +Output received in logs when skipFrames is 0 +```txt +[2017-12-19T16:53:12Z}][INFO][1][Serilog.Core.Sinks.SafeAggregateSink(Emit),0][SERILOG-custom property added for "ikson01" { firstname: "john", lastname: "doe" }] +``` + [(More information.)](http://nblumhardt.com/2013/06/serilog-sinks-log4net/) diff --git a/Sample/Serilog.Sinks.Log4Net.Sample/App.config b/Sample/Serilog.Sinks.Log4Net.Sample/App.config index d4e37c5..5d371cd 100644 --- a/Sample/Serilog.Sinks.Log4Net.Sample/App.config +++ b/Sample/Serilog.Sinks.Log4Net.Sample/App.config @@ -1,25 +1,44 @@  - -
- - - + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sample/Serilog.Sinks.Log4Net.Sample/Program.cs b/Sample/Serilog.Sinks.Log4Net.Sample/Program.cs index 9b7aa21..d8dcc12 100644 --- a/Sample/Serilog.Sinks.Log4Net.Sample/Program.cs +++ b/Sample/Serilog.Sinks.Log4Net.Sample/Program.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using log4net; using log4net.Config; +using Serilog.Context; using Serilog.Enrichers; namespace Serilog.Sinks.Log4Net.Sample @@ -22,17 +23,20 @@ private static void Main() Log.Logger = new LoggerConfiguration() .Enrich.With(new ThreadIdEnricher()) .WriteTo.ColoredConsole(outputTemplate: OutputTemplate) - .WriteTo.Log4Net() + .WriteTo.Log4Net(skipFrames: 3) .CreateLogger(); - - var log4NetLogger = LogManager.GetLogger(typeof (Program)); + var log4NetLogger = LogManager.GetLogger(typeof(Program)); var serilogLogger = Log.ForContext(); var username = Environment.UserName; - log4NetLogger.InfoFormat("Hello from log4net, running as {0}!", username); - serilogLogger.Information("Hello from Serilog, running as {Username}!", username); + log4NetLogger.InfoFormat("LOG4NET---Hello, running as {0}!", username); + + serilogLogger.Information("SERILOG---Hello, running as {Username}!", username); + + var p = new { firstname = "john", lastname = "doe" }; + serilogLogger.Information("SERILOG-custom property added for {user} {@p}", username, p); Console.ReadKey(true); } diff --git a/Sample/Serilog.Sinks.Log4Net.Sample/packages.config b/Sample/Serilog.Sinks.Log4Net.Sample/packages.config index 04b58ed..cd2261f 100644 --- a/Sample/Serilog.Sinks.Log4Net.Sample/packages.config +++ b/Sample/Serilog.Sinks.Log4Net.Sample/packages.config @@ -1,6 +1,6 @@  - + diff --git a/src/Serilog.Sinks.Log4Net/LoggerConfigurationLog4NetExtensions.cs b/src/Serilog.Sinks.Log4Net/LoggerConfigurationLog4NetExtensions.cs index 32891d8..6217150 100644 --- a/src/Serilog.Sinks.Log4Net/LoggerConfigurationLog4NetExtensions.cs +++ b/src/Serilog.Sinks.Log4Net/LoggerConfigurationLog4NetExtensions.cs @@ -29,25 +29,30 @@ public static class LoggerConfigurationLog4NetExtensions /// Adds a sink that writes log events as documents to log4net. /// /// The logger configuration. - /// If events are logged using Serilog's method, + /// If events are logged using Serilog's method, /// the type name will be used as the logger name (in line with log4net's behaviour. If no context type is specified, /// Serilog will use this value (which defaults to "serilog") as the logger name. /// The minimum log event level required in order to write an event to the sink. /// Supplies culture-specific formatting information, or null. - /// Whether to supply a marker context message to use during the log. See: https://logging.apache.org/log4net/release/manual/contexts.html#stacks - /// Logger configuration, allowing configuration to continue. + /// Whether to supply a marker context message to use during the log. See: https://logging.apache.org/log4net/release/manual/contexts.html#stacks + /// The skip frames. + /// + /// Logger configuration, allowing configuration to continue. + /// /// A required parameter is null. public static LoggerConfiguration Log4Net( this LoggerSinkConfiguration loggerConfiguration, string defaultLoggerName = "serilog", LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, IFormatProvider formatProvider = null, - bool supplyContextMessage = false) + bool supplyContextMessage = false, int skipFrames = 3) { - if (loggerConfiguration == null) throw new ArgumentNullException("loggerConfiguration"); - if (defaultLoggerName == null) throw new ArgumentNullException("defaultLoggerName"); + if (loggerConfiguration == null) + throw new ArgumentNullException("loggerConfiguration"); + if (defaultLoggerName == null) + throw new ArgumentNullException("defaultLoggerName"); - return loggerConfiguration.Sink(new Log4NetSink(defaultLoggerName, formatProvider, supplyContextMessage), restrictedToMinimumLevel); + return loggerConfiguration.Sink(new Log4NetSink(defaultLoggerName, formatProvider, supplyContextMessage, skipFrames), restrictedToMinimumLevel); } /// diff --git a/src/Serilog.Sinks.Log4Net/Sinks/Log4Net/Log4NetSink.cs b/src/Serilog.Sinks.Log4Net/Sinks/Log4Net/Log4NetSink.cs index dcfa671..9887633 100644 --- a/src/Serilog.Sinks.Log4Net/Sinks/Log4Net/Log4NetSink.cs +++ b/src/Serilog.Sinks.Log4Net/Sinks/Log4Net/Log4NetSink.cs @@ -13,11 +13,13 @@ // limitations under the License. using System; +using System.Diagnostics; using System.Security.AccessControl; using Serilog.Core; using Serilog.Debugging; using Serilog.Events; using log4net; +using log4net.Core; namespace Serilog.Sinks.Log4Net { @@ -25,20 +27,33 @@ class Log4NetSink : ILogEventSink { class NullDisposable : IDisposable { - public void Dispose(){} + public void Dispose() { } } private const string ContextMessage = "Serilog-Log4NetSink"; + private const int DefaultSkipFrames = 3; readonly string _defaultLoggerName; readonly IFormatProvider _formatProvider; private readonly bool _supplyContextMessage; + private readonly int _skipFrames = 3; - public Log4NetSink(string defaultLoggerName, IFormatProvider formatProvider = null, bool supplyContextMessage = false) + public Log4NetSink(string defaultLoggerName, IFormatProvider formatProvider = null, bool supplyContextMessage = false, int skipFrames = DefaultSkipFrames) { - if (defaultLoggerName == null) throw new ArgumentNullException("defaultLoggerName"); + if (defaultLoggerName == null) + throw new ArgumentNullException(nameof(defaultLoggerName)); _defaultLoggerName = defaultLoggerName; _formatProvider = formatProvider; _supplyContextMessage = supplyContextMessage; + if (_skipFrames < 0) + { + throw new ArgumentException(nameof(skipFrames)); + } + _skipFrames = skipFrames; + } + + private Type GetCallingType() + { + return new StackFrame(_skipFrames, false).GetMethod().DeclaringType; } public void Emit(LogEvent logEvent) @@ -57,30 +72,31 @@ public void Emit(LogEvent logEvent) var exception = logEvent.Exception; var logger = LogManager.GetLogger(loggerName); - + using (_supplyContextMessage ? ThreadContext.Stacks["NDC"].Push(ContextMessage) : new NullDisposable()) { + var type = GetCallingType(); switch (logEvent.Level) { case LogEventLevel.Verbose: case LogEventLevel.Debug: - logger.Debug(message, exception); + logger.Logger.Log(type, Level.Debug, message, exception); break; case LogEventLevel.Information: - logger.Info(message, exception); + logger.Logger.Log(type, Level.Info, message, exception); break; case LogEventLevel.Warning: - logger.Warn(message, exception); + logger.Logger.Log(type, Level.Warn, message, exception); break; case LogEventLevel.Error: - logger.Error(message, exception); + logger.Logger.Log(type, Level.Error, message, exception); break; case LogEventLevel.Fatal: - logger.Fatal(message, exception); + logger.Logger.Log(type, Level.Fatal, message, exception); break; default: SelfLog.WriteLine("Unexpected logging level, writing to log4net as Info"); - logger.Info(message, exception); + logger.Logger.Log(type, Level.Info, message, exception); break; } } diff --git a/src/Serilog.Sinks.Log4Net/packages.config b/src/Serilog.Sinks.Log4Net/packages.config index 02f9a68..da30bb3 100644 --- a/src/Serilog.Sinks.Log4Net/packages.config +++ b/src/Serilog.Sinks.Log4Net/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file