Skip to content
Open
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
3 changes: 3 additions & 0 deletions src/ApiGateway/ApiGateway.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Shared\Shared.Monitoring\Shared.Monitoring.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Yarp.ReverseProxy" Version="2.*" />
</ItemGroup>
Expand Down
5 changes: 5 additions & 0 deletions src/ApiGateway/Program.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
using Shared.Monitoring.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

builder.Services.AddHealthChecks();

builder.Services.AddAppInsightsMonitoring(builder.Configuration, "ApiGateway");

var app = builder.Build();

app.UseAppInsightsMonitoring();
app.MapReverseProxy();
app.MapHealthChecks("/healthz");

Expand Down
10 changes: 10 additions & 0 deletions src/Microservices.sln
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.Contracts", "Shared\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.Infrastructure", "Shared\Shared.Infrastructure\Shared.Infrastructure.csproj", "{D0000002-0000-0000-0000-000000000001}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared.Monitoring", "Shared\Shared.Monitoring\Shared.Monitoring.csproj", "{D0000003-0000-0000-0000-000000000001}"
EndProject

Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Monitoring", "Monitoring", "{A1B2C3D4-0008-0000-0000-000000000001}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Monitoring.Agent", "Services\Monitoring\Monitoring.Agent\Monitoring.Agent.csproj", "{B6000001-0000-0000-0000-000000000001}"
EndProject

Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -88,5 +95,8 @@ Global
{B5000001-0000-0000-0000-000000000001} = {A1B2C3D4-0006-0000-0000-000000000001}
{B5000002-0000-0000-0000-000000000001} = {A1B2C3D4-0006-0000-0000-000000000001}
{B5000003-0000-0000-0000-000000000001} = {A1B2C3D4-0006-0000-0000-000000000001}
{D0000003-0000-0000-0000-000000000001} = {A1B2C3D4-0007-0000-0000-000000000001}
{A1B2C3D4-0008-0000-0000-000000000001} = {A1B2C3D4-0001-0000-0000-000000000001}
{B6000001-0000-0000-0000-000000000001} = {A1B2C3D4-0008-0000-0000-000000000001}
EndGlobalSection
EndGlobal
5 changes: 3 additions & 2 deletions src/Services/Customer/Customer.API/Customer.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
<ItemGroup>
<ProjectReference Include="..\Customer.Domain\Customer.Domain.csproj" />
<ProjectReference Include="..\Customer.Infrastructure\Customer.Infrastructure.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Infrastructure\Shared.Infrastructure.csproj" />
<ProjectReference Include="..\..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
<ProjectReference Include="..\..\..\Shared\Shared.Infrastructure\Shared.Infrastructure.csproj" />
<ProjectReference Include="..\..\..\Shared\Shared.Monitoring\Shared.Monitoring.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.*" />
Expand Down
4 changes: 4 additions & 0 deletions src/Services/Customer/Customer.API/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Customer.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Shared.Monitoring.Extensions;

var builder = WebApplication.CreateBuilder(args);

Expand All @@ -11,6 +12,8 @@
builder.Services.AddDbContext<CustomerDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddAppInsightsMonitoring(builder.Configuration, "CustomerService");

var app = builder.Build();

if (app.Environment.IsDevelopment())
Expand All @@ -19,6 +22,7 @@
app.UseSwaggerUI();
}

app.UseAppInsightsMonitoring();
app.MapControllers();
app.MapHealthChecks("/healthz");

Expand Down
5 changes: 3 additions & 2 deletions src/Services/Identity/Identity.API/Identity.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
<ItemGroup>
<ProjectReference Include="..\Identity.Domain\Identity.Domain.csproj" />
<ProjectReference Include="..\Identity.Infrastructure\Identity.Infrastructure.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Infrastructure\Shared.Infrastructure.csproj" />
<ProjectReference Include="..\..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
<ProjectReference Include="..\..\..\Shared\Shared.Infrastructure\Shared.Infrastructure.csproj" />
<ProjectReference Include="..\..\..\Shared\Shared.Monitoring\Shared.Monitoring.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.*" />
Expand Down
4 changes: 4 additions & 0 deletions src/Services/Identity/Identity.API/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Identity.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Shared.Monitoring.Extensions;

