Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions src/Exceptionless.Core/Configuration/MessageBusOptions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
using Exceptionless.Core.Extensions;
using System.Diagnostics.CodeAnalysis;
using Exceptionless.Core.Extensions;
using Foundatio.Utility;
using Microsoft.Extensions.Configuration;

namespace Exceptionless.Core.Configuration;

public class MessageBusOptions
{
private const string RabbitMqProvider = "rabbitmq";
private const string ServerKey = "server";

public string? ConnectionString { get; internal set; }
public string? Provider { get; internal set; }
public Dictionary<string, string?> Data { get; internal set; } = null!;
Expand All @@ -24,12 +28,24 @@ public static MessageBusOptions ReadFromConfiguration(IConfiguration config, App

if (cs != null)
{
if (TryGetRabbitMqConnectionString(config, cs, out string? connectionString))
{
options.Provider = RabbitMqProvider;
options.ConnectionString = connectionString;
options.Data = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
{
[nameof(options.Provider)] = options.Provider
};
options.Data[ServerKey] = connectionString;

return options;
}

options.Data = cs.ParseConnectionString();
options.Provider = options.Data.GetString(nameof(options.Provider));
string? providerConnectionString = !String.IsNullOrEmpty(options.Provider) ? config.GetConnectionString(options.Provider) : null;

var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server");
options.Data ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: ServerKey);
options.Data.AddRange(providerOptions);

options.ConnectionString = options.Data.BuildConnectionString(new HashSet<string> { nameof(options.Provider) });
Expand All @@ -47,4 +63,34 @@ public static MessageBusOptions ReadFromConfiguration(IConfiguration config, App

return options;
}

private static bool TryGetRabbitMqConnectionString(IConfiguration config, string selector, [NotNullWhen(true)] out string? connectionString)
{
connectionString = null;

int separatorIndex = selector.IndexOf(';');
string providerSelector = separatorIndex >= 0 ? selector[..separatorIndex] : selector;
var providerData = providerSelector.ParseConnectionString();

if (!String.Equals(providerData.GetString(nameof(Provider)), RabbitMqProvider, StringComparison.OrdinalIgnoreCase))
return false;

string? configuredConnectionString = separatorIndex >= 0 ? selector[(separatorIndex + 1)..] : null;
if (String.IsNullOrWhiteSpace(configuredConnectionString))
configuredConnectionString = config.GetConnectionString(RabbitMqProvider);

if (String.IsNullOrWhiteSpace(configuredConnectionString))
return false;

connectionString = TrimMatchingQuotes(configuredConnectionString.Trim());
return true;
}

private static string TrimMatchingQuotes(string value)
{
if (value.Length >= 2 && ((value[0] == '"' && value[^1] == '"') || (value[0] == '\'' && value[^1] == '\'')))
return value[1..^1];

return value;
}
}
4 changes: 4 additions & 0 deletions src/Exceptionless.Job/appsettings.Development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ ConnectionStrings:
# Elasticsearch: server=https://elastic:elastic@localhost:9200
# Cache: provider=redis;
# MessageBus: provider=redis;
# RabbitMQ examples (choose inline or separate):
# MessageBus: 'provider=rabbitmq;amqp://localhost/%2F'
# MessageBus: 'provider=rabbitmq'
# rabbitmq: 'amqp://localhost/%2F'
# Queue: provider=redis;
# Storage: provider=folder;path=..\Exceptionless.Web\storage
Email: smtp://localhost:1025
Expand Down
4 changes: 4 additions & 0 deletions src/Exceptionless.Job/appsettings.Production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ ConnectionStrings:
# Elasticsearch: server=http://localhost:9200
# Cache: provider=redis;server="localhost,abortConnect=false"
# MessageBus: provider=redis;server="localhost,abortConnect=false"
# RabbitMQ examples (choose inline or separate):
# MessageBus: 'provider=rabbitmq;amqp://localhost/%2F'
# MessageBus: 'provider=rabbitmq'
# rabbitmq: 'amqp://localhost/%2F'
# Queue: provider=redis;server="localhost,abortConnect=false"
# Storage: ''
# Email: 'smtps://user:password@domain.com:587'
Expand Down
4 changes: 4 additions & 0 deletions src/Exceptionless.Job/appsettings.Staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ ConnectionStrings:
# Elasticsearch: server=http://localhost:9200;replicas=0
# Cache: provider=redis;
# MessageBus: provider=redis;
# RabbitMQ examples (choose inline or separate):
# MessageBus: 'provider=rabbitmq;amqp://localhost/%2F'
# MessageBus: 'provider=rabbitmq'
# rabbitmq: 'amqp://localhost/%2F'
# Queue: provider=redis;
# Storage: provider=folder;path=.\storage=
OAuth: FacebookId=395178683904310;GitHubId=7ef1dd5bfbc4ccf7f5ef;GoogleId=809763155066-enkkdmt4ierc33q9cft9nf5d5c02h30q.apps.googleusercontent.com;MicrosoftId=000000004C137E8B;SlackId=34500115540.177239122322;
Expand Down
4 changes: 4 additions & 0 deletions src/Exceptionless.Web/appsettings.Development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ ConnectionStrings:
# Elasticsearch: server=https://elastic:elastic@localhost:9200
# Cache: provider=redis;
# MessageBus: provider=redis;
# RabbitMQ examples (choose inline or separate):
# MessageBus: 'provider=rabbitmq;amqp://localhost/%2F'
# MessageBus: 'provider=rabbitmq'
# rabbitmq: 'amqp://localhost/%2F'
# Queue: provider=redis;
# Storage: provider=folder;path=.\storage
# LDAP: ''
Expand Down
4 changes: 4 additions & 0 deletions src/Exceptionless.Web/appsettings.Production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ ConnectionStrings:
# Elasticsearch: server=http://localhost:9200
# Cache: provider=redis;server="localhost,abortConnect=false"
# MessageBus: provider=redis;server="localhost,abortConnect=false"
# RabbitMQ examples (choose inline or separate):
# MessageBus: 'provider=rabbitmq;amqp://localhost/%2F'
# MessageBus: 'provider=rabbitmq'
# rabbitmq: 'amqp://localhost/%2F'
# Queue: provider=redis;server="localhost,abortConnect=false"
# Storage: ''
# Email: 'smtps://user:password@domain.com:587'
Expand Down
4 changes: 4 additions & 0 deletions src/Exceptionless.Web/appsettings.Staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ ConnectionStrings:
# Elasticsearch: server=http://localhost:9200;replicas=0
# Cache: provider=redis;
# MessageBus: provider=redis;
# RabbitMQ examples (choose inline or separate):
# MessageBus: 'provider=rabbitmq;amqp://localhost/%2F'
# MessageBus: 'provider=rabbitmq'
# rabbitmq: 'amqp://localhost/%2F'
# Queue: provider=redis;
# Storage: provider=folder;path=.\storage=
OAuth: FacebookId=395178683904310;GitHubId=7ef1dd5bfbc4ccf7f5ef;GoogleId=809763155066-enkkdmt4ierc33q9cft9nf5d5c02h30q.apps.googleusercontent.com;MicrosoftId=000000004C137E8B;SlackId=34500115540.177239122322;
Expand Down
92 changes: 92 additions & 0 deletions tests/Exceptionless.Tests/Configuration/MessageBusOptionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using Exceptionless.Core;
using Exceptionless.Core.Configuration;
using Microsoft.Extensions.Configuration;
using Xunit;

namespace Exceptionless.Tests.Configuration;

public class MessageBusOptionsTests
{
[Theory]
[InlineData("provider=rabbitmq;amqp://localhost/%2F", "amqp://localhost/%2F")]
[InlineData("provider=rabbitmq;\"amqp://localhost/%2F\"", "amqp://localhost/%2F")]
[InlineData("provider=rabbitmq;'amqp://localhost/%2F'", "amqp://localhost/%2F")]
[InlineData(
" PROVIDER = \"RABBITMQ\" ; 'amqps://user:p%40ss@rabbit.example.com:5671/team%2Fprod?heartbeat=30&connection_timeout=10000' ",
"amqps://user:p%40ss@rabbit.example.com:5671/team%2Fprod?heartbeat=30&connection_timeout=10000")]
public void ReadFromConfiguration_WithInlineRabbitMqUri_PreservesRawConnectionString(string configuredConnectionString, string expectedConnectionString)
{
var options = ReadOptions(new Dictionary<string, string?>
{
["ConnectionStrings:MessageBus"] = configuredConnectionString
});

Assert.Equal("rabbitmq", options.Provider);
Assert.Equal(expectedConnectionString, options.ConnectionString);
Assert.Equal(expectedConnectionString, options.Data["server"]);
}

[Theory]
[InlineData("provider=rabbitmq", "amqp://localhost/%2F", "amqp://localhost/%2F")]
[InlineData("provider=rabbitmq;", "'amqp://localhost/%2F'", "amqp://localhost/%2F")]
[InlineData(
"provider=RaBbItMq",
"\"amqps://user:p%40ss@rabbit.example.com:5671/team%2Fprod?heartbeat=30\"",
"amqps://user:p%40ss@rabbit.example.com:5671/team%2Fprod?heartbeat=30")]
public void ReadFromConfiguration_WithNamedRabbitMqUri_PreservesRawConnectionString(string selector, string configuredConnectionString, string expectedConnectionString)
{
var options = ReadOptions(new Dictionary<string, string?>
{
["ConnectionStrings:MessageBus"] = selector,
["ConnectionStrings:rabbitmq"] = configuredConnectionString
});

Assert.Equal("rabbitmq", options.Provider);
Assert.Equal(expectedConnectionString, options.ConnectionString);
Assert.Equal(expectedConnectionString, options.Data["server"]);
}

[Fact]
public void ReadFromConfiguration_WithRedisProviderSettings_MergesLegacyKeyValueData()
{
var options = ReadOptions(new Dictionary<string, string?>
{
["ConnectionStrings:MessageBus"] = "provider=redis;ssl=true",
["ConnectionStrings:redis"] = "server=localhost:6379;abortConnect=false"
});

Assert.Equal("redis", options.Provider);
Assert.Equal("ssl=true;server=localhost:6379;abortConnect=false", options.ConnectionString);
Assert.Equal("true", options.Data["ssl"]);
Assert.Equal("localhost:6379", options.Data["server"]);
Assert.Equal("false", options.Data["abortConnect"]);
}

[Fact]
public void ReadFromConfiguration_WithInlineRedisConnectionString_PreservesLegacyFormatting()
{
var options = ReadOptions(new Dictionary<string, string?>
{
["ConnectionStrings:MessageBus"] = "provider=redis;server=localhost:6379,abortConnect=false"
});

Assert.Equal("redis", options.Provider);
Assert.Equal("server=localhost:6379,abortConnect=false", options.ConnectionString);
Assert.Equal("localhost:6379,abortConnect=false", options.Data["server"]);
}

private static MessageBusOptions ReadOptions(Dictionary<string, string?> values)
{
var configuration = CreateConfiguration(values);
var appOptions = new AppOptions { AppScope = "production" };

return MessageBusOptions.ReadFromConfiguration(configuration, appOptions);
}

private static IConfiguration CreateConfiguration(Dictionary<string, string?> values)
{
return new ConfigurationBuilder()
.AddInMemoryCollection(values)
.Build();
}
}
4 changes: 4 additions & 0 deletions tests/Exceptionless.Tests/appsettings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ ConnectionStrings:
# Elasticsearch: server=https://elastic:elastic@localhost:9200
# Cache: provider=redis;
# MessageBus: provider=redis;
# RabbitMQ examples (choose inline or separate):
# MessageBus: 'provider=rabbitmq;amqp://localhost/%2F'
# MessageBus: 'provider=rabbitmq'
# rabbitmq: 'amqp://localhost/%2F'
# Queue: provider=redis;
Storage: provider=folder;path=..\..\..\..\..\src\Exceptionless.Web\storage

Expand Down
Loading