diff --git a/src/HealthChecks.ArangoDb/ArangoDbHealthCheck.cs b/src/HealthChecks.ArangoDb/ArangoDbHealthCheck.cs index ed049ff6b6..38ad50c5d0 100644 --- a/src/HealthChecks.ArangoDb/ArangoDbHealthCheck.cs +++ b/src/HealthChecks.ArangoDb/ArangoDbHealthCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using ArangoDBNetStandard; using ArangoDBNetStandard.AuthApi; using ArangoDBNetStandard.Transport.Http; @@ -8,6 +9,10 @@ namespace HealthChecks.ArangoDb; public class ArangoDbHealthCheck : IHealthCheck { private readonly ArangoDbOptions _options; + private readonly Dictionary _baseCheckDetails = new Dictionary{ + { "healthcheck.type", nameof(ArangoDbHealthCheck) }, + { "db.system", "arango" } + }; public ArangoDbHealthCheck(ArangoDbOptions options) { @@ -17,18 +22,20 @@ public ArangoDbHealthCheck(ArangoDbOptions options) /// public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + Dictionary checkDetails = _baseCheckDetails; try { using var transport = await GetTransportAsync(_options).ConfigureAwait(false); + using var adb = new ArangoDBClient(transport); var databases = await adb.Database.GetCurrentDatabaseInfoAsync(cancellationToken).ConfigureAwait(false); return databases.Error - ? new HealthCheckResult(context.Registration.FailureStatus, $"HealthCheck failed with status code: {databases.Code}.") - : HealthCheckResult.Healthy(); + ? new HealthCheckResult(context.Registration.FailureStatus, $"HealthCheck failed with status code: {databases.Code}.", data: new ReadOnlyDictionary(checkDetails)) + : HealthCheckResult.Healthy(data: new ReadOnlyDictionary(checkDetails)); } catch (Exception ex) { - return new HealthCheckResult(context.Registration.FailureStatus, ex.Message, ex); + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex, data: new ReadOnlyDictionary(checkDetails)); } } diff --git a/src/HealthChecks.DocumentDb/DocumentDbHealthCheck.cs b/src/HealthChecks.DocumentDb/DocumentDbHealthCheck.cs index c174d0c228..1b5e53854b 100644 --- a/src/HealthChecks.DocumentDb/DocumentDbHealthCheck.cs +++ b/src/HealthChecks.DocumentDb/DocumentDbHealthCheck.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Collections.ObjectModel; using Microsoft.Azure.Documents.Client; using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -8,6 +9,10 @@ public class DocumentDbHealthCheck : IHealthCheck { private static readonly ConcurrentDictionary _connections = new(); private readonly DocumentDbOptions _options; + private readonly Dictionary _baseCheckDetails = new Dictionary{ + { "healthcheck.type", nameof(DocumentDbHealthCheck) }, + { "db.system", "documentdb" } + }; public DocumentDbHealthCheck(DocumentDbOptions documentDbOptions) { @@ -20,10 +25,14 @@ public DocumentDbHealthCheck(DocumentDbOptions documentDbOptions) /// public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + Dictionary checkDetails = _baseCheckDetails; try { + checkDetails.Add("db.namespace", _options.DatabaseName ?? ""); + checkDetails.Add("db.collection.name", _options.CollectionName ?? ""); if (!_connections.TryGetValue(_options.UriEndpoint, out var documentDbClient)) { + checkDetails.Add("server.address", _options.UriEndpoint); documentDbClient = new DocumentClient(new Uri(_options.UriEndpoint), _options.PrimaryKey); if (!_connections.TryAdd(_options.UriEndpoint, documentDbClient)) @@ -42,11 +51,11 @@ public async Task CheckHealthAsync(HealthCheckContext context await documentDbClient.OpenAsync(cancellationToken).ConfigureAwait(false); } - return HealthCheckResult.Healthy(); + return HealthCheckResult.Healthy(data: new ReadOnlyDictionary(checkDetails)); } catch (Exception ex) { - return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex, data: new ReadOnlyDictionary(checkDetails)); } } } diff --git a/src/HealthChecks.DynamoDb/DynamoDbHealthCheck.cs b/src/HealthChecks.DynamoDb/DynamoDbHealthCheck.cs index 456fcc0bdf..ba4b867a61 100644 --- a/src/HealthChecks.DynamoDb/DynamoDbHealthCheck.cs +++ b/src/HealthChecks.DynamoDb/DynamoDbHealthCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.Model; using Amazon.Runtime; @@ -11,6 +12,10 @@ namespace HealthChecks.DynamoDb; public class DynamoDbHealthCheck : IHealthCheck { private readonly DynamoDBOptions _options; + private readonly Dictionary _baseCheckDetails = new Dictionary{ + { "healthcheck.type", nameof(DynamoDbHealthCheck) }, + { "db.system", "dynamodb" } + }; /// /// Creates health check for AWS DynamoDb database with the specified options. @@ -24,6 +29,7 @@ public DynamoDbHealthCheck(DynamoDBOptions options) /// public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + Dictionary checkDetails = _baseCheckDetails; try { AWSCredentials? credentials = _options.Credentials; @@ -50,11 +56,11 @@ public async Task CheckHealthAsync(HealthCheckContext context var response = await client.ListTablesAsync(request, cancellationToken).ConfigureAwait(false); - return HealthCheckResult.Healthy(); + return HealthCheckResult.Healthy(data: new ReadOnlyDictionary(checkDetails)); } catch (Exception ex) { - return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex, data: new ReadOnlyDictionary(checkDetails)); } } diff --git a/src/HealthChecks.Elasticsearch/ElasticsearchHealthCheck.cs b/src/HealthChecks.Elasticsearch/ElasticsearchHealthCheck.cs index 10752e1e60..4b263e4474 100644 --- a/src/HealthChecks.Elasticsearch/ElasticsearchHealthCheck.cs +++ b/src/HealthChecks.Elasticsearch/ElasticsearchHealthCheck.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Collections.ObjectModel; using Elasticsearch.Net; using Microsoft.Extensions.Diagnostics.HealthChecks; using Nest; @@ -10,6 +11,10 @@ public class ElasticsearchHealthCheck : IHealthCheck private static readonly ConcurrentDictionary _connections = new(); private readonly ElasticsearchOptions _options; + private readonly Dictionary _baseCheckDetails = new Dictionary{ + { "healthcheck.type", nameof(ElasticsearchHealthCheck) }, + { "db.system", "elasticsearch" } + }; public ElasticsearchHealthCheck(ElasticsearchOptions options) { @@ -19,6 +24,7 @@ public ElasticsearchHealthCheck(ElasticsearchOptions options) /// public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + Dictionary checkDetails = _baseCheckDetails; try { if (!_connections.TryGetValue(_options.Uri, out var lowLevelClient)) @@ -52,6 +58,7 @@ public async Task CheckHealthAsync(HealthCheckContext context if (!_connections.TryAdd(_options.Uri, lowLevelClient)) { + checkDetails.Add("server.address", _options.Uri); lowLevelClient = _connections[_options.Uri]; } } @@ -62,14 +69,14 @@ public async Task CheckHealthAsync(HealthCheckContext context if (healthResponse.ApiCall.HttpStatusCode != 200) { - return new HealthCheckResult(context.Registration.FailureStatus); + return new HealthCheckResult(context.Registration.FailureStatus, data: new ReadOnlyDictionary(checkDetails)); } return healthResponse.Status switch { - Health.Green => HealthCheckResult.Healthy(), - Health.Yellow => HealthCheckResult.Degraded(), - _ => new HealthCheckResult(context.Registration.FailureStatus) + Health.Green => HealthCheckResult.Healthy(data: new ReadOnlyDictionary(checkDetails)), + Health.Yellow => HealthCheckResult.Degraded(data: new ReadOnlyDictionary(checkDetails)), + _ => new HealthCheckResult(context.Registration.FailureStatus, data: new ReadOnlyDictionary(checkDetails)) }; } @@ -77,12 +84,12 @@ public async Task CheckHealthAsync(HealthCheckContext context bool isSuccess = pingResult.ApiCall.HttpStatusCode == 200; return isSuccess - ? HealthCheckResult.Healthy() - : new HealthCheckResult(context.Registration.FailureStatus); + ? HealthCheckResult.Healthy(data: new ReadOnlyDictionary(checkDetails)) + : new HealthCheckResult(context.Registration.FailureStatus, data: new ReadOnlyDictionary(checkDetails)); } catch (Exception ex) { - return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex, data: new ReadOnlyDictionary(checkDetails)); } } } diff --git a/src/HealthChecks.InfluxDB/InfluxDBHealthCheck.cs b/src/HealthChecks.InfluxDB/InfluxDBHealthCheck.cs index 983c909c2d..4b5fbacf30 100644 --- a/src/HealthChecks.InfluxDB/InfluxDBHealthCheck.cs +++ b/src/HealthChecks.InfluxDB/InfluxDBHealthCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using InfluxDB.Client; using InfluxDB.Client.Api.Domain; using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -10,6 +11,10 @@ public class InfluxDBHealthCheck : IHealthCheck, IDisposable private readonly InfluxDBClient _influxDbClient; #pragma warning restore IDISP008 // Don't assign member with injected and created disposables private readonly bool _ownsClient; + private readonly Dictionary _baseCheckDetails = new Dictionary{ + { "healthcheck.type", nameof(InfluxDBHealthCheck) }, + { "db.system", "influxdb" } + }; public InfluxDBHealthCheck(Func _options) { @@ -26,6 +31,7 @@ public InfluxDBHealthCheck(InfluxDBClient influxDBClient) /// public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + Dictionary checkDetails = _baseCheckDetails; try { var ready = await _influxDbClient.ReadyAsync().ConfigureAwait(false); @@ -35,17 +41,17 @@ public async Task CheckHealthAsync(HealthCheckContext context { var me = await _influxDbClient.GetUsersApi().MeAsync(cancellationToken).ConfigureAwait(false); return me?.Status == User.StatusEnum.Active - ? HealthCheckResult.Healthy($"Started:{ready.Started} Up:{ready.Up}") - : HealthCheckResult.Degraded($"User status is {me?.Status}."); + ? HealthCheckResult.Healthy($"Started:{ready.Started} Up:{ready.Up}", data: new ReadOnlyDictionary(checkDetails)) + : HealthCheckResult.Degraded($"User status is {me?.Status}.", data: new ReadOnlyDictionary(checkDetails)); } else { - return HealthCheckResult.Unhealthy($"Ping:{ping} Status:{ready.Status} Started:{ready.Started} Up:{ready.Up}"); + return HealthCheckResult.Unhealthy($"Ping:{ping} Status:{ready.Status} Started:{ready.Started} Up:{ready.Up}", data: new ReadOnlyDictionary(checkDetails)); } } catch (Exception ex) { - return HealthCheckResult.Unhealthy(ex.Message, exception: ex); + return new HealthCheckResult(context.Registration.FailureStatus, ex.Message, exception: ex, data: new ReadOnlyDictionary(checkDetails)); } } diff --git a/src/HealthChecks.MongoDb/MongoDbHealthCheck.cs b/src/HealthChecks.MongoDb/MongoDbHealthCheck.cs index a0558bad7a..a797f4a09a 100644 --- a/src/HealthChecks.MongoDb/MongoDbHealthCheck.cs +++ b/src/HealthChecks.MongoDb/MongoDbHealthCheck.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Collections.ObjectModel; using Microsoft.Extensions.Diagnostics.HealthChecks; using MongoDB.Bson; using MongoDB.Driver; @@ -11,6 +12,10 @@ public class MongoDbHealthCheck : IHealthCheck private static readonly ConcurrentDictionary _mongoClient = new(); private readonly MongoClientSettings _mongoClientSettings; private readonly string? _specifiedDatabase; + private readonly Dictionary _baseCheckDetails = new Dictionary{ + { "healthcheck.type", nameof(MongoDbHealthCheck) }, + { "db.system", "mongodb" } + }; public MongoDbHealthCheck(string connectionString, string? databaseName = default) : this(MongoClientSettings.FromUrl(MongoUrl.Create(connectionString)), databaseName) @@ -36,12 +41,16 @@ public MongoDbHealthCheck(MongoClientSettings clientSettings, string? databaseNa /// public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + Dictionary checkDetails = _baseCheckDetails; try { var mongoClient = _mongoClient.GetOrAdd(_mongoClientSettings.ToString(), _ => new MongoClient(_mongoClientSettings)); + checkDetails.Add("server.address", _mongoClientSettings.Server.Host); + checkDetails.Add("server.port", _mongoClientSettings.Server.Port); if (!string.IsNullOrEmpty(_specifiedDatabase)) { + checkDetails.Add("db.namespace", _specifiedDatabase); // some users can't list all databases depending on database privileges, with // this you can check a specified database. // Related with issue #43 and #617 @@ -57,11 +66,11 @@ await mongoClient await cursor.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false); } - return HealthCheckResult.Healthy(); + return HealthCheckResult.Healthy(data: new ReadOnlyDictionary(checkDetails)); } catch (Exception ex) { - return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex, data: new ReadOnlyDictionary(checkDetails)); } } } diff --git a/src/HealthChecks.MySql/MySqlHealthCheck.cs b/src/HealthChecks.MySql/MySqlHealthCheck.cs index 8a009836ac..2c6a92925f 100644 --- a/src/HealthChecks.MySql/MySqlHealthCheck.cs +++ b/src/HealthChecks.MySql/MySqlHealthCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using Microsoft.Extensions.Diagnostics.HealthChecks; using MySqlConnector; @@ -9,6 +10,10 @@ namespace HealthChecks.MySql; public class MySqlHealthCheck : IHealthCheck { private readonly MySqlHealthCheckOptions _options; + private readonly Dictionary _baseCheckDetails = new Dictionary{ + { "healthcheck.type", nameof(MySqlHealthCheck) }, + { "db.system", "mysql" } + }; public MySqlHealthCheck(MySqlHealthCheckOptions options) { @@ -18,11 +23,14 @@ public MySqlHealthCheck(MySqlHealthCheckOptions options) /// public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + Dictionary checkDetails = _baseCheckDetails; try { using var connection = _options.DataSource is not null ? _options.DataSource.CreateConnection() : new MySqlConnection(_options.ConnectionString); + checkDetails.Add("db.namespace", connection.Database); + checkDetails.Add("server.address", connection.DataSource); _options.Configure?.Invoke(connection); await connection.OpenAsync(cancellationToken).ConfigureAwait(false); @@ -31,6 +39,7 @@ public async Task CheckHealthAsync(HealthCheckContext context { using var command = connection.CreateCommand(); command.CommandText = _options.CommandText; + checkDetails.Add("db.query.text", _options.CommandText); object? result = await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); return _options.HealthCheckResultBuilder == null @@ -41,13 +50,13 @@ public async Task CheckHealthAsync(HealthCheckContext context { var success = await connection.PingAsync(cancellationToken).ConfigureAwait(false); return _options.HealthCheckResultBuilder is null - ? (success ? HealthCheckResult.Healthy() : new HealthCheckResult(context.Registration.FailureStatus)) : + ? (success ? HealthCheckResult.Healthy(data: new ReadOnlyDictionary(checkDetails)) : new HealthCheckResult(context.Registration.FailureStatus, data: new ReadOnlyDictionary(checkDetails))) : _options.HealthCheckResultBuilder(success); } } catch (Exception ex) { - return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex, data: new ReadOnlyDictionary(checkDetails)); } } } diff --git a/src/HealthChecks.NpgSql/NpgSqlHealthCheck.cs b/src/HealthChecks.NpgSql/NpgSqlHealthCheck.cs index 379ab32357..e986e5f059 100644 --- a/src/HealthChecks.NpgSql/NpgSqlHealthCheck.cs +++ b/src/HealthChecks.NpgSql/NpgSqlHealthCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using System.Diagnostics; using Microsoft.Extensions.Diagnostics.HealthChecks; using Npgsql; @@ -10,6 +11,10 @@ namespace HealthChecks.NpgSql; public class NpgSqlHealthCheck : IHealthCheck { private readonly NpgSqlHealthCheckOptions _options; + private readonly Dictionary _baseCheckDetails = new Dictionary{ + { "healthcheck.type", nameof(NpgSqlHealthCheck) }, + { "db.system", "npgsql" } + }; public NpgSqlHealthCheck(NpgSqlHealthCheckOptions options) { @@ -21,12 +26,16 @@ public NpgSqlHealthCheck(NpgSqlHealthCheckOptions options) /// public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + Dictionary checkDetails = _baseCheckDetails; try { await using var connection = _options.DataSource is not null ? _options.DataSource.CreateConnection() : new NpgsqlConnection(_options.ConnectionString); + checkDetails.Add("db.query.text", _options.CommandText); + checkDetails.Add("db.namespace", connection.Database); + checkDetails.Add("server.address", connection.DataSource); _options.Configure?.Invoke(connection); await connection.OpenAsync(cancellationToken).ConfigureAwait(false); @@ -35,12 +44,12 @@ public async Task CheckHealthAsync(HealthCheckContext context var result = await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); return _options.HealthCheckResultBuilder == null - ? HealthCheckResult.Healthy() + ? HealthCheckResult.Healthy(data: new ReadOnlyDictionary(checkDetails)) : _options.HealthCheckResultBuilder(result); } catch (Exception ex) { - return new HealthCheckResult(context.Registration.FailureStatus, description: ex.Message, exception: ex); + return new HealthCheckResult(context.Registration.FailureStatus, description: ex.Message, exception: ex, data: new ReadOnlyDictionary(checkDetails)); } } } diff --git a/src/HealthChecks.Oracle/OracleHealthCheck.cs b/src/HealthChecks.Oracle/OracleHealthCheck.cs index f03a7e247e..7ef5df5dd7 100644 --- a/src/HealthChecks.Oracle/OracleHealthCheck.cs +++ b/src/HealthChecks.Oracle/OracleHealthCheck.cs @@ -9,6 +9,10 @@ namespace HealthChecks.Oracle; public class OracleHealthCheck : IHealthCheck { private readonly OracleHealthCheckOptions _options; + private readonly Dictionary _baseCheckDetails = new Dictionary{ + { "healthcheck.type", nameof(OracleHealthCheck) }, + { "db.system", "oracle" } + }; public OracleHealthCheck(OracleHealthCheckOptions options) { @@ -22,9 +26,13 @@ public async Task CheckHealthAsync(HealthCheckContext context { try { + Dictionary checkDetails = _baseCheckDetails; using var connection = _options.Credential == null ? new OracleConnection(_options.ConnectionString) : new OracleConnection(_options.ConnectionString, _options.Credential); + checkDetails.Add("db.query.text", _options.CommandText); + checkDetails.Add("db.namespace", connection.Database); + checkDetails.Add("server.address", connection.DataSource); _options.Configure?.Invoke(connection); await connection.OpenAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/HealthChecks.RavenDB/RavenDBHealthCheck.cs b/src/HealthChecks.RavenDB/RavenDBHealthCheck.cs index 66158c8fd7..526dd5c2d0 100644 --- a/src/HealthChecks.RavenDB/RavenDBHealthCheck.cs +++ b/src/HealthChecks.RavenDB/RavenDBHealthCheck.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Collections.ObjectModel; using Microsoft.Extensions.Diagnostics.HealthChecks; using Raven.Client.Documents; using Raven.Client.Documents.Conventions; @@ -26,6 +27,10 @@ public class RavenDBHealthCheck : IHealthCheck private static readonly GetStatisticsOperation _legacyDatabaseHealthCheck = new(); private static readonly ConcurrentDictionary _stores = new(); + private readonly Dictionary _baseCheckDetails = new Dictionary{ + { "healthcheck.type", nameof(RavenDBHealthCheck) }, + { "db.system", "ravendb" } + }; public RavenDBHealthCheck(RavenDBOptions options) { @@ -39,6 +44,7 @@ public RavenDBHealthCheck(RavenDBOptions options) [System.Diagnostics.CodeAnalysis.SuppressMessage("IDisposableAnalyzers.Correctness", "IDISP004:Don't ignore created IDisposable", Justification = "DocumentStore instances are static")] public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + Dictionary checkDetails = _baseCheckDetails; try { var value = _stores.GetOrAdd(_options, o => @@ -49,11 +55,13 @@ public async Task CheckHealthAsync(HealthCheckContext context Certificate = o.Certificate }; + checkDetails.Add("server.address", _options.Urls); try { store.Initialize(); if (!string.IsNullOrWhiteSpace(_options.Database)) { + checkDetails.Add("db.namespace", _options.Database); store.SetRequestTimeout(_options.RequestTimeout ?? TimeSpan.FromSeconds(DEFAULT_REQUEST_TIMEOUT_IN_SECONDS), _options.Database); } @@ -99,16 +107,16 @@ public async Task CheckHealthAsync(HealthCheckContext context await CheckDatabaseHealthAsync(store, _options.Database!, value.Legacy, cancellationToken).ConfigureAwait(false); } - return HealthCheckResult.Healthy(); + return HealthCheckResult.Healthy(data: new ReadOnlyDictionary(checkDetails)); } catch (DatabaseDoesNotExistException) { - return new HealthCheckResult(context.Registration.FailureStatus, $"RavenDB does not contain '{_options.Database}' database."); + return new HealthCheckResult(context.Registration.FailureStatus, $"RavenDB does not contain '{_options.Database}' database.", data: new ReadOnlyDictionary(checkDetails)); } } catch (Exception ex) { - return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex, data: new ReadOnlyDictionary(checkDetails)); } } diff --git a/src/HealthChecks.Redis/RedisHealthCheck.cs b/src/HealthChecks.Redis/RedisHealthCheck.cs index 48a163674d..6f211761f5 100644 --- a/src/HealthChecks.Redis/RedisHealthCheck.cs +++ b/src/HealthChecks.Redis/RedisHealthCheck.cs @@ -1,4 +1,6 @@ using System.Collections.Concurrent; +using System.Collections.ObjectModel; +using System.Net; using Microsoft.Extensions.Diagnostics.HealthChecks; using StackExchange.Redis; @@ -13,6 +15,10 @@ public class RedisHealthCheck : IHealthCheck private readonly string? _redisConnectionString; private readonly IConnectionMultiplexer? _connectionMultiplexer; private readonly Func? _connectionMultiplexerFactory; + private readonly Dictionary _baseCheckDetails = new Dictionary{ + { "healthcheck.type", nameof(RedisHealthCheck) }, + { "db.system", "redis" } + }; public RedisHealthCheck(string redisConnectionString) { @@ -42,6 +48,7 @@ internal RedisHealthCheck(Func connectionMultiplexerFact /// public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + Dictionary checkDetails = _baseCheckDetails; try { IConnectionMultiplexer? connection = _connectionMultiplexer ?? _connectionMultiplexerFactory?.Invoke(); @@ -68,8 +75,15 @@ public async Task CheckHealthAsync(HealthCheckContext context foreach (var endPoint in connection!.GetEndPoints(configuredOnly: true)) { + if (endPoint == null) + { + continue; + } var server = connection.GetServer(endPoint); + checkDetails.Add("server.address", (endPoint as IPEndPoint).Address); + checkDetails.Add("server.po ", (endPoint as IPEndPoint).Port); + if (server.ServerType != ServerType.Cluster) { await connection.GetDatabase().PingAsync().ConfigureAwait(false); @@ -84,18 +98,18 @@ public async Task CheckHealthAsync(HealthCheckContext context if (!clusterInfo.ToString()!.Contains("cluster_state:ok")) { //cluster info is not ok! - return new HealthCheckResult(context.Registration.FailureStatus, description: $"INFO CLUSTER is not on OK state for endpoint {endPoint}"); + return new HealthCheckResult(context.Registration.FailureStatus, description: $"INFO CLUSTER is not on OK state for endpoint {endPoint}", data: new ReadOnlyDictionary(checkDetails)); } } else { //cluster info cannot be read for this cluster node - return new HealthCheckResult(context.Registration.FailureStatus, description: $"INFO CLUSTER is null or can't be read for endpoint {endPoint}"); + return new HealthCheckResult(context.Registration.FailureStatus, description: $"INFO CLUSTER is null or can't be read for endpoint {endPoint}", data: new ReadOnlyDictionary(checkDetails)); } } } - return HealthCheckResult.Healthy(); + return HealthCheckResult.Healthy(data: new ReadOnlyDictionary(checkDetails)); } catch (Exception ex) { @@ -106,7 +120,7 @@ public async Task CheckHealthAsync(HealthCheckContext context connection?.Dispose(); #pragma warning restore IDISP007 // Don't dispose injected } - return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex, data: new ReadOnlyDictionary(checkDetails)); } } diff --git a/src/HealthChecks.SqlServer/SqlServerHealthCheck.cs b/src/HealthChecks.SqlServer/SqlServerHealthCheck.cs index 32f2261e96..6a296549d3 100644 --- a/src/HealthChecks.SqlServer/SqlServerHealthCheck.cs +++ b/src/HealthChecks.SqlServer/SqlServerHealthCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -9,6 +10,10 @@ namespace HealthChecks.SqlServer; public class SqlServerHealthCheck : IHealthCheck { private readonly SqlServerHealthCheckOptions _options; + private readonly Dictionary _baseCheckDetails = new Dictionary{ + { "healthcheck.type", nameof(SqlServerHealthCheck) }, + { "db.system", "mssql" } + }; public SqlServerHealthCheck(SqlServerHealthCheckOptions options) { @@ -20,9 +25,13 @@ public SqlServerHealthCheck(SqlServerHealthCheckOptions options) /// public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + Dictionary checkDetails = _baseCheckDetails; try { + checkDetails.Add("db.query.text", _options.CommandText); using var connection = new SqlConnection(_options.ConnectionString); + checkDetails.Add("db.namespace", connection.Database); + checkDetails.Add("server.address", connection.DataSource); _options.Configure?.Invoke(connection); await connection.OpenAsync(cancellationToken).ConfigureAwait(false); @@ -32,12 +41,12 @@ public async Task CheckHealthAsync(HealthCheckContext context object result = await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); return _options.HealthCheckResultBuilder == null - ? HealthCheckResult.Healthy() + ? HealthCheckResult.Healthy(data: new ReadOnlyDictionary(checkDetails)) : _options.HealthCheckResultBuilder(result); } catch (Exception ex) { - return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex, data: new ReadOnlyDictionary(checkDetails)); } } } diff --git a/src/HealthChecks.Sqlite/SqliteHealthCheck.cs b/src/HealthChecks.Sqlite/SqliteHealthCheck.cs index 37d8c2e825..1eabc724b9 100644 --- a/src/HealthChecks.Sqlite/SqliteHealthCheck.cs +++ b/src/HealthChecks.Sqlite/SqliteHealthCheck.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using Microsoft.Data.Sqlite; using Microsoft.Extensions.Diagnostics.HealthChecks; @@ -9,6 +10,10 @@ namespace HealthChecks.Sqlite; public class SqliteHealthCheck : IHealthCheck { private readonly SqliteHealthCheckOptions _options; + private readonly Dictionary _baseCheckDetails = new Dictionary{ + { "healthcheck.type", nameof(SqliteHealthCheck) }, + { "db.system", "sqlite" } + }; public SqliteHealthCheck(SqliteHealthCheckOptions options) { @@ -20,10 +25,12 @@ public SqliteHealthCheck(SqliteHealthCheckOptions options) /// public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { + Dictionary checkDetails = _baseCheckDetails; try { using var connection = new SqliteConnection(_options.ConnectionString); - + checkDetails.Add("db.query.text", _options.CommandText); + checkDetails.Add("db.namespace", connection.Database); _options.Configure?.Invoke(connection); await connection.OpenAsync(cancellationToken).ConfigureAwait(false); @@ -32,12 +39,12 @@ public async Task CheckHealthAsync(HealthCheckContext context object? result = await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); return _options.HealthCheckResultBuilder == null - ? HealthCheckResult.Healthy() + ? HealthCheckResult.Healthy(data: new ReadOnlyDictionary(checkDetails)) : _options.HealthCheckResultBuilder(result); } catch (Exception ex) { - return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex, data: new ReadOnlyDictionary(checkDetails)); } } }