mirror of https://github.com/Squidex/squidex.git
27 changed files with 381 additions and 21 deletions
@ -0,0 +1,36 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Orleans; |
||||
|
using Squidex.Domain.Apps.Entities.Apps.Indexes; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Diagnostics; |
||||
|
using Squidex.Infrastructure.Orleans; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Apps.Diagnostics |
||||
|
{ |
||||
|
public sealed class OrleansAppsHealthCheck : IHealthCheck |
||||
|
{ |
||||
|
private readonly IAppsByNameIndex index; |
||||
|
|
||||
|
public OrleansAppsHealthCheck(IGrainFactory grainFactory) |
||||
|
{ |
||||
|
Guard.NotNull(grainFactory, nameof(grainFactory)); |
||||
|
|
||||
|
index = grainFactory.GetGrain<IAppsByNameIndex>(SingleGrain.Id); |
||||
|
} |
||||
|
|
||||
|
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) |
||||
|
{ |
||||
|
await index.CountAsync(); |
||||
|
|
||||
|
return new HealthCheckResult(true); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using EventStore.ClientAPI; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Diagnostics |
||||
|
{ |
||||
|
public sealed class GetEventStoreHealthCheck : IHealthCheck |
||||
|
{ |
||||
|
private readonly IEventStoreConnection connection; |
||||
|
|
||||
|
public GetEventStoreHealthCheck(IEventStoreConnection connection) |
||||
|
{ |
||||
|
Guard.NotNull(connection, nameof(connection)); |
||||
|
|
||||
|
this.connection = connection; |
||||
|
} |
||||
|
|
||||
|
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) |
||||
|
{ |
||||
|
await connection.ReadEventAsync("test", 1, false); |
||||
|
|
||||
|
return new HealthCheckResult(true, "Querying test event from event store."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using MongoDB.Driver; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Diagnostics |
||||
|
{ |
||||
|
public sealed class MongoDBHealthCheck : IHealthCheck |
||||
|
{ |
||||
|
private readonly IMongoDatabase mongoDatabase; |
||||
|
|
||||
|
public MongoDBHealthCheck(IMongoDatabase mongoDatabase) |
||||
|
{ |
||||
|
Guard.NotNull(mongoDatabase, nameof(mongoDatabase)); |
||||
|
|
||||
|
this.mongoDatabase = mongoDatabase; |
||||
|
} |
||||
|
|
||||
|
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) |
||||
|
{ |
||||
|
var collectionNames = await mongoDatabase.ListCollectionNamesAsync(cancellationToken: cancellationToken); |
||||
|
|
||||
|
var result = await collectionNames.AnyAsync(cancellationToken); |
||||
|
|
||||
|
return new HealthCheckResult(result); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Diagnostics |
||||
|
{ |
||||
|
public sealed class GCHealthCheck : IHealthCheck |
||||
|
{ |
||||
|
private readonly long threshold; |
||||
|
|
||||
|
public GCHealthCheck(IOptions<GCHealthCheckOptions> options) |
||||
|
{ |
||||
|
Guard.NotNull(options, nameof(options)); |
||||
|
|
||||
|
threshold = 1024 * 1024 * options.Value.Threshold; |
||||
|
} |
||||
|
|
||||
|
public Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) |
||||
|
{ |
||||
|
var allocated = GC.GetTotalMemory(false); |
||||
|
|
||||
|
var data = new Dictionary<string, object>() |
||||
|
{ |
||||
|
{ "Allocated", allocated }, |
||||
|
{ "Gen0Collections", GC.CollectionCount(0) }, |
||||
|
{ "Gen1Collections", GC.CollectionCount(1) }, |
||||
|
{ "Gen2Collections", GC.CollectionCount(2) }, |
||||
|
}; |
||||
|
|
||||
|
return Task.FromResult(new HealthCheckResult(allocated < threshold, $"Reports degraded status if allocated bytes >= {threshold.ToReadableSize()}", data)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
namespace Squidex.Infrastructure.Diagnostics |
||||
|
{ |
||||
|
public sealed class GCHealthCheckOptions |
||||
|
{ |
||||
|
public long Threshold { get; set; } = 4 * 1024L; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Diagnostics |
||||
|
{ |
||||
|
public sealed class HealthCheckResult |
||||
|
{ |
||||
|
public bool IsValid { get; } |
||||
|
|
||||
|
public string Description { get; } |
||||
|
|
||||
|
public Dictionary<string, object> Data { get; } |
||||
|
|
||||
|
public HealthCheckResult(bool isValid, string description = null, Dictionary<string, object> data = null) |
||||
|
{ |
||||
|
IsValid = isValid; |
||||
|
Data = data; |
||||
|
Description = description; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Diagnostics |
||||
|
{ |
||||
|
public interface IHealthCheck |
||||
|
{ |
||||
|
Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Orleans; |
||||
|
using Orleans.Runtime; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.Diagnostics |
||||
|
{ |
||||
|
public sealed class OrleansHealthCheck : IHealthCheck |
||||
|
{ |
||||
|
private readonly IManagementGrain managementGrain; |
||||
|
|
||||
|
public OrleansHealthCheck(IGrainFactory grainFactory) |
||||
|
{ |
||||
|
Guard.NotNull(grainFactory, nameof(grainFactory)); |
||||
|
|
||||
|
managementGrain = grainFactory.GetGrain<IManagementGrain>(0); |
||||
|
} |
||||
|
|
||||
|
public async Task<HealthCheckResult> CheckHealthAsync(CancellationToken cancellationToken = default(CancellationToken)) |
||||
|
{ |
||||
|
var activationCount = await managementGrain.GetTotalActivationCount(); |
||||
|
|
||||
|
return new HealthCheckResult(activationCount > 0, "Orleans must have at least one activation."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Http; |
||||
|
using Newtonsoft.Json; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Diagnostics; |
||||
|
|
||||
|
namespace Squidex.Pipeline.Diagnostics |
||||
|
{ |
||||
|
public sealed class HealthCheckMiddleware : IMiddleware |
||||
|
{ |
||||
|
private const string Suffix = "HealthCheck"; |
||||
|
private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(2); |
||||
|
|
||||
|
private readonly Dictionary<string, IHealthCheck> healthChecks; |
||||
|
private readonly JsonSerializerSettings serializerSettings; |
||||
|
|
||||
|
public HealthCheckMiddleware(IEnumerable<IHealthCheck> healthChecks, JsonSerializerSettings serializerSettings) |
||||
|
{ |
||||
|
Guard.NotNull(healthChecks, nameof(healthChecks)); |
||||
|
Guard.NotNull(serializerSettings, nameof(serializerSettings)); |
||||
|
|
||||
|
this.healthChecks = healthChecks.ToDictionary(GetName); |
||||
|
this.serializerSettings = serializerSettings; |
||||
|
} |
||||
|
|
||||
|
public async Task InvokeAsync(HttpContext context, RequestDelegate next) |
||||
|
{ |
||||
|
using (var cts = new CancellationTokenSource(Timeout)) |
||||
|
{ |
||||
|
var checks = await Task.WhenAll(healthChecks.Select(x => MakeHealthCheckAsync(x.Key, x.Value, cts.Token))); |
||||
|
|
||||
|
context.Response.StatusCode = 200; |
||||
|
context.Response.Headers.Add("content-type", "application/json"); |
||||
|
|
||||
|
if (checks.Any(x => !x.Result.IsValid)) |
||||
|
{ |
||||
|
context.Response.StatusCode = 503; |
||||
|
} |
||||
|
|
||||
|
var response = checks.ToDictionary(x => x.Name, x => x.Result); |
||||
|
|
||||
|
await context.Response.WriteAsync(JsonConvert.SerializeObject(new { status = response }, Formatting.Indented, serializerSettings)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static string GetName(IHealthCheck check) |
||||
|
{ |
||||
|
var name = check.GetType().Name.ToCamelCase(); |
||||
|
|
||||
|
if (name.EndsWith(Suffix, StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
name = name.Substring(0, name.Length - Suffix.Length); |
||||
|
} |
||||
|
|
||||
|
return name; |
||||
|
} |
||||
|
|
||||
|
private async Task<(string Name, HealthCheckResult Result)> MakeHealthCheckAsync(string name, IHealthCheck check, CancellationToken ct) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var result = await check.CheckHealthAsync(ct); |
||||
|
|
||||
|
return (name, result); |
||||
|
} |
||||
|
catch |
||||
|
{ |
||||
|
return (name, new HealthCheckResult(false)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue