diff --git a/src/Arc4u.Diagnostics/Arc4u.Diagnostics.csproj b/src/Arc4u.Diagnostics/Arc4u.Diagnostics.csproj
index ab8a8a51..e86c4951 100644
--- a/src/Arc4u.Diagnostics/Arc4u.Diagnostics.csproj
+++ b/src/Arc4u.Diagnostics/Arc4u.Diagnostics.csproj
@@ -6,12 +6,15 @@
+
+
+
diff --git a/src/Arc4u.Diagnostics/Logging/ApplicationBuilderExtensions.cs b/src/Arc4u.Diagnostics/Logging/ApplicationBuilderExtensions.cs
new file mode 100644
index 00000000..911882da
--- /dev/null
+++ b/src/Arc4u.Diagnostics/Logging/ApplicationBuilderExtensions.cs
@@ -0,0 +1,12 @@
+using Arc4u.Diagnostics.Logging;
+using Microsoft.AspNetCore.Builder;
+
+namespace Arc4u.Diagnostics;
+public static class ApplicationBuilderExtensions
+{
+ public static IApplicationBuilder UseArc4uLogging(this IApplicationBuilder app)
+ {
+ LoggerWrapperContext.Initialize(app.ApplicationServices);
+ return app;
+ }
+}
diff --git a/src/Arc4u.Diagnostics/Logging/HostBuilderExtensions.cs b/src/Arc4u.Diagnostics/Logging/HostBuilderExtensions.cs
new file mode 100644
index 00000000..9d41de8a
--- /dev/null
+++ b/src/Arc4u.Diagnostics/Logging/HostBuilderExtensions.cs
@@ -0,0 +1,20 @@
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace Arc4u.Diagnostics.Logging;
+
+public static class HostBuilderExtensions
+{
+ public static IHostBuilder UseArc4uLogging(
+ this IHostBuilder builder,
+ Action? configure = null)
+ {
+ builder.ConfigureLogging((context, loggingBuilder) =>
+ {
+ var services = loggingBuilder.Services;
+ configure?.Invoke(context, loggingBuilder);
+ });
+
+ return builder;
+ }
+}
diff --git a/src/Arc4u.Diagnostics/Logging/ILoggerExtensions.cs b/src/Arc4u.Diagnostics/Logging/ILoggerExtensions.cs
new file mode 100644
index 00000000..7f384d47
--- /dev/null
+++ b/src/Arc4u.Diagnostics/Logging/ILoggerExtensions.cs
@@ -0,0 +1,25 @@
+using System.Runtime.CompilerServices;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Arc4u.Diagnostics.Logging;
+public static class ILoggerExtensions
+{
+ public static LoggerWrapper Technical(this ILogger logger, [CallerMemberName] string methodName = "") =>
+ new(logger, MessageCategory.Technical, typeof(T), methodName);
+
+ public static LoggerWrapper Business(this ILogger logger, [CallerMemberName] string methodName = "") =>
+ new(logger, MessageCategory.Business, typeof(T), methodName);
+
+ public static LoggerWrapper Monitoring(this ILogger logger, [CallerMemberName] string methodName = "") =>
+ new(logger, MessageCategory.Monitoring, typeof(T), methodName);
+
+ public static LoggerWrapper Technical(this ILogger logger, [CallerMemberName] string methodName = "") =>
+ new(logger, MessageCategory.Technical, typeof(T), methodName);
+
+ public static LoggerWrapper Business(this ILogger logger, [CallerMemberName] string methodName = "") =>
+ new(logger, MessageCategory.Business, typeof(T), methodName);
+
+ public static LoggerWrapper Monitoring(this ILogger logger, [CallerMemberName] string methodName = "") =>
+ new(logger, MessageCategory.Monitoring, typeof(T), methodName);
+}
diff --git a/src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs b/src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs
new file mode 100644
index 00000000..8457af87
--- /dev/null
+++ b/src/Arc4u.Diagnostics/Logging/LoggerWrapper.cs
@@ -0,0 +1,244 @@
+using System.Reflection;
+using System.Text;
+using Arc4u.Diagnostics;
+using Arc4u.Diagnostics.Logging;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.Extensions.DependencyInjection;
+
+public sealed class LoggerWrapper : ILogger
+{
+ private readonly ILogger _logger;
+ private readonly MessageCategory _category;
+ private readonly Dictionary _additionalFields = [];
+ private readonly Type? _contextType;
+ private readonly string _caller = string.Empty;
+ private bool _disposed;
+ private bool _includeStackTrace;
+ private readonly Lazy> _providers = new(Enumerable.Empty().ToList());
+
+ internal Dictionary AdditionalFields => _additionalFields;
+ internal bool IncludeStackTrace { get => _includeStackTrace; set => _includeStackTrace = value; }
+
+ internal static int ProcessId
+ {
+ get
+ {
+ try
+ {
+ return Environment.ProcessId;
+ }
+ catch (PlatformNotSupportedException)
+ {
+ return -1;
+ }
+ }
+ }
+
+ public LoggerWrapper(ILoggerFactory loggerFactory) =>
+ _logger = loggerFactory.CreateLogger();
+
+ internal LoggerWrapper(
+ ILogger logger,
+ MessageCategory category,
+ Type? contextType = null,
+ string caller = "")
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _category = category;
+ _contextType = contextType;
+ _caller = caller;
+
+ _providers = new Lazy>(() =>
+ {
+ using var scope = LoggerWrapperContext.CreateScope();
+ return scope.ServiceProvider.GetServices().ToList();
+ });
+ }
+
+ private static class LoggerMessages
+ {
+ public static readonly EventId TechnicalEventId = new(1000, "Technical");
+ public static readonly EventId BusinessEventId = new(2000, "Business");
+ public static readonly EventId MonitoringEventId = new(3000, "Monitoring");
+
+ public static readonly Func> TechnicalLog =
+ level => Logging.LoggerMessage.Define(
+ level,
+ TechnicalEventId,
+ "{Category} [{Context}] [{@State}] {Message}");
+
+ public static readonly Func> BusinessLog =
+ level => Logging.LoggerMessage.Define(
+ level,
+ BusinessEventId,
+ "{Category} [{Context}] [{@State}] {Message}");
+
+ public static readonly Func> MonitoringLog =
+ level => Logging.LoggerMessage.Define(
+ level,
+ MonitoringEventId,
+ "{Category} [{Context}] [{@State}] {Message}");
+ }
+
+ private void Log(LogLevel level, EventId eventId, string? message, Exception? exception = null)
+ {
+ ThrowIfDisposed();
+
+ if (!IsEnabled(level))
+ {
+ return;
+ }
+
+ try
+ {
+ var properties = AddAdditionalProperties();
+ if (IncludeStackTrace)
+ {
+ properties.AddIfNotExist(LoggingConstants.Stacktrace, exception?.StackTrace ?? Environment.StackTrace);
+ }
+
+ if (exception is not null)
+ {
+ properties.AddIfNotExist(LoggingConstants.UnwrappedException, exception.ToFormattedstring());
+ }
+
+ properties.AddIfNotExist(LoggingConstants.SubEventId, eventId.Id);
+ properties.AddIfNotExist(LoggingConstants.MethodName, _caller);
+ properties.AddIfNotExist(LoggingConstants.Class, _contextType?.FullName ?? nameof(_contextType));
+ properties.AddIfNotExist(LoggingConstants.Category, (short)_category);
+ properties.AddIfNotExist(LoggingConstants.Application, Assembly.GetEntryAssembly()?.GetName().Name ?? "Unknown App");
+ properties.AddIfNotExist(LoggingConstants.ThreadId, Environment.CurrentManagedThreadId);
+ properties.AddIfNotExist(LoggingConstants.ProcessId, ProcessId);
+
+ var enrichedState = new
+ {
+ Caller = _caller,
+ TimeStamp = DateTime.UtcNow,
+ AdditionalFields = properties,
+ Level = level
+ };
+
+ var logAction = _category switch
+ {
+ MessageCategory.Technical => LoggerMessages.TechnicalLog(level),
+ MessageCategory.Business => LoggerMessages.BusinessLog(level),
+ MessageCategory.Monitoring => LoggerMessages.MonitoringLog(level),
+ _ => LoggerMessages.TechnicalLog(level)
+ };
+
+ logAction(
+ _logger,
+ _category.ToString(),
+ _contextType?.Name ?? GetType().Name,
+ enrichedState,
+ message ?? string.Empty,
+ exception);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to log message with property providers and eventId");
+ }
+ }
+
+ internal void ThrowIfDisposed() => ObjectDisposedException.ThrowIf(_disposed, this);
+
+ public void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+ }
+
+ private Dictionary AddAdditionalProperties()
+ {
+ Dictionary properties;
+
+ try
+ {
+ properties = new Dictionary(AdditionalFields);
+ var definedProperties = _providers.Value.Select(x => x.GetProperties()).SelectMany(x => x);
+ if (definedProperties != null)
+ {
+ foreach (var property in definedProperties)
+ {
+ if (property.Value != null)
+ {
+ this.AddIfNotExist(property.Key, property.Value);
+ }
+ }
+
+ return properties;
+ }
+
+ return new Dictionary(AdditionalFields);
+ }
+ catch (Exception ex)
+ {
+ Log(LogLevel.Error, default, "Error getting property providers. Logging without additional properties.", ex);
+ return new Dictionary(AdditionalFields);
+ }
+ }
+
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
+ {
+ if (state is IEnumerable> pairs)
+ {
+ foreach (var pair in pairs)
+ {
+ AdditionalFields.AddOrReplace(pair.Key, pair.Value);
+ }
+ }
+
+ Log(logLevel, eventId, formatter(state, exception), exception);
+ }
+
+ public bool IsEnabled(LogLevel logLevel) => _logger.IsEnabled(logLevel);
+
+ public IDisposable? BeginScope(TState state) where TState : notnull => _logger.BeginScope(state);
+}
+
+internal static class DumpException
+{
+ internal static string ToFormattedstring(this Exception exception)
+ {
+ var messages = exception
+ .GetAllExceptions()
+ .Where(e => !string.IsNullOrWhiteSpace(e.Message))
+ .Select(e => e.GetType().FullName + " : " + e.Message.Trim());
+ var sb = new StringBuilder();
+ var i = 0;
+ foreach (var message in messages)
+ {
+ sb.Append("".PadLeft(i * 4));
+ sb.Append("|---".PadLeft(i++ > 0 ? 4 : 0));
+ sb.AppendLine(message);
+ }
+
+ return sb.ToString();
+ }
+
+ private static IEnumerable GetAllExceptions(this Exception exception)
+ {
+ yield return exception;
+
+ if (exception is AggregateException aggrEx)
+ {
+ foreach (var innerEx in aggrEx.InnerExceptions.SelectMany(e => e.GetAllExceptions()))
+ {
+ yield return innerEx;
+ }
+ }
+ else if (exception.InnerException != null)
+ {
+ foreach (var innerEx in exception.InnerException.GetAllExceptions())
+ {
+ yield return innerEx;
+ }
+ }
+ }
+}
+
diff --git a/src/Arc4u.Diagnostics/Logging/LoggerWrapperContext.cs b/src/Arc4u.Diagnostics/Logging/LoggerWrapperContext.cs
new file mode 100644
index 00000000..a5bd38dd
--- /dev/null
+++ b/src/Arc4u.Diagnostics/Logging/LoggerWrapperContext.cs
@@ -0,0 +1,29 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Arc4u.Diagnostics.Logging;
+internal class LoggerWrapperContext
+{
+ private static Lazy _scopeFactory = default!;
+
+ ///
+ /// Initializes the specified service provider.
+ ///
+ /// The service provider.
+ public static void Initialize(IServiceProvider serviceProvider)
+ => _scopeFactory = new Lazy(serviceProvider.GetRequiredService());
+
+ ///
+ /// Creates the scope.
+ ///
+ ///
+ /// LoggerContext not initialized
+ public static IServiceScope CreateScope()
+ {
+ if (!_scopeFactory.IsValueCreated)
+ {
+ throw new InvalidOperationException("LoggerContext not initialized");
+ }
+
+ return _scopeFactory.Value.CreateScope();
+ }
+}
diff --git a/src/Arc4u.Diagnostics/Logging/LoggerWrapperExtensions.cs b/src/Arc4u.Diagnostics/Logging/LoggerWrapperExtensions.cs
new file mode 100644
index 00000000..a8cc029e
--- /dev/null
+++ b/src/Arc4u.Diagnostics/Logging/LoggerWrapperExtensions.cs
@@ -0,0 +1,142 @@
+using Arc4u.Diagnostics;
+
+namespace Microsoft.Extensions.DependencyInjection;
+
+public static class LoggerWrapperExtensions
+{
+ ///
+ /// Adds the specified key.
+ ///
+ ///
+ /// The logger.
+ /// The key.
+ /// The value.
+ ///
+ public static LoggerWrapper Add(this LoggerWrapper logger, string key, object? value)
+ {
+ logger.ThrowIfDisposed();
+ logger.AdditionalFields[ValidateKey(key)] = value;
+ return logger;
+ }
+
+ ///
+ /// Adds the specified key when condition is true
+ ///
+ ///
+ /// The logger.
+ /// if set to true [condition].
+ /// The key.
+ /// The value.
+ ///
+ public static LoggerWrapper AddIf(this LoggerWrapper logger, bool condition, string key, object? value)
+ {
+ logger.ThrowIfDisposed();
+ if (condition)
+ {
+ logger.AdditionalFields[ValidateKey(key)] = value;
+ }
+ return logger;
+ }
+
+ ///
+ /// Adds the specified key if doesn't not exist.
+ ///
+ ///
+ /// The logger.
+ /// The key.
+ /// The value.
+ ///
+ public static LoggerWrapper AddIfNotExist(this LoggerWrapper logger, string key, object? value)
+ {
+ logger.ThrowIfDisposed();
+ var validKey = ValidateKey(key);
+
+ if (value == null || logger.AdditionalFields.ContainsKey(validKey))
+ {
+ return logger;
+ }
+
+ logger.AdditionalFields[validKey] = value;
+ return logger;
+ }
+
+ ///
+ /// Adds the specified key or replaces it.
+ ///
+ ///
+ /// The logger.
+ /// The key.
+ /// The value.
+ ///
+ public static LoggerWrapper AddOrReplace(this LoggerWrapper logger, string key, object? value)
+ {
+ logger.ThrowIfDisposed();
+ logger.AdditionalFields[ValidateKey(key)] = value;
+ return logger;
+ }
+
+ ///
+ /// Adds the specified key or replaces it when the condition is true.
+ ///
+ ///
+ /// The logger.
+ /// if set to true [condition].
+ /// The key.
+ /// The value.
+ ///
+ public static LoggerWrapper AddOrReplaceIf(this LoggerWrapper logger, bool condition, string key, object? value)
+ {
+ logger.ThrowIfDisposed();
+
+ if (condition)
+ {
+ logger.AdditionalFields[ValidateKey(key)] = value;
+ }
+ return logger;
+ }
+
+ ///
+ /// Adds the stack trace.
+ ///
+ ///
+ /// The logger.
+ ///
+ public static LoggerWrapper AddStackTrace(this LoggerWrapper logger)
+ {
+ logger.ThrowIfDisposed();
+ logger.IncludeStackTrace = true;
+ return logger;
+ }
+
+ ///
+ /// Validates the key.
+ ///
+ /// The key.
+ ///
+ /// key
+ ///
+ private static string ValidateKey(string key)
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ return key switch
+ {
+ LoggingConstants.ActivityId or
+ LoggingConstants.Application or
+ LoggingConstants.Category or
+ LoggingConstants.Class or
+ LoggingConstants.Identity or
+ LoggingConstants.MethodName or
+ LoggingConstants.ProcessId or
+ LoggingConstants.Stacktrace or
+ LoggingConstants.UnwrappedException or
+ LoggingConstants.SubEventId or
+ LoggingConstants.ThreadId =>
+ throw new ReservedLoggingKeyException(key),
+ _ => key,
+ };
+ }
+}
diff --git a/src/Arc4u.Diagnostics/LoggingConstants.cs b/src/Arc4u.Diagnostics/LoggingConstants.cs
index 88bb4bed..266025af 100644
--- a/src/Arc4u.Diagnostics/LoggingConstants.cs
+++ b/src/Arc4u.Diagnostics/LoggingConstants.cs
@@ -19,4 +19,8 @@ public static class LoggingConstants
public const string Stacktrace = "Stacktrace";
public const string Category = "Category";
+
+ public const string UnwrappedException = "UnwrappedException";
+
+ public const string SubEventId = "SubEventId";
}
diff --git a/src/Arc4u.Diagnostics/MessageProperty.cs b/src/Arc4u.Diagnostics/MessageProperty.cs
index 16de0694..0f5001ca 100644
--- a/src/Arc4u.Diagnostics/MessageProperty.cs
+++ b/src/Arc4u.Diagnostics/MessageProperty.cs
@@ -2,7 +2,7 @@ namespace Arc4u.Diagnostics;
public static class MessagePropertyEx
{
- public static void AddIfNotExist(this IDictionary properties, string key, object value)
+ public static void AddIfNotExist(this IDictionary properties, string key, object? value)
{
if (value == null || properties.ContainsKey(key))
{
@@ -12,8 +12,10 @@ public static void AddIfNotExist(this IDictionary properties, st
properties[key] = value;
}
- public static void AddOrReplace(this IDictionary properties, string key, object value)
+ public static void AddOrReplace(this IDictionary properties, string key, object? value)
{
+ ArgumentNullException.ThrowIfNull(value);
+
properties[key] = value;
}
}
diff --git a/src/Arc4u.UnitTest/Logging/SerilogTests.cs b/src/Arc4u.UnitTest/Logging/SerilogTests.cs
index 8e63cb85..2a2b4ced 100644
--- a/src/Arc4u.UnitTest/Logging/SerilogTests.cs
+++ b/src/Arc4u.UnitTest/Logging/SerilogTests.cs
@@ -1,6 +1,5 @@
using System.Globalization;
using Arc4u.Diagnostics;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Core;
@@ -8,6 +7,7 @@
using Xunit;
using Arc4u.Dependency;
using FluentAssertions;
+using Microsoft.Extensions.DependencyInjection;
namespace Arc4u.UnitTest.Logging;
diff --git a/src/Arc4u.sln b/src/Arc4u.sln
index 60f3f9a5..cf9fe307 100644
--- a/src/Arc4u.sln
+++ b/src/Arc4u.sln
@@ -98,6 +98,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Arc4u.AspNetCore.Results",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arc4u.Dependency.Tool", "Arc4u.Dependency.Tool\Arc4u.Dependency.Tool.csproj", "{2C83BE21-3C11-46B2-9831-5B84E4912BFB}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B9419F5B-9CF3-4247-9929-7AF2362FD8E3}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{E8E0B4BA-5659-40E6-94C8-CA6D9F339101}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -956,6 +960,50 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {CCA589E1-100F-47A6-A34B-FD46FDFDAA2A} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {511B1EB2-F853-4C5C-80A7-0825AECF1BBE} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {B270FC8C-6B28-40FA-BA78-B73F720DB98D} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {705ADAFC-9C5C-4DCB-93C5-B71D49371074} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {C1048831-D985-481B-BA50-2840315EFFEA} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {29DA3E34-4769-457F-B583-8BCFF5958E41} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {531058F7-1DD4-4E51-8727-A2D038B57274} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {1AD8095D-CEF1-486F-912B-CB9C6DCE0A20} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {4AC131B6-9FF2-4F66-A2DE-BE66C3CA366B} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {42B7DC08-23E2-4854-A5DB-71E9FB3BAF6D} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {51D25BBB-E6E5-46AF-81A1-B854F715F2C0} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {150D036C-4B66-4A99-BAA9-28AC0A81E2D7} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {F333A9E4-52F7-473D-AFCE-46A4FCFE844C} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {9B9ABE45-A069-4E97-BAEF-5C273CF4C8AB} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {0F2EEF43-174D-436D-A5B1-DA92D3EF9B25} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {C10B3D6C-E5E4-4365-B5E5-826A2C7A44AE} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {62867DEB-C095-4D34-8C56-3F0C3A0FE3B7} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {39E14B92-11E6-433E-8B5C-082485F16DA9} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {F0D96654-0415-4540-A224-44C2585F825E} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {86290E0E-3450-4CFD-80B3-8EED35E2A121} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {64CBEAB5-41B7-47CE-95E7-62CE3ECFE2FC} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {F0EF3965-11F5-4F8E-8D8C-8A1BC32DFFE0} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {E954455E-AB54-4144-B784-1E68B6548482} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {1959B659-8A69-4D68-A198-C627464C2143} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {5157EBFA-D53F-49A3-BDC6-B4887D4F6E2F} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {FCFE7961-7B48-42BF-BD72-67A1C0858BB1} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {FACC7AF6-8AAF-41B9-A0EF-0E6285EE1CA7} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {F854BCF4-D7DF-46D3-809A-0EF53586EC00} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {F018AC7E-2799-48E3-BAC0-79C94452085A} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {46D4C073-FD32-4C23-B22A-306E69122BF6} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {30D7D397-A29D-4148-AD66-EF26495A5BC0} = {E8E0B4BA-5659-40E6-94C8-CA6D9F339101}
+ {EA273269-7720-48DE-8866-FC82E2C9490F} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {6ED0D79F-96D4-418A-9C75-69E165FA5682} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {FB64F43E-F22E-4202-9F33-840CB7F44AA1} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {EB7B7062-7366-4262-8686-3DDAA19141B4} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {B16F1734-B286-4898-8B15-EFA0D17DA912} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {06393D15-0DA9-40FA-84A1-9A822B627B52} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {1910732C-CF4C-4411-B29F-F75185DD2F9B} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {2B61C53F-ECFC-471D-AB98-0D23FF2D071A} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {6FD0A0B7-A94C-4DBD-AF62-CE3B5359F088} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {AE2BA9A5-AFCD-4D98-87DC-71D6DCBC2C21} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ {2C83BE21-3C11-46B2-9831-5B84E4912BFB} = {B9419F5B-9CF3-4247-9929-7AF2362FD8E3}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {748E3661-3906-4840-B5DE-843E083BAE5E}
EndGlobalSection