diff --git a/samples/ContextAwareServiceSample/ContextAwareServiceSample.csproj b/samples/ContextAwareServiceSample/ContextAwareServiceSample.csproj new file mode 100644 index 0000000..52ba25a --- /dev/null +++ b/samples/ContextAwareServiceSample/ContextAwareServiceSample.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + diff --git a/samples/ContextAwareServiceSample/Program.cs b/samples/ContextAwareServiceSample/Program.cs new file mode 100644 index 0000000..dfa50d0 --- /dev/null +++ b/samples/ContextAwareServiceSample/Program.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Serilog; + +namespace ContextAwareServiceSample +{ + public static class Program + { + public static int Main(string[] args) + { + Log.Logger = new LoggerConfiguration() + .Enrich.FromLogContext() + .WriteTo.Console() + .CreateBootstrapLogger(); + + try { + Log.Information("Getting the motors running..."); + CreateHostBuilder(args).Build().Run(); + return 0; + } + catch (Exception ex) { + Log.Fatal(ex, "Host terminated unexpectedly"); + return 1; + } + finally { + Log.CloseAndFlush(); + } + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + services.AddHostedService() + .AddSingleton()) + .UseSerilog((context, services, loggerConfiguration) => loggerConfiguration + .ReadFrom.Configuration(context.Configuration) + .WriteTo.Seq("http://localhost:5341") + .Enrich.FromLogContext() + .WriteTo.Console()); + } +} \ No newline at end of file diff --git a/samples/ContextAwareServiceSample/Properties/launchSettings.json b/samples/ContextAwareServiceSample/Properties/launchSettings.json new file mode 100644 index 0000000..fd650fa --- /dev/null +++ b/samples/ContextAwareServiceSample/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "ContextAwareServiceSample": { + "commandName": "Project", + "dotnetRunMessages": "true", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/samples/ContextAwareServiceSample/Worker.cs b/samples/ContextAwareServiceSample/Worker.cs new file mode 100644 index 0000000..5d8ec14 --- /dev/null +++ b/samples/ContextAwareServiceSample/Worker.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog; +using ILogger = Serilog.ILogger; + +namespace ContextAwareServiceSample +{ + public class Worker : BackgroundService + { + readonly WorkExecutor _executor; + readonly IDiagnosticContext _diagnosticContext; + + public Worker(WorkExecutor executor, IDiagnosticContext diagnosticContext) + { + _executor = executor; + _diagnosticContext = diagnosticContext; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) { + var outerOpId = Guid.NewGuid(); + using (_diagnosticContext.Begin( + "Worker executed operation {OperationId} at time {Time}", + outerOpId, DateTimeOffset.Now)) { + await _executor.DoWork(outerOpId); + + var innerOpId = Guid.NewGuid(); + using (_diagnosticContext.Begin( + "Worker executed inner operation {OperationId} at time {Time}", + innerOpId, + DateTimeOffset.Now)) { + await _executor.DoWork(innerOpId); + } + } + + await Task.Delay(1000, stoppingToken); + } + } + } + + public class WorkExecutor + { + readonly IDiagnosticContext _diagnosticContext; + + public WorkExecutor(IDiagnosticContext diagnosticContext) + { + _diagnosticContext = diagnosticContext; + } + + public async Task DoWork(Guid operationId) + { + var R = new Random(); + + var operationNames = new[] { "SaveUser", "LoadUser", "AddRole", "RemoveRole", "DeactivateUser" }; + + var randomOperation = operationNames[R.Next(0, operationNames.Length - 1)]; + + _diagnosticContext.Set("OperationName", randomOperation); + _diagnosticContext.Set("ThisOperationId", operationId); + + await Task.Delay(R.Next(100, 1000)); + + Log.Information("Completed operation {OperationId}: {OperationName}", operationId, + randomOperation); + } + } +} \ No newline at end of file diff --git a/samples/ContextAwareServiceSample/appsettings.Development.json b/samples/ContextAwareServiceSample/appsettings.Development.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/samples/ContextAwareServiceSample/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/samples/ContextAwareServiceSample/appsettings.json b/samples/ContextAwareServiceSample/appsettings.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/samples/ContextAwareServiceSample/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/serilog-extensions-hosting.sln b/serilog-extensions-hosting.sln index 2208752..7acc906 100644 --- a/serilog-extensions-hosting.sln +++ b/serilog-extensions-hosting.sln @@ -26,6 +26,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleServiceSample", "samp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApplicationSample", "samples\WebApplicationSample\WebApplicationSample.csproj", "{1ACDCA67-F404-45AB-9348-98E55E03CB8C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ContextAwareServiceSample", "samples\ContextAwareServiceSample\ContextAwareServiceSample.csproj", "{41535D34-1DC8-4BC9-A085-208AF948ED00}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -48,6 +50,10 @@ Global {1ACDCA67-F404-45AB-9348-98E55E03CB8C}.Debug|Any CPU.Build.0 = Debug|Any CPU {1ACDCA67-F404-45AB-9348-98E55E03CB8C}.Release|Any CPU.ActiveCfg = Release|Any CPU {1ACDCA67-F404-45AB-9348-98E55E03CB8C}.Release|Any CPU.Build.0 = Release|Any CPU + {41535D34-1DC8-4BC9-A085-208AF948ED00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41535D34-1DC8-4BC9-A085-208AF948ED00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41535D34-1DC8-4BC9-A085-208AF948ED00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41535D34-1DC8-4BC9-A085-208AF948ED00}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -57,6 +63,7 @@ Global {AD51759B-CD58-473F-9620-0B0E56A123A1} = {E30F638E-BBBE-4AD1-93CE-48CC69CFEFE1} {E5A82756-4619-4E6B-8B26-6D83E00E99F0} = {F2407211-6043-439C-8E06-3641634332E7} {1ACDCA67-F404-45AB-9348-98E55E03CB8C} = {F2407211-6043-439C-8E06-3641634332E7} + {41535D34-1DC8-4BC9-A085-208AF948ED00} = {F2407211-6043-439C-8E06-3641634332E7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {811E61C5-3871-4633-AFAE-B35B619C8A10} diff --git a/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContext.cs b/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContext.cs index 7c12c19..260cb41 100644 --- a/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContext.cs +++ b/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContext.cs @@ -14,6 +14,7 @@ using System; using System.Threading; +using Serilog.Events; namespace Serilog.Extensions.Hosting { @@ -56,5 +57,16 @@ public void Set(string propertyName, object value, bool destructureObjects = fal collector.AddOrUpdate(property); } } + + /// + /// + /// + /// + /// + /// + public DiagnosticContextScope Begin(string messageTemplate, params object[] properties) + { + return new DiagnosticContextScope(this, LogEventLevel.Information, messageTemplate, properties); + } } } diff --git a/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContextScope.cs b/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContextScope.cs new file mode 100644 index 0000000..6bf0191 --- /dev/null +++ b/src/Serilog.Extensions.Hosting/Extensions/Hosting/DiagnosticContextScope.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq; +using Serilog.Events; +using Serilog.Extensions.Hosting; +using Serilog.Parsing; + +namespace Serilog.Extensions +{ + /// + /// + /// + public sealed class DiagnosticContextScope : IDisposable + { + readonly LogEventLevel _level; + readonly string _messageTemplate; + readonly object[] _properties; + readonly DiagnosticContextCollector _collector; + + static readonly LogEventProperty[] NoProperties = new LogEventProperty[0]; + + /// + /// + /// + /// + /// + /// + /// + public DiagnosticContextScope(DiagnosticContext diagnosticContext, + LogEventLevel level, + string messageTemplate, + object[] properties) + { + _level = level; + _messageTemplate = messageTemplate; + _properties = properties; + + _collector = diagnosticContext.BeginCollection(); + } + + /// + /// + /// + public void Dispose() + { + var logger = Log.ForContext(); + + if (!_collector.TryComplete(out var collectedProperties)) + collectedProperties = NoProperties; + + foreach (var collectedProp in collectedProperties) { + logger = logger.ForContext(collectedProp.Name, collectedProp.Value); + } + + logger.Write(_level, (Exception)null, _messageTemplate, _properties); + } + } +} diff --git a/src/Serilog.Extensions.Hosting/IDiagnosticContext.cs b/src/Serilog.Extensions.Hosting/IDiagnosticContext.cs index 468fa84..94cb7e9 100644 --- a/src/Serilog.Extensions.Hosting/IDiagnosticContext.cs +++ b/src/Serilog.Extensions.Hosting/IDiagnosticContext.cs @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Serilog.Events; +using Serilog.Extensions; + namespace Serilog { /// @@ -27,6 +30,12 @@ public interface IDiagnosticContext /// The property value. /// If true, the value will be serialized as structured /// data if possible; if false, the object will be recorded as a scalar or simple array. - void Set(string propertyName, object value, bool destructureObjects = false); + void Set(string propertyName, object value, bool destructureObjects = false); + + /// + /// + /// + /// + DiagnosticContextScope Begin(string messageTemplate, params object[] properties); } }