var builder = WebApplication.CreateBuilder(args);

Expand All @@ -11,6 +12,8 @@
builder.Services.AddDbContext<IdentityDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddAppInsightsMonitoring(builder.Configuration, "IdentityService");

var app = builder.Build();

if (app.Environment.IsDevelopment())
Expand All @@ -19,6 +22,7 @@
app.UseSwaggerUI();
}

app.UseAppInsightsMonitoring();
app.MapControllers();
app.MapHealthChecks("/healthz");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using Microsoft.AspNetCore.Mvc;
using Monitoring.Agent.Models;
using Monitoring.Agent.Services;

namespace Monitoring.Agent.Controllers;

[ApiController]
[Route("api/[controller]")]
public class MonitoringController : ControllerBase
{
private readonly ServiceHealthAggregator _aggregator;
private readonly ILogger<MonitoringController> _logger;

public MonitoringController(
ServiceHealthAggregator aggregator,
ILogger<MonitoringController> logger)
{
_aggregator = aggregator;
_logger = logger;
}

/// <summary>
/// Returns the full AI monitoring dashboard with health scores, anomalies, and insights.
/// </summary>
[HttpGet("dashboard")]
public async Task<ActionResult<MonitoringDashboard>> GetDashboard()
{
var dashboard = await _aggregator.GetDashboardAsync();
return Ok(dashboard);
}

/// <summary>
/// Returns the most recent cached dashboard snapshot without re-probing services.
/// </summary>
[HttpGet("dashboard/latest")]
public ActionResult<MonitoringDashboard> GetLatestDashboard()
{
var dashboard = MonitoringBackgroundService.LatestDashboard;
if (dashboard is null)
return NotFound("No monitoring data available yet. The background service may still be initializing.");

return Ok(dashboard);
}

/// <summary>
/// Returns health score and status for all monitored services.
/// </summary>
[HttpGet("services")]
public async Task<ActionResult<IEnumerable<ServiceHealthReport>>> GetServiceHealth()
{
var dashboard = await _aggregator.GetDashboardAsync();
return Ok(dashboard.Services);
}

/// <summary>
/// Returns health details for a specific service.
/// </summary>
[HttpGet("services/{serviceName}")]
public async Task<ActionResult<ServiceHealthReport>> GetServiceHealth(string serviceName)
{
var dashboard = await _aggregator.GetDashboardAsync();
var service = dashboard.Services
.FirstOrDefault(s => s.ServiceName.Equals(serviceName, StringComparison.OrdinalIgnoreCase));

if (service is null)
return NotFound($"Service '{serviceName}' not found.");

return Ok(service);
}

/// <summary>
/// Returns all active anomalies detected by the AI engine.
/// </summary>
[HttpGet("anomalies")]
public async Task<ActionResult<IEnumerable<AnomalyReport>>> GetAnomalies(
[FromQuery] AnomalySeverity? severity = null)
{
var dashboard = await _aggregator.GetDashboardAsync();
var anomalies = dashboard.RecentAnomalies.AsEnumerable();

if (severity.HasValue)
anomalies = anomalies.Where(a => a.Severity == severity.Value);

return Ok(anomalies);
}

/// <summary>
/// Returns AI-generated insights and recommendations.
/// </summary>
[HttpGet("insights")]
public async Task<ActionResult<IEnumerable<AiInsight>>> GetInsights(
[FromQuery] InsightCategory? category = null,
[FromQuery] InsightPriority? priority = null)
{
var dashboard = await _aggregator.GetDashboardAsync();
var insights = dashboard.Insights.AsEnumerable();

if (category.HasValue)
insights = insights.Where(i => i.Category == category.Value);
if (priority.HasValue)
insights = insights.Where(i => i.Priority == priority.Value);

return Ok(insights);
}

/// <summary>
/// Returns historical dashboard snapshots for trend analysis.
/// </summary>
[HttpGet("history")]
public ActionResult<IEnumerable<MonitoringDashboard>> GetHistory(
[FromQuery] int count = 10)
{
var history = MonitoringBackgroundService.DashboardHistory
.TakeLast(Math.Min(count, 60))
.ToList();

return Ok(history);
}

/// <summary>
/// Returns system-wide health summary.
/// </summary>
[HttpGet("summary")]
public async Task<ActionResult<object>> GetSummary()
{
var dashboard = await _aggregator.GetDashboardAsync();
return Ok(new
{
dashboard.GeneratedAt,
dashboard.OverallStatus,
dashboard.SystemHealthScore,
ServiceCount = dashboard.Services.Count,
HealthyCount = dashboard.Services.Count(s => s.Status == ServiceStatus.Healthy),
DegradedCount = dashboard.Services.Count(s => s.Status == ServiceStatus.Degraded),
UnhealthyCount = dashboard.Services.Count(s => s.Status == ServiceStatus.Unhealthy),
AnomalyCount = dashboard.RecentAnomalies.Count,
CriticalAnomalyCount = dashboard.RecentAnomalies.Count(a => a.Severity == AnomalySeverity.Critical),
InsightCount = dashboard.Insights.Count
});
}
}
95 changes: 95 additions & 0 deletions src/Services/Monitoring/Monitoring.Agent/Models/AnomalyReport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
namespace Monitoring.Agent.Models;

