mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
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