Convey.Metrics.AppMetrics
Application metrics collection and monitoring integration using App.Metrics framework providing comprehensive performance monitoring, custom metrics, and observability for microservices.
Installation
dotnet add package Convey.Metrics.AppMetrics
Overview
Convey.Metrics.AppMetrics provides:
- Performance metrics - Request/response times, throughput, and error rates
- Custom metrics - Counters, gauges, histograms, and timers
- Health checks - Application and dependency health monitoring
- Multiple outputs - Export to InfluxDB, Prometheus, Console, and more
- CQRS integration - Automatic metrics for commands, queries, and events
- HTTP metrics - Request tracking and performance monitoring
- Business metrics - Custom business KPIs and domain-specific metrics
- Real-time monitoring - Live metrics collection and reporting
Configuration
Basic Metrics Setup
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddConvey()
.AddMetrics();
var app = builder.Build();
app.UseMetrics();
app.Run();
Advanced Configuration with Multiple Outputs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddConvey()
.AddMetrics(metrics =>
{
metrics.Enabled = true;
metrics.InfluxEnabled = true;
metrics.PrometheusEnabled = true;
metrics.PrometheusFormatter = true;
metrics.InfluxUrl = "http://influxdb:8086";
metrics.Database = "metrics";
metrics.Env = "production";
metrics.Interval = 5;
metrics.Tags = new Dictionary<string, string>
{
["service"] = "user-service",
["version"] = "1.0.0",
["environment"] = "production"
};
});
// Add custom metrics
builder.Services.AddSingleton<ICustomMetrics, CustomMetrics>();
var app = builder.Build();
app.UseMetrics();
app.UseMetricsTextEndpoint(); // Expose metrics at /metrics-text
app.UseMetricsAllEndpoints(); // Expose all metrics endpoints
app.Run();
InfluxDB Configuration
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddConvey()
.AddMetrics(metrics =>
{
metrics.Enabled = true;
metrics.InfluxEnabled = true;
metrics.InfluxUrl = "http://influxdb:8086";
metrics.Database = "microservices_metrics";
metrics.Username = "admin";
metrics.Password = "password";
metrics.RetentionPolicy = "default";
metrics.Env = "production";
metrics.Interval = 10; // Report every 10 seconds
metrics.Tags = new Dictionary<string, string>
{
["service"] = Environment.GetEnvironmentVariable("SERVICE_NAME") ?? "unknown-service",
["version"] = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "1.0.0",
["environment"] = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Development",
["datacenter"] = Environment.GetEnvironmentVariable("DATACENTER") ?? "dc1",
["node"] = Environment.MachineName
};
});
var app = builder.Build();
app.UseMetrics();
app.Run();
Prometheus Configuration
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddConvey()
.AddMetrics(metrics =>
{
metrics.Enabled = true;
metrics.PrometheusEnabled = true;
metrics.PrometheusFormatter = true;
metrics.Interval = 15;
metrics.Tags = new Dictionary<string, string>
{
["job"] = "user-service",
["instance"] = $"{Environment.MachineName}:5000",
["environment"] = "production"
};
});
var app = builder.Build();
app.UseMetrics();
app.UseMetricsTextEndpoint("/metrics"); // Prometheus scraping endpoint
app.Run();
Key Features
1. HTTP Request Metrics
Automatic HTTP request tracking and performance monitoring:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddConvey()
.AddWebApi()
.AddMetrics();
var app = builder.Build();
app.UseMetrics();
// Automatic HTTP metrics collection
app.MapGet("/api/users", async (IUserService userService) =>
{
// Request automatically tracked:
// - Response time
// - Status codes
// - Request count
// - Error rates
var users = await userService.GetUsersAsync();
return Results.Ok(users);
});
app.MapPost("/api/users", async (CreateUserRequest request, IUserService userService) =>
{
var user = await userService.CreateUserAsync(request);
return Results.Created($"/api/users/{user.Id}", user);
});
app.MapGet("/api/users/{id:guid}", async (Guid id, IUserService userService) =>
{
var user = await userService.GetUserByIdAsync(id);
return user != null ? Results.Ok(user) : Results.NotFound();
});
app.MapPut("/api/users/{id:guid}", async (Guid id, UpdateUserRequest request, IUserService userService) =>
{
await userService.UpdateUserAsync(id, request);
return Results.NoContent();
});
app.MapDelete("/api/users/{id:guid}", async (Guid id, IUserService userService) =>
{
await userService.DeleteUserAsync(id);
return Results.NoContent();
});
app.Run();
2. CQRS Metrics Integration
Automatic metrics collection for CQRS operations:
// Commands with automatic metrics
public class CreateUser : ICommand
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public UserRole Role { get; set; }
}
public class UpdateUser : ICommand
{
public Guid Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsActive { get; set; }
}
public class DeleteUser : ICommand
{
public Guid Id { get; set; }
public bool SoftDelete { get; set; } = true;
}
// Command handlers with automatic metrics collection
public class CreateUserHandler : ICommandHandler<CreateUser>
{
private readonly IUserRepository _userRepository;
private readonly IEventBus _eventBus;
private readonly ICustomMetrics _customMetrics;
public CreateUserHandler(
IUserRepository userRepository,
IEventBus eventBus,
ICustomMetrics customMetrics)
{
_userRepository = userRepository;
_eventBus = eventBus;
_customMetrics = customMetrics;
}
// Automatically tracked metrics:
// - Command execution time
// - Success/failure rates
// - Command throughput
public async Task HandleAsync(CreateUser command, CancellationToken cancellationToken = default)
{
// Custom business metric
_customMetrics.IncrementUserRegistrations(command.Role);
var existingUser = await _userRepository.GetByEmailAsync(command.Email);
if (existingUser != null)
{
_customMetrics.IncrementDuplicateEmailAttempts();
throw new UserAlreadyExistsException(command.Email);
}
var user = new User
{
Id = command.Id,
Email = command.Email,
FirstName = command.FirstName,
LastName = command.LastName,
Role = command.Role,
CreatedAt = DateTime.UtcNow,
IsActive = true
};
await _userRepository.CreateAsync(user);
var userCreatedEvent = new UserCreated
{
Id = user.Id,
Email = user.Email,
FirstName = user.FirstName,
LastName = user.LastName,
Role = user.Role,
CreatedAt = user.CreatedAt
};
await _eventBus.PublishAsync(userCreatedEvent, cancellationToken);
// Track successful user creation
_customMetrics.IncrementSuccessfulUserCreations(command.Role);
}
}
// Queries with automatic metrics
public class GetUsers : IQuery<PagedResult<UserDto>>
{
public string SearchTerm { get; set; }
public int Page { get; set; } = 1;
public int PageSize { get; set; } = 20;
public UserRole? Role { get; set; }
public bool? IsActive { get; set; } = true;
}
public class GetUsersHandler : IQueryHandler<GetUsers, PagedResult<UserDto>>
{
private readonly IUserRepository _userRepository;
private readonly ICustomMetrics _customMetrics;
public GetUsersHandler(IUserRepository userRepository, ICustomMetrics customMetrics)
{
_userRepository = userRepository;
_customMetrics = customMetrics;
}
// Automatically tracked:
// - Query execution time
// - Result set sizes
// - Query patterns
public async Task<PagedResult<UserDto>> HandleAsync(GetUsers query, CancellationToken cancellationToken = default)
{
// Track query patterns
_customMetrics.RecordUserQuery(query.SearchTerm, query.Role, query.Page, query.PageSize);
var users = await _userRepository.GetUsersAsync(
searchTerm: query.SearchTerm,
page: query.Page,
pageSize: query.PageSize,
role: query.Role,
isActive: query.IsActive);
var totalCount = await _userRepository.GetUserCountAsync(
searchTerm: query.SearchTerm,
role: query.Role,
isActive: query.IsActive);
var userDtos = users.Select(u => new UserDto
{
Id = u.Id,
Email = u.Email,
FirstName = u.FirstName,
LastName = u.LastName,
Role = u.Role,
IsActive = u.IsActive,
CreatedAt = u.CreatedAt,
UpdatedAt = u.UpdatedAt
}).ToList();
var result = new PagedResult<UserDto>
{
Items = userDtos,
TotalCount = totalCount,
Page = query.Page,
PageSize = query.PageSize,
TotalPages = (int)Math.Ceiling((double)totalCount / query.PageSize)
};
// Record result metrics
_customMetrics.RecordQueryResults(result.Items.Count, result.TotalCount);
return result;
}
}
// Events with automatic metrics
public class UserCreated : IEvent
{
public Guid Id { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public UserRole Role { get; set; }
public DateTime CreatedAt { get; set; }
}
public class UserCreatedHandler : IEventHandler<UserCreated>
{
private readonly IEmailService _emailService;
private readonly ICustomMetrics _customMetrics;
public UserCreatedHandler(IEmailService emailService, ICustomMetrics customMetrics)
{
_emailService = emailService;
_customMetrics = customMetrics;
}
// Event handling metrics automatically tracked
public async Task HandleAsync(UserCreated @event, CancellationToken cancellationToken = default)
{
try
{
await _emailService.SendWelcomeEmailAsync(new WelcomeEmailRequest
{
RecipientEmail = @event.Email,
FirstName = @event.FirstName,
LastName = @event.LastName,
UserId = @event.Id
});
_customMetrics.IncrementWelcomeEmailsSent(@event.Role);
}
catch (Exception ex)
{
_customMetrics.IncrementWelcomeEmailFailures(@event.Role);
throw;
}
}
}
3. Custom Business Metrics
Implementing custom metrics for business KPIs:
// Custom metrics service
public interface ICustomMetrics
{
void IncrementUserRegistrations(UserRole role);
void IncrementSuccessfulUserCreations(UserRole role);
void IncrementDuplicateEmailAttempts();
void IncrementWelcomeEmailsSent(UserRole role);
void IncrementWelcomeEmailFailures(UserRole role);
void RecordUserQuery(string searchTerm, UserRole? role, int page, int pageSize);
void RecordQueryResults(int resultCount, int totalCount);
void RecordDatabaseOperationTime(string operation, double milliseconds);
void RecordCacheHitRate(bool isHit, string cacheType);
void SetActiveUserCount(int count);
void RecordBusinessTransaction(string transactionType, decimal amount);
void IncrementApiCallsPerCustomer(string customerId);
}
public class CustomMetrics : ICustomMetrics
{
private readonly IMetrics _metrics;
public CustomMetrics(IMetrics metrics)
{
_metrics = metrics;
}
public void IncrementUserRegistrations(UserRole role)
{
_metrics.Measure.Counter.Increment(
MetricRegistry.Counters.UserRegistrations,
new MetricTags("role", role.ToString().ToLower()));
}
public void IncrementSuccessfulUserCreations(UserRole role)
{
_metrics.Measure.Counter.Increment(
MetricRegistry.Counters.SuccessfulUserCreations,
new MetricTags("role", role.ToString().ToLower()));
}
public void IncrementDuplicateEmailAttempts()
{
_metrics.Measure.Counter.Increment(MetricRegistry.Counters.DuplicateEmailAttempts);
}
public void IncrementWelcomeEmailsSent(UserRole role)
{
_metrics.Measure.Counter.Increment(
MetricRegistry.Counters.WelcomeEmailsSent,
new MetricTags("role", role.ToString().ToLower()));
}
public void IncrementWelcomeEmailFailures(UserRole role)
{
_metrics.Measure.Counter.Increment(
MetricRegistry.Counters.WelcomeEmailFailures,
new MetricTags("role", role.ToString().ToLower()));
}
public void RecordUserQuery(string searchTerm, UserRole? role, int page, int pageSize)
{
var tags = new List<KeyValuePair<string, string>>
{
new("has_search", (!string.IsNullOrEmpty(searchTerm)).ToString().ToLower()),
new("page_size", pageSize.ToString())
};
if (role.HasValue)
{
tags.Add(new("role", role.ToString().ToLower()));
}
_metrics.Measure.Counter.Increment(
MetricRegistry.Counters.UserQueries,
new MetricTags(tags.ToArray()));
// Record page number distribution
_metrics.Measure.Histogram.Update(
MetricRegistry.Histograms.QueryPageNumbers,
page);
}
public void RecordQueryResults(int resultCount, int totalCount)
{
_metrics.Measure.Histogram.Update(MetricRegistry.Histograms.QueryResultCounts, resultCount);
_metrics.Measure.Histogram.Update(MetricRegistry.Histograms.QueryTotalCounts, totalCount);
}
public void RecordDatabaseOperationTime(string operation, double milliseconds)
{
_metrics.Measure.Timer.Time(
MetricRegistry.Timers.DatabaseOperations,
TimeSpan.FromMilliseconds(milliseconds),
new MetricTags("operation", operation.ToLower()));
}
public void RecordCacheHitRate(bool isHit, string cacheType)
{
_metrics.Measure.Counter.Increment(
isHit ? MetricRegistry.Counters.CacheHits : MetricRegistry.Counters.CacheMisses,
new MetricTags("type", cacheType.ToLower()));
}
public void SetActiveUserCount(int count)
{
_metrics.Measure.Gauge.SetValue(MetricRegistry.Gauges.ActiveUsers, count);
}
public void RecordBusinessTransaction(string transactionType, decimal amount)
{
_metrics.Measure.Counter.Increment(
MetricRegistry.Counters.BusinessTransactions,
new MetricTags("type", transactionType.ToLower()));
_metrics.Measure.Histogram.Update(
MetricRegistry.Histograms.TransactionAmounts,
(double)amount,
new MetricTags("type", transactionType.ToLower()));
}
public void IncrementApiCallsPerCustomer(string customerId)
{
_metrics.Measure.Counter.Increment(
MetricRegistry.Counters.ApiCallsPerCustomer,
new MetricTags("customer_id", customerId));
}
}
// Metric registry for organized metric definitions
public static class MetricRegistry
{
public static class Counters
{
public static readonly CounterOptions UserRegistrations = new CounterOptions
{
Name = "user_registrations_total",
MeasurementUnit = Unit.Calls,
Tags = new MetricTags("role", "unknown")
};
public static readonly CounterOptions SuccessfulUserCreations = new CounterOptions
{
Name = "successful_user_creations_total",
MeasurementUnit = Unit.Calls,
Tags = new MetricTags("role", "unknown")
};
public static readonly CounterOptions DuplicateEmailAttempts = new CounterOptions
{
Name = "duplicate_email_attempts_total",
MeasurementUnit = Unit.Calls
};
public static readonly CounterOptions WelcomeEmailsSent = new CounterOptions
{
Name = "welcome_emails_sent_total",
MeasurementUnit = Unit.Calls,
Tags = new MetricTags("role", "unknown")
};
public static readonly CounterOptions WelcomeEmailFailures = new CounterOptions
{
Name = "welcome_email_failures_total",
MeasurementUnit = Unit.Calls,
Tags = new MetricTags("role", "unknown")
};
public static readonly CounterOptions UserQueries = new CounterOptions
{
Name = "user_queries_total",
MeasurementUnit = Unit.Calls
};
public static readonly CounterOptions CacheHits = new CounterOptions
{
Name = "cache_hits_total",
MeasurementUnit = Unit.Calls,
Tags = new MetricTags("type", "unknown")
};
public static readonly CounterOptions CacheMisses = new CounterOptions
{
Name = "cache_misses_total",
MeasurementUnit = Unit.Calls,
Tags = new MetricTags("type", "unknown")
};
public static readonly CounterOptions BusinessTransactions = new CounterOptions
{
Name = "business_transactions_total",
MeasurementUnit = Unit.Calls,
Tags = new MetricTags("type", "unknown")
};
public static readonly CounterOptions ApiCallsPerCustomer = new CounterOptions
{
Name = "api_calls_per_customer_total",
MeasurementUnit = Unit.Calls,
Tags = new MetricTags("customer_id", "unknown")
};
}
public static class Gauges
{
public static readonly GaugeOptions ActiveUsers = new GaugeOptions
{
Name = "active_users_current",
MeasurementUnit = Unit.Items
};
public static readonly GaugeOptions DatabaseConnections = new GaugeOptions
{
Name = "database_connections_current",
MeasurementUnit = Unit.Connections
};
public static readonly GaugeOptions CacheSize = new GaugeOptions
{
Name = "cache_size_bytes",
MeasurementUnit = Unit.Bytes,
Tags = new MetricTags("type", "unknown")
};
}
public static class Histograms
{
public static readonly HistogramOptions QueryResultCounts = new HistogramOptions
{
Name = "query_result_counts",
MeasurementUnit = Unit.Items
};
public static readonly HistogramOptions QueryTotalCounts = new HistogramOptions
{
Name = "query_total_counts",
MeasurementUnit = Unit.Items
};
public static readonly HistogramOptions QueryPageNumbers = new HistogramOptions
{
Name = "query_page_numbers",
MeasurementUnit = Unit.None
};
public static readonly HistogramOptions TransactionAmounts = new HistogramOptions
{
Name = "transaction_amounts",
MeasurementUnit = Unit.None,
Tags = new MetricTags("type", "unknown")
};
}
public static class Timers
{
public static readonly TimerOptions DatabaseOperations = new TimerOptions
{
Name = "database_operation_duration",
MeasurementUnit = Unit.Requests,
DurationUnit = TimeUnit.Milliseconds,
RateUnit = TimeUnit.Seconds,
Tags = new MetricTags("operation", "unknown")
};
public static readonly TimerOptions ExternalApiCalls = new TimerOptions
{
Name = "external_api_call_duration",
MeasurementUnit = Unit.Requests,
DurationUnit = TimeUnit.Milliseconds,
RateUnit = TimeUnit.Seconds,
Tags = new MetricTags("service", "unknown")
};
}
}
4. Health Checks Integration
Combining metrics with health checks:
var builder = WebApplication.CreateBuilder(args);
// Add health checks
builder.Services.AddHealthChecks()
.AddCheck<DatabaseHealthCheck>("database")
.AddCheck<RedisHealthCheck>("redis")
.AddCheck<EmailServiceHealthCheck>("email-service")
.AddCheck<ExternalApiHealthCheck>("external-api");
builder.Services.AddConvey()
.AddMetrics(metrics =>
{
metrics.Enabled = true;
metrics.InfluxEnabled = true;
metrics.InfluxUrl = "http://influxdb:8086";
metrics.Database = "health_metrics";
metrics.Interval = 30;
});
var app = builder.Build();
// Health check endpoints
app.MapHealthChecks("/health");
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("live")
});
app.UseMetrics();
app.Run();
// Custom health checks with metrics
public class DatabaseHealthCheck : IHealthCheck
{
private readonly IDbContext _dbContext;
private readonly ICustomMetrics _customMetrics;
public DatabaseHealthCheck(IDbContext dbContext, ICustomMetrics customMetrics)
{
_dbContext = dbContext;
_customMetrics = customMetrics;
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
var stopwatch = Stopwatch.StartNew();
await _dbContext.Database.CanConnectAsync(cancellationToken);
stopwatch.Stop();
_customMetrics.RecordDatabaseOperationTime("health_check", stopwatch.ElapsedMilliseconds);
return HealthCheckResult.Healthy("Database connection successful",
new Dictionary<string, object>
{
["response_time_ms"] = stopwatch.ElapsedMilliseconds
});
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Database connection failed", ex);
}
}
}
public class RedisHealthCheck : IHealthCheck
{
private readonly IRedisService _redisService;
private readonly ICustomMetrics _customMetrics;
public RedisHealthCheck(IRedisService redisService, ICustomMetrics customMetrics)
{
_redisService = redisService;
_customMetrics = customMetrics;
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
var stopwatch = Stopwatch.StartNew();
var testKey = $"health_check_{Guid.NewGuid()}";
await _redisService.SetAsync(testKey, "test", TimeSpan.FromSeconds(5));
var value = await _redisService.GetAsync<string>(testKey);
await _redisService.DeleteAsync(testKey);
stopwatch.Stop();
if (value != "test")
{
return HealthCheckResult.Degraded("Redis read/write test failed");
}
_customMetrics.RecordCacheHitRate(true, "redis");
return HealthCheckResult.Healthy("Redis connection successful",
new Dictionary<string, object>
{
["response_time_ms"] = stopwatch.ElapsedMilliseconds
});
}
catch (Exception ex)
{
_customMetrics.RecordCacheHitRate(false, "redis");
return HealthCheckResult.Unhealthy("Redis connection failed", ex);
}
}
}
5. Performance Monitoring
Advanced performance monitoring and alerting:
// Performance monitoring middleware
public class PerformanceMonitoringMiddleware
{
private readonly RequestDelegate _next;
private readonly ICustomMetrics _customMetrics;
private readonly ILogger<PerformanceMonitoringMiddleware> _logger;
public PerformanceMonitoringMiddleware(
RequestDelegate next,
ICustomMetrics customMetrics,
ILogger<PerformanceMonitoringMiddleware> logger)
{
_next = next;
_customMetrics = customMetrics;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();
var originalBodyStream = context.Response.Body;
try
{
using var responseBody = new MemoryStream();
context.Response.Body = responseBody;
await _next(context);
stopwatch.Stop();
// Record metrics
var endpoint = context.Request.Path.Value ?? "unknown";
var method = context.Request.Method;
var statusCode = context.Response.StatusCode;
var responseTime = stopwatch.ElapsedMilliseconds;
// Record response time
_customMetrics.RecordHttpRequestDuration(method, endpoint, statusCode, responseTime);
// Record response size
var responseSize = responseBody.Length;
_customMetrics.RecordHttpResponseSize(method, endpoint, responseSize);
// Copy response back
await responseBody.CopyToAsync(originalBodyStream);
// Log slow requests
if (responseTime > 1000) // Slow request threshold
{
_logger.LogWarning("Slow request detected: {Method} {Path} took {Duration}ms",
method, endpoint, responseTime);
}
}
catch (Exception ex)
{
stopwatch.Stop();
_customMetrics.RecordHttpRequestError(context.Request.Method, context.Request.Path.Value ?? "unknown", ex.GetType().Name);
throw;
}
finally
{
context.Response.Body = originalBodyStream;
}
}
}
// Service for background metrics collection
public class MetricsCollectionService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<MetricsCollectionService> _logger;
public MetricsCollectionService(IServiceProvider serviceProvider, ILogger<MetricsCollectionService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
using var scope = _serviceProvider.CreateScope();
var customMetrics = scope.ServiceProvider.GetRequiredService<ICustomMetrics>();
var userRepository = scope.ServiceProvider.GetRequiredService<IUserRepository>();
// Collect system metrics
await CollectSystemMetrics(customMetrics);
// Collect business metrics
await CollectBusinessMetrics(customMetrics, userRepository);
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error collecting metrics");
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
}
private async Task CollectSystemMetrics(ICustomMetrics customMetrics)
{
// System metrics
var process = Process.GetCurrentProcess();
customMetrics.SetProcessMemoryUsage(process.WorkingSet64);
customMetrics.SetProcessCpuUsage(process.TotalProcessorTime.TotalMilliseconds);
// GC metrics
var gen0Collections = GC.CollectionCount(0);
var gen1Collections = GC.CollectionCount(1);
var gen2Collections = GC.CollectionCount(2);
var totalMemory = GC.GetTotalMemory(false);
customMetrics.SetGcCollections(0, gen0Collections);
customMetrics.SetGcCollections(1, gen1Collections);
customMetrics.SetGcCollections(2, gen2Collections);
customMetrics.SetGcMemoryUsage(totalMemory);
}
private async Task CollectBusinessMetrics(ICustomMetrics customMetrics, IUserRepository userRepository)
{
// Active user count
var activeUserCount = await userRepository.GetActiveUserCountAsync();
customMetrics.SetActiveUserCount(activeUserCount);
// User registrations today
var today = DateTime.UtcNow.Date;
var todayRegistrations = await userRepository.GetUserCountByDateAsync(today);
customMetrics.SetDailyRegistrations(todayRegistrations);
// User distribution by role
var usersByRole = await userRepository.GetUserCountByRoleAsync();
foreach (var roleCount in usersByRole)
{
customMetrics.SetUserCountByRole(roleCount.Key, roleCount.Value);
}
}
}
// Extended custom metrics interface
public interface IExtendedCustomMetrics : ICustomMetrics
{
void RecordHttpRequestDuration(string method, string endpoint, int statusCode, double milliseconds);
void RecordHttpResponseSize(string method, string endpoint, long bytes);
void RecordHttpRequestError(string method, string endpoint, string errorType);
void SetProcessMemoryUsage(long bytes);
void SetProcessCpuUsage(double milliseconds);
void SetGcCollections(int generation, int count);
void SetGcMemoryUsage(long bytes);
void SetDailyRegistrations(int count);
void SetUserCountByRole(UserRole role, int count);
}
Configuration Options
Metrics Options
public class MetricsOptions
{
public bool Enabled { get; set; } = true;
public bool InfluxEnabled { get; set; } = false;
public bool PrometheusEnabled { get; set; } = false;
public bool PrometheusFormatter { get; set; } = false;
public string InfluxUrl { get; set; }
public string Database { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string RetentionPolicy { get; set; }
public string Env { get; set; }
public int Interval { get; set; } = 5;
public Dictionary<string, string> Tags { get; set; } = new();
}
API Reference
Extension Methods
public static class ConveyExtensions
{
public static IConveyBuilder AddMetrics(this IConveyBuilder builder);
public static IConveyBuilder AddMetrics(this IConveyBuilder builder, Action<MetricsOptions> configure);
}
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseMetrics(this IApplicationBuilder app);
}
Best Practices
- Use meaningful metric names - Follow naming conventions for clarity
- Add appropriate tags - Use tags for filtering and grouping
- Monitor performance impact - Ensure metrics collection doesn’t degrade performance
- Set up alerting - Configure alerts for critical metrics
- Use appropriate metric types - Choose counters, gauges, histograms, or timers appropriately
- Implement data retention - Configure appropriate data retention policies
- Monitor metric cardinality - Avoid high-cardinality metrics that can overwhelm systems
- Document metrics - Maintain documentation for custom metrics and their purposes
Troubleshooting
Common Issues
- High cardinality metrics
- Review tag usage and avoid unbounded tags
- Implement sampling for high-volume metrics
- Use metric aggregation strategies
- Performance impact
- Monitor metrics collection overhead
- Optimize metric calculation and reporting
- Consider asynchronous metric collection
- Storage issues
- Configure appropriate retention policies
- Monitor storage usage and capacity
- Implement metric archival strategies
- Missing metrics
- Verify metric registration and configuration
- Check metric collection intervals
- Validate metric output endpoints