diff --git a/src/Bss.Platform.Logging/Bss.Platform.Logging.csproj b/src/Bss.Platform.Logging/Bss.Platform.Logging.csproj
new file mode 100644
index 0000000..b060f47
--- /dev/null
+++ b/src/Bss.Platform.Logging/Bss.Platform.Logging.csproj
@@ -0,0 +1,10 @@
+
+
+ Luxoft.Bss.Platform.Logging
+
+
+
+
+
+
+
diff --git a/src/Bss.Platform.Logging/DependencyInjection.cs b/src/Bss.Platform.Logging/DependencyInjection.cs
new file mode 100644
index 0000000..f0a6db1
--- /dev/null
+++ b/src/Bss.Platform.Logging/DependencyInjection.cs
@@ -0,0 +1,22 @@
+using Bss.Platform.Logging.Sinks;
+
+using Microsoft.ApplicationInsights;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+using Serilog;
+
+namespace Bss.Platform.Logging;
+
+public static class DependencyInjection
+{
+ public static void AddPlatformLogging(this IHostBuilder builder) =>
+ builder
+ .UseSerilog(
+ (context, services, configuration) =>
+ configuration
+ .ReadFrom.Configuration(context.Configuration)
+ .ReadFrom.Services(services)
+ .WriteTo.Sink(new ApplicationInsightsSink(services.GetService())),
+ true);
+}
diff --git a/src/Bss.Platform.Logging/Sinks/ApplicationInsightsSink.cs b/src/Bss.Platform.Logging/Sinks/ApplicationInsightsSink.cs
new file mode 100644
index 0000000..c598262
--- /dev/null
+++ b/src/Bss.Platform.Logging/Sinks/ApplicationInsightsSink.cs
@@ -0,0 +1,19 @@
+using Microsoft.ApplicationInsights;
+
+using Serilog.Core;
+using Serilog.Events;
+
+namespace Bss.Platform.Logging.Sinks;
+
+public class ApplicationInsightsSink(TelemetryClient? telemetryClient) : ILogEventSink
+{
+ public void Emit(LogEvent logEvent)
+ {
+ if (telemetryClient is null || logEvent.Exception == null)
+ {
+ return;
+ }
+
+ telemetryClient.TrackException(logEvent.Exception, logEvent.Properties.ToDictionary(x => x.Key, x => x.Value.ToString()));
+ }
+}
diff --git a/src/Bss.Platform.RabbitMq.Consumer/Services/ConcurrentConsumer.cs b/src/Bss.Platform.RabbitMq.Consumer/Services/ConcurrentConsumer.cs
index 2d93c42..c129a9f 100644
--- a/src/Bss.Platform.RabbitMq.Consumer/Services/ConcurrentConsumer.cs
+++ b/src/Bss.Platform.RabbitMq.Consumer/Services/ConcurrentConsumer.cs
@@ -4,13 +4,13 @@
namespace Bss.Platform.RabbitMq.Consumer.Services;
-internal record ConcurrentConsumer(IRabbitMqMessageReader MessageReader) : IRabbitMqConsumer
+internal class ConcurrentConsumer(IRabbitMqMessageReader messageReader) : IRabbitMqConsumer
{
public async Task ConsumeAsync(IModel channel, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
- await this.MessageReader.ReadAsync(channel, token);
+ await messageReader.ReadAsync(channel, token);
}
}
diff --git a/src/Bss.Platform.RabbitMq.Consumer/Services/ConsumerInitializer.cs b/src/Bss.Platform.RabbitMq.Consumer/Services/ConsumerInitializer.cs
index f2f49c4..2a7003c 100644
--- a/src/Bss.Platform.RabbitMq.Consumer/Services/ConsumerInitializer.cs
+++ b/src/Bss.Platform.RabbitMq.Consumer/Services/ConsumerInitializer.cs
@@ -7,11 +7,11 @@
namespace Bss.Platform.RabbitMq.Consumer.Services;
-internal record ConsumerInitializer(IOptions Options) : IRabbitMqInitializer
+internal class ConsumerInitializer(IOptions options) : IRabbitMqInitializer
{
public void Initialize(IModel model)
{
- var consumerSettings = this.Options.Value;
+ var consumerSettings = options.Value;
model.ExchangeDeclare(consumerSettings.Exchange, ExchangeType.Topic, true);
model.ExchangeDeclare(consumerSettings.DeadLetterExchange, ExchangeType.Fanout, true);
diff --git a/src/Bss.Platform.RabbitMq.Consumer/Services/DeadLetterProcessor.cs b/src/Bss.Platform.RabbitMq.Consumer/Services/DeadLetterProcessor.cs
index 2c77673..697ec41 100644
--- a/src/Bss.Platform.RabbitMq.Consumer/Services/DeadLetterProcessor.cs
+++ b/src/Bss.Platform.RabbitMq.Consumer/Services/DeadLetterProcessor.cs
@@ -12,17 +12,17 @@
namespace Bss.Platform.RabbitMq.Consumer.Services;
-internal record DeadLetterProcessor(
- IRabbitMqClient Client,
- ILogger Logger,
- IOptions ConsumerSettings)
+internal class DeadLetterProcessor(
+ IRabbitMqClient client,
+ ILogger logger,
+ IOptions consumerSettings)
: IDeadLetterProcessor
{
public async Task ProcessAsync(string message, string routingKey, Exception? exception, CancellationToken token)
{
try
{
- using var connection = await this.Client.TryConnectAsync(this.ConsumerSettings.Value.ConnectionAttemptCount, token);
+ using var connection = await client.TryConnectAsync(consumerSettings.Value.ConnectionAttemptCount, token);
if (connection == null)
{
throw new Exception("Failed to open connection");
@@ -35,17 +35,17 @@ public async Task ProcessAsync(string message, string routin
properties.Headers = new Dictionary
{
{ "routingKey", routingKey },
- { "queue", this.ConsumerSettings.Value.Queue },
+ { "queue", consumerSettings.Value.Queue },
{ "error", exception?.GetBaseException().Message ?? "unknown exception" },
{ "stacktrace", exception?.StackTrace ?? "missing stacktrace" }
};
- channel.BasicPublish(this.ConsumerSettings.Value.DeadLetterExchange, string.Empty, properties, Encoding.UTF8.GetBytes(message));
+ channel.BasicPublish(consumerSettings.Value.DeadLetterExchange, string.Empty, properties, Encoding.UTF8.GetBytes(message));
return DeadLetterDecision.RemoveFromQueue;
}
catch (Exception e)
{
- this.Logger.LogError(e, "Failed to process dead letter with routing key '{RoutingKey}'", routingKey);
+ logger.LogError(e, "Failed to process dead letter with routing key '{RoutingKey}'", routingKey);
return DeadLetterDecision.Requeue;
}
}
diff --git a/src/Bss.Platform.RabbitMq.Consumer/Services/MessageReader.cs b/src/Bss.Platform.RabbitMq.Consumer/Services/MessageReader.cs
index 04db44f..e1738f2 100644
--- a/src/Bss.Platform.RabbitMq.Consumer/Services/MessageReader.cs
+++ b/src/Bss.Platform.RabbitMq.Consumer/Services/MessageReader.cs
@@ -13,19 +13,19 @@
namespace Bss.Platform.RabbitMq.Consumer.Services;
-internal record MessageReader(
- IRabbitMqMessageProcessor MessageProcessor,
- IDeadLetterProcessor DeadLetterProcessor,
- ILogger Logger,
- IOptions ConsumerSettings)
+internal class MessageReader(
+ IRabbitMqMessageProcessor messageProcessor,
+ IDeadLetterProcessor deadLetterProcessor,
+ ILogger logger,
+ IOptions consumerSettings)
: IRabbitMqMessageReader
{
public async Task ReadAsync(IModel channel, CancellationToken token)
{
- var result = channel.BasicGet(this.ConsumerSettings.Value.Queue, false);
+ var result = channel.BasicGet(consumerSettings.Value.Queue, false);
if (result is null)
{
- await Delay(this.ConsumerSettings.Value.ReceiveMessageDelayMilliseconds, token);
+ await Delay(consumerSettings.Value.ReceiveMessageDelayMilliseconds, token);
return;
}
@@ -37,10 +37,10 @@ private async Task ProcessAsync(BasicGetResult message, IModel channel, Cancella
var result = await Policy
.Handle()
.WaitAndRetryAsync(
- this.ConsumerSettings.Value.FailedMessageRetryCount,
- _ => TimeSpan.FromMilliseconds(this.ConsumerSettings.Value.RejectMessageDelayMilliseconds))
+ consumerSettings.Value.FailedMessageRetryCount,
+ _ => TimeSpan.FromMilliseconds(consumerSettings.Value.RejectMessageDelayMilliseconds))
.ExecuteAndCaptureAsync(
- innerToken => this.MessageProcessor.ProcessAsync(
+ innerToken => messageProcessor.ProcessAsync(
message.BasicProperties,
message.RoutingKey,
GetMessageBody(message),
@@ -62,7 +62,7 @@ private async Task HandleProcessResultAsync(BasicGetResult message, IModel chann
}
else
{
- var deadLetteringResult = await this.DeadLetterProcessor.ProcessAsync(
+ var deadLetteringResult = await deadLetterProcessor.ProcessAsync(
GetMessageBody(message),
message.RoutingKey,
result.FinalException,
@@ -79,9 +79,9 @@ private async Task HandleProcessResultAsync(BasicGetResult message, IModel chann
}
catch (Exception ex)
{
- this.Logger.LogError(ex, "Failed to deadLetter message with routing key {RoutingKey}", message.RoutingKey);
+ logger.LogError(ex, "Failed to deadLetter message with routing key {RoutingKey}", message.RoutingKey);
- await Delay(this.ConsumerSettings.Value.ReceiveMessageDelayMilliseconds, token);
+ await Delay(consumerSettings.Value.ReceiveMessageDelayMilliseconds, token);
channel.BasicNack(message.DeliveryTag, false, true);
}
diff --git a/src/Bss.Platform.RabbitMq.Consumer/Services/MsSqlLockService.cs b/src/Bss.Platform.RabbitMq.Consumer/Services/MsSqlLockService.cs
index dd2bd06..a464be7 100644
--- a/src/Bss.Platform.RabbitMq.Consumer/Services/MsSqlLockService.cs
+++ b/src/Bss.Platform.RabbitMq.Consumer/Services/MsSqlLockService.cs
@@ -8,7 +8,7 @@
namespace Bss.Platform.RabbitMq.Consumer.Services;
-internal record MsSqlLockService(IOptions ConsumerSettings) : IRabbitMqConsumerLockService
+internal class MsSqlLockService(IOptions consumerSettings) : IRabbitMqConsumerLockService
{
public bool TryObtainLock(SqlConnection connection)
{
@@ -51,5 +51,5 @@ public void TryReleaseLock(SqlConnection connection)
}
}
- private string GetLockName() => $"{this.ConsumerSettings.Value.Queue}_Consumer_Lock";
+ private string GetLockName() => $"{consumerSettings.Value.Queue}_Consumer_Lock";
}
diff --git a/src/Bss.Platform.RabbitMq.Consumer/Services/SynchronizedConsumer.cs b/src/Bss.Platform.RabbitMq.Consumer/Services/SynchronizedConsumer.cs
index d8c4664..402f465 100644
--- a/src/Bss.Platform.RabbitMq.Consumer/Services/SynchronizedConsumer.cs
+++ b/src/Bss.Platform.RabbitMq.Consumer/Services/SynchronizedConsumer.cs
@@ -9,12 +9,12 @@
namespace Bss.Platform.RabbitMq.Consumer.Services;
-internal record SynchronizedConsumer(
- SqlConnectionStringProvider ConnectionStringProvider,
- IRabbitMqConsumerLockService LockService,
- ILogger Logger,
- IRabbitMqMessageReader MessageReader,
- IOptions ConsumerSettings)
+internal class SynchronizedConsumer(
+ SqlConnectionStringProvider connectionStringProvider,
+ IRabbitMqConsumerLockService lockService,
+ ILogger logger,
+ IRabbitMqMessageReader messageReader,
+ IOptions consumerSettings)
: IRabbitMqConsumer
{
private SqlConnection? connection;
@@ -29,19 +29,19 @@ public async Task ConsumeAsync(IModel channel, CancellationToken token)
{
if (await this.GetLock(token))
{
- await this.MessageReader.ReadAsync(channel, token);
+ await messageReader.ReadAsync(channel, token);
}
else
{
await this.CloseConnectionAsync();
- await Delay(this.ConsumerSettings.Value.InactiveConsumerSleepMilliseconds, token);
+ await Delay(consumerSettings.Value.InactiveConsumerSleepMilliseconds, token);
}
}
catch (Exception e)
{
- this.Logger.LogError(e, "Consuming error");
+ logger.LogError(e, "Consuming error");
await this.CloseConnectionAsync();
- await Delay(this.ConsumerSettings.Value.InactiveConsumerSleepMilliseconds, token);
+ await Delay(consumerSettings.Value.InactiveConsumerSleepMilliseconds, token);
}
}
}
@@ -50,7 +50,7 @@ public void Dispose()
{
if (this.connection is not null)
{
- this.LockService.TryReleaseLock(this.connection);
+ lockService.TryReleaseLock(this.connection);
}
this.connection?.Close();
@@ -59,19 +59,19 @@ public void Dispose()
private async Task GetLock(CancellationToken token)
{
- if (this.lockObtainedDate?.AddMilliseconds(this.ConsumerSettings.Value.ActiveConsumerRefreshMilliseconds) >= DateTime.Now)
+ if (this.lockObtainedDate?.AddMilliseconds(consumerSettings.Value.ActiveConsumerRefreshMilliseconds) >= DateTime.Now)
{
return true;
}
await this.OpenConnectionAsync(token);
- if (!this.LockService.TryObtainLock(this.connection!))
+ if (!lockService.TryObtainLock(this.connection!))
{
return false;
}
this.lockObtainedDate = DateTime.Now;
- this.Logger.LogDebug("Current consumer is active");
+ logger.LogDebug("Current consumer is active");
return true;
}
@@ -80,7 +80,7 @@ private async Task OpenConnectionAsync(CancellationToken token)
{
await this.CloseConnectionAsync();
- this.connection = new SqlConnection(this.ConnectionStringProvider.ConnectionString);
+ this.connection = new SqlConnection(connectionStringProvider.ConnectionString);
await this.connection.OpenAsync(token);
}
@@ -90,13 +90,13 @@ private async Task CloseConnectionAsync()
{
if (this.connection is not null)
{
- this.LockService.TryReleaseLock(this.connection);
+ lockService.TryReleaseLock(this.connection);
await this.connection!.CloseAsync();
}
}
catch (Exception e)
{
- this.Logger.LogError(e, "Failed to close connection");
+ logger.LogError(e, "Failed to close connection");
}
}
diff --git a/src/Bss.Platform.RabbitMq/Services/RabbitMqClient.cs b/src/Bss.Platform.RabbitMq/Services/RabbitMqClient.cs
index 7d4f1ec..9988543 100644
--- a/src/Bss.Platform.RabbitMq/Services/RabbitMqClient.cs
+++ b/src/Bss.Platform.RabbitMq/Services/RabbitMqClient.cs
@@ -11,13 +11,13 @@
namespace Bss.Platform.RabbitMq.Services;
-public record RabbitMqClient(IOptions Options, ILogger Logger) : IRabbitMqClient
+public class RabbitMqClient(IOptions options, ILogger logger) : IRabbitMqClient
{
private const int RetryConnectDelay = 5000;
public Task TryConnectAsync(int? attempts, CancellationToken token = default)
{
- var serverSettings = this.Options.Value;
+ var serverSettings = options.Value;
var factory = new ConnectionFactory
{
HostName = serverSettings.Host,
@@ -58,5 +58,5 @@ private AsyncRetryPolicy CreateRetryPolicy(int? attempts)
(ex, _) => this.LogConnectionError(ex));
}
- private void LogConnectionError(Exception exception) => this.Logger.LogError(exception, "Could not connect to RabbitMQ server");
+ private void LogConnectionError(Exception exception) => logger.LogError(exception, "Could not connect to RabbitMQ server");
}
diff --git a/src/Bss.Platform.sln b/src/Bss.Platform.sln
index 8644128..dd8b5e8 100644
--- a/src/Bss.Platform.sln
+++ b/src/Bss.Platform.sln
@@ -14,6 +14,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C53E37ED
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Unit", "Tests.Unit\Tests.Unit.csproj", "{16BA5D44-4C8F-4EF6-9633-C4502ED91A22}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Logging", "Logging", "{D07584C3-F6D5-472C-8515-7D38F4C54F11}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bss.Platform.Logging", "Bss.Platform.Logging\Bss.Platform.Logging.csproj", "{2417B26C-C12D-4E63-8996-395DD78F81A1}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -24,6 +28,7 @@ Global
{EF6932F4-67BD-422D-B2D8-0DA4CE98181A} = {0F54786D-AB46-46AC-88DF-BEB789A62C1F}
{5B028860-35C4-43CF-AA63-830A66D83E46} = {1E012CC2-9AD1-491A-9D6B-8E3215347B97}
{16BA5D44-4C8F-4EF6-9633-C4502ED91A22} = {C53E37ED-7EFD-4383-9D89-A3CD4B8ED2AB}
+ {2417B26C-C12D-4E63-8996-395DD78F81A1} = {D07584C3-F6D5-472C-8515-7D38F4C54F11}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{860BFBD8-26EA-44F9-980E-21B828FC8F72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -42,5 +47,9 @@ Global
{16BA5D44-4C8F-4EF6-9633-C4502ED91A22}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16BA5D44-4C8F-4EF6-9633-C4502ED91A22}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16BA5D44-4C8F-4EF6-9633-C4502ED91A22}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2417B26C-C12D-4E63-8996-395DD78F81A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2417B26C-C12D-4E63-8996-395DD78F81A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2417B26C-C12D-4E63-8996-395DD78F81A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2417B26C-C12D-4E63-8996-395DD78F81A1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 7f6865d..196a8c1 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -3,19 +3,21 @@
true
+
-
+
+
-
-
+
+
\ No newline at end of file
diff --git a/src/__SolutionItems/CommonAssemblyInfo.cs b/src/__SolutionItems/CommonAssemblyInfo.cs
index 801e1cb..fe55b65 100644
--- a/src/__SolutionItems/CommonAssemblyInfo.cs
+++ b/src/__SolutionItems/CommonAssemblyInfo.cs
@@ -4,9 +4,9 @@
[assembly: AssemblyCompany("Luxoft")]
[assembly: AssemblyCopyright("Copyright © Luxoft 2024")]
-[assembly: AssemblyVersion("1.0.1.0")]
-[assembly: AssemblyFileVersion("1.0.1.0")]
-[assembly: AssemblyInformationalVersion("1.0.1.0")]
+[assembly: AssemblyVersion("1.1.0.0")]
+[assembly: AssemblyFileVersion("1.1.0.0")]
+[assembly: AssemblyInformationalVersion("1.1.0.0")]
#if DEBUG
[assembly: AssemblyConfiguration("Debug")]