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);
}
}