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
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ The monolith's bounded contexts are decomposed into the following independently
| `product-service` | 5004 | Product catalog management | `ProductsController`, product models |
| `notification-service` | 5005 | Email and in-app notifications | `NotificationService`, notification models |
| `api-gateway` | 5000 | YARP reverse proxy, request routing, rate limiting | New — replaces monolith's single entry point |
| `ai-monitoring-agent` | 5006 | Azure App Insights AI monitoring, anomaly detection, alerting | New — cross-cutting observability service |

## Project Structure

Expand Down Expand Up @@ -68,10 +69,12 @@ src/
│ │ ├── Product.API/
│ │ ├── Product.Domain/
│ │ └── Product.Infrastructure/
│ └── Notification/
│ ├── Notification.API/
│ ├── Notification.Domain/
│ └── Notification.Infrastructure/
│ ├── Notification/
│ │ ├── Notification.API/
│ │ ├── Notification.Domain/
│ │ └── Notification.Infrastructure/
│ └── Monitoring/
│ └── AIMonitoringAgent/ # App Insights AI monitoring agent
├── Shared/
│ ├── Shared.Contracts/ # Shared DTOs, events, interfaces
│ └── Shared.Infrastructure/ # Common middleware, logging, health checks
Expand Down
17 changes: 17 additions & 0 deletions src/Services/Monitoring/AIMonitoringAgent/AIMonitoringAgent.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>AIMonitoringAgent</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
<ProjectReference Include="..\..\..\Shared\Shared.Infrastructure\Shared.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.*" />
<PackageReference Include="Microsoft.ApplicationInsights" Version="2.*" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.*" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace AIMonitoringAgent.Configuration;

public class MonitoringConfiguration
{
public const string SectionName = "Monitoring";

public string ApplicationInsightsConnectionString { get; set; } = string.Empty;
public int HealthCheckIntervalSeconds { get; set; } = 30;
public double AnomalySensitivityMultiplier { get; set; } = 2.0;

public Dictionary<string, string> ServiceEndpoints { get; set; } = new()
{
["identity-service"] = "http://localhost:5001/healthz",
["customer-service"] = "http://localhost:5002/healthz",
["order-service"] = "http://localhost:5003/healthz",
["product-service"] = "http://localhost:5004/healthz",
["notification-service"] = "http://localhost:5005/healthz",
["api-gateway"] = "http://localhost:5000/healthz"
};

public List<DefaultAlertRule> DefaultAlertRules { get; set; } = new();
}