public record AnomalyReport(
string ServiceName,
DateTime DetectedAt,
AnomalySeverity Severity,
string Category,
string Description,
double CurrentValue,
double ThresholdValue,
string RecommendedAction);

public enum AnomalySeverity
{
Info,
Warning,
Critical
}

public record ServiceHealthReport(
string ServiceName,
DateTime Timestamp,
HealthScore HealthScore,
ServiceStatus Status,
PerformanceMetrics Performance,
ResourceUtilization Resources,
List<AnomalyReport> ActiveAnomalies,
List<string> Recommendations);

public record HealthScore(
double Overall,
double Availability,
double Performance,
double ErrorRate,
double ResourceUsage);

public enum ServiceStatus
{
Healthy,
Degraded,
Unhealthy,
Unknown
}

public record PerformanceMetrics(
double AverageResponseTimeMs,
double P95ResponseTimeMs,
int RequestsPerMinute,
double ErrorRatePercent,
int ActiveConnections);

public record ResourceUtilization(
double CpuPercent,
double MemoryMb,
double MemoryPercent,
long GcGen0Collections,
long GcGen1Collections,
long GcGen2Collections,
double GcTotalMemoryMb);

public record AiInsight(
string InsightId,
DateTime GeneratedAt,
InsightCategory Category,
string Title,
string Description,
InsightPriority Priority,
List<string> AffectedServices,
List<string> ActionItems);

public enum InsightCategory
{
Performance,
Reliability,
Scalability,
CostOptimization,
Security
}

public enum InsightPriority
{
Low,
Medium,
High,
Urgent
}

public record MonitoringDashboard(
DateTime GeneratedAt,
string OverallStatus,
double SystemHealthScore,
List<ServiceHealthReport> Services,
List<AnomalyReport> RecentAnomalies,
List<AiInsight> Insights,
Dictionary<string, object> SystemMetrics);
15 changes: 15 additions & 0 deletions src/Services/Monitoring/Monitoring.Agent/Monitoring.Agent.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Shared\Shared.Monitoring\Shared.Monitoring.csproj" />
<ProjectReference Include="..\..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.*" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.*" />
</ItemGroup>
</Project>
Loading