public class DefaultAlertRule
{
public string Name { get; set; } = string.Empty;
public string MetricName { get; set; } = string.Empty;
public string? ServiceName { get; set; }
public string Condition { get; set; } = "GreaterThan";
public double Threshold { get; set; }
public string Severity { get; set; } = "Warning";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using AIMonitoringAgent.Models;
using AIMonitoringAgent.Services;
using Microsoft.AspNetCore.Mvc;

namespace AIMonitoringAgent.Controllers;

[ApiController]
[Route("api/[controller]")]
public class AlertsController : ControllerBase
{
private readonly IAlertingService _alertingService;

public AlertsController(IAlertingService alertingService)
{
_alertingService = alertingService;
}

[HttpGet("rules")]
public IActionResult GetAllRules()
{
var rules = _alertingService.GetAllRules();
return Ok(rules);
}

[HttpGet("rules/{ruleId}")]
public IActionResult GetRule(string ruleId)
{
var rule = _alertingService.GetRule(ruleId);
if (rule == null) return NotFound();
return Ok(rule);
}

[HttpPost("rules")]
public IActionResult AddRule([FromBody] AlertRule rule)
{
var created = _alertingService.AddRule(rule);
return CreatedAtAction(nameof(GetRule), new { ruleId = created.Id }, created);
}

[HttpDelete("rules/{ruleId}")]
public IActionResult DeleteRule(string ruleId)
{
var removed = _alertingService.RemoveRule(ruleId);
if (!removed) return NotFound();
return NoContent();
}

[HttpPost("evaluate")]
public IActionResult EvaluateMetric([FromBody] MetricEvaluationRequest request)
{
var notification = _alertingService.EvaluateMetric(request.MetricName, request.ServiceName, request.Value);
if (notification == null)
return Ok(new { triggered = false, message = "No alert rules triggered" });

return Ok(new { triggered = true, alert = notification });
}

[HttpGet("recent")]
public IActionResult GetRecentAlerts([FromQuery] int count = 50)
{
var alerts = _alertingService.GetRecentAlerts(count);
return Ok(alerts);
}
}

public class MetricEvaluationRequest
{
public string MetricName { get; set; } = string.Empty;
public string? ServiceName { get; set; }
public double Value { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using AIMonitoringAgent.Models;
using AIMonitoringAgent.Services;
using Microsoft.AspNetCore.Mvc;

namespace AIMonitoringAgent.Controllers;

[ApiController]
[Route("api/[controller]")]
public class MonitoringController : ControllerBase
{
private readonly IAIModelMonitoringService _modelMonitoring;
private readonly IServiceHealthMonitor _healthMonitor;
private readonly IAnomalyDetectionService _anomalyDetection;
private readonly IAppInsightsTelemetryService _telemetryService;

public MonitoringController(
IAIModelMonitoringService modelMonitoring,
IServiceHealthMonitor healthMonitor,
IAnomalyDetectionService anomalyDetection,
IAppInsightsTelemetryService telemetryService)
{
_modelMonitoring = modelMonitoring;
_healthMonitor = healthMonitor;
_anomalyDetection = anomalyDetection;
_telemetryService = telemetryService;
}

[HttpPost("ai-model/track")]
public IActionResult TrackAIModelInvocation([FromBody] AIModelMetrics metrics)
{
_modelMonitoring.RecordInvocation(metrics);
return Accepted(new { message = "AI model invocation tracked successfully" });
}

[HttpGet("ai-model/summary")]
public IActionResult GetAllModelSummaries()
{
var summaries = _modelMonitoring.GetAllModelSummaries();
return Ok(summaries);
}

[HttpGet("ai-model/summary/{modelName}")]
public IActionResult GetModelSummary(string modelName)
{
var summary = _modelMonitoring.GetModelSummary(modelName);
return Ok(summary);
}

[HttpGet("ai-model/invocations")]
public IActionResult GetRecentInvocations([FromQuery] string? modelName = null, [FromQuery] int count = 50)
{
var invocations = _modelMonitoring.GetRecentInvocations(modelName, count);
return Ok(invocations);
}

[HttpGet("health/services")]
public IActionResult GetServiceHealthStatuses()
{
var statuses = _healthMonitor.GetLatestStatuses();
return Ok(statuses);
}

[HttpPost("health/check")]
public async Task<IActionResult> TriggerHealthCheck()
{
var results = await _healthMonitor.CheckAllServicesAsync();
return Ok(results);
}

[HttpPost("health/check/{serviceName}")]
public async Task<IActionResult> CheckServiceHealth(string serviceName, [FromQuery] string endpoint)
{
var result = await _healthMonitor.CheckServiceHealthAsync(serviceName, endpoint);
return Ok(result);
}

[HttpGet("anomalies")]
public IActionResult GetRecentAnomalies([FromQuery] int count = 50)
{
var anomalies = _anomalyDetection.GetRecentAnomalies(count);
return Ok(anomalies);
}

[HttpPost("metrics/custom")]
public IActionResult TrackCustomMetric([FromBody] CustomMetricRequest request)
{
_telemetryService.TrackCustomMetric(request.Name, request.Value, request.Properties);
return Accepted(new { message = "Custom metric tracked" });
}

[HttpGet("dashboard")]
public async Task<IActionResult> GetDashboard()
{
var modelSummaries = _modelMonitoring.GetAllModelSummaries();
var serviceStatuses = _healthMonitor.GetLatestStatuses();
var recentAnomalies = _anomalyDetection.GetRecentAnomalies(10);

return Ok(new
{
timestamp = DateTime.UtcNow,
aiModels = modelSummaries,
services = serviceStatuses,
recentAnomalies = recentAnomalies
});
}
}

public class CustomMetricRequest
{
public string Name { get; set; } = string.Empty;
public double Value { get; set; }
public Dictionary<string, string>? Properties { get; set; }
}
22 changes: 22 additions & 0 deletions src/Services/Monitoring/AIMonitoringAgent/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM mcr.microsoft.com/dotnet/aspnet:10.0-preview AS base
WORKDIR /app
EXPOSE 5006

FROM mcr.microsoft.com/dotnet/sdk:10.0-preview AS build
WORKDIR /src
COPY ["Services/Monitoring/AIMonitoringAgent/AIMonitoringAgent.csproj", "Services/Monitoring/AIMonitoringAgent/"]
COPY ["Shared/Shared.Contracts/Shared.Contracts.csproj", "Shared/Shared.Contracts/"]
COPY ["Shared/Shared.Infrastructure/Shared.Infrastructure.csproj", "Shared/Shared.Infrastructure/"]
RUN dotnet restore "Services/Monitoring/AIMonitoringAgent/AIMonitoringAgent.csproj"
COPY . .
WORKDIR "/src/Services/Monitoring/AIMonitoringAgent"
RUN dotnet build "AIMonitoringAgent.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "AIMonitoringAgent.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_URLS=http://+:5006
ENTRYPOINT ["dotnet", "AIMonitoringAgent.dll"]
15 changes: 15 additions & 0 deletions src/Services/Monitoring/AIMonitoringAgent/Models/AIModelMetrics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace AIMonitoringAgent.Models;

public class AIModelMetrics
{
public string ModelName { get; set; } = string.Empty;
public string ModelVersion { get; set; } = string.Empty;
public double Latency { get; set; }
public double TokensUsed { get; set; }
public double PromptTokens { get; set; }
public double CompletionTokens { get; set; }
public bool IsSuccessful { get; set; }
public string? ErrorMessage { get; set; }
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
public Dictionary<string, string> CustomProperties { get; set; } = new();
}
45 changes: 45 additions & 0 deletions src/Services/Monitoring/AIMonitoringAgent/Models/AlertRule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace AIMonitoringAgent.Models;

public class AlertRule
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Name { get; set; } = string.Empty;
public string MetricName { get; set; } = string.Empty;
public string? ServiceName { get; set; }
public AlertCondition Condition { get; set; } = AlertCondition.GreaterThan;
public double Threshold { get; set; }
public int EvaluationWindowMinutes { get; set; } = 5;
public AlertSeverityLevel Severity { get; set; } = AlertSeverityLevel.Warning;
public bool IsEnabled { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

public enum AlertCondition
{
GreaterThan,
LessThan,
EqualTo,
GreaterThanOrEqual,
LessThanOrEqual
}

public enum AlertSeverityLevel
{
Information,
Warning,
Error,
Critical
}

public class AlertNotification
{
public string AlertRuleId { get; set; } = string.Empty;
public string AlertRuleName { get; set; } = string.Empty;
public string MetricName { get; set; } = string.Empty;
public string? ServiceName { get; set; }
public double CurrentValue { get; set; }
public double Threshold { get; set; }
public AlertSeverityLevel Severity { get; set; }
public DateTime FiredAt { get; set; } = DateTime.UtcNow;
public string Message { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace AIMonitoringAgent.Models;

public class AnomalyDetectionResult
{
public string MetricName { get; set; } = string.Empty;
public string ServiceName { get; set; } = string.Empty;
public double CurrentValue { get; set; }
public double ExpectedValue { get; set; }
public double DeviationPercentage { get; set; }
public AnomalySeverity Severity { get; set; }
public bool IsAnomaly { get; set; }
public DateTime DetectedAt { get; set; } = DateTime.UtcNow;
public string Description { get; set; } = string.Empty;
}

public enum AnomalySeverity
{
Low,
Medium,
High,
Critical
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace AIMonitoringAgent.Models;

public class ServiceHealthStatus
{
public string ServiceName { get; set; } = string.Empty;
public string Endpoint { get; set; } = string.Empty;
public HealthState State { get; set; } = HealthState.Unknown;
public double ResponseTimeMs { get; set; }
public int HttpStatusCode { get; set; }
public string? ErrorDetails { get; set; }
public DateTime LastCheckedAt { get; set; } = DateTime.UtcNow;
public int ConsecutiveFailures { get; set; }
}

public enum HealthState
{
Healthy,
Degraded,
Unhealthy,
Unknown
}
Loading