The cloops.microservices SDK uses Serilog for structured logging, integrated with Microsoft.Extensions.Logging. This provides a powerful, flexible logging system that works seamlessly with log collectors in production environments.
- Getting a Logger Through Dependency Injection
- Using the Logger
- Customizing Log Levels
- Production Logging Setup
The SDK automatically configures Serilog and integrates it with Microsoft.Extensions.Logging. To use logging in your services, inject ILogger<T> through constructor dependency injection:
using Microsoft.Extensions.Logging;
namespace YourApp.Services;
public class MyService
{
private readonly ILogger<MyService> _logger;
public MyService(ILogger<MyService> logger)
{
_logger = logger;
}
}Important: Always use the generic ILogger<T> where T is your service class. This provides better log filtering and categorization.
The ILogger<T> interface provides several methods for logging at different levels:
LogTrace- Very detailed logs, typically only useful during developmentLogDebug- Detailed information for debuggingLogInformation- General informational messages about application flowLogWarning- Warning messages for unexpected but recoverable situationsLogError- Error messages for failures that don't stop the applicationLogCritical- Critical failures that may cause the application to abort
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
}
public async Task ProcessOrder(Order order)
{
_logger.LogInformation("Processing order {OrderId} for customer {CustomerId}",
order.Id, order.CustomerId);
try
{
// Process order logic
_logger.LogDebug("Order {OrderId} validated successfully", order.Id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to process order {OrderId}", order.Id);
throw;
}
}
}Serilog supports structured logging using placeholders. Instead of string interpolation, use placeholders:
// ✅ Good - Structured logging
_logger.LogInformation("User {UserId} logged in from {IPAddress}", userId, ipAddress);
// ❌ Avoid - String interpolation loses structure
_logger.LogInformation($"User {userId} logged in from {ipAddress}");Structured logging allows log aggregators to parse and query logs by specific fields, making debugging and monitoring much more powerful.
Log levels can be customized through environment variables via AppSettings.
By default, the SDK configures:
- Minimum Level:
Information - Framework Overrides:
System.Net.Http,Microsoft, andSystemnamespaces are set toWarningto reduce noise
To enable debug-level logging, set the DEBUG environment variable to true:
export DEBUG=trueThis will:
- Set the minimum log level to
Debug - Allow
LogDebugandLogTracemessages to be emitted
The logging configuration is automatically adjusted based on the environment:
- Production: Logs are output in compact JSON format (structured logging) for easy parsing by log collectors
- Non-Production: Logs are output in a human-readable, colorized format for easier development
The environment is determined by the DOTNET_ENVIRONMENT environment variable and defaults to Production. Set it to Development, or any non-Production value, to render logs in a human-friendly console format.
In production environments (especially Kubernetes), logs are typically collected by log aggregation systems. The SDK is configured to work seamlessly with these systems.
-
Structured JSON Output: In production, the SDK outputs logs in compact JSON format using Serilog's
CompactJsonFormatter. This format is easily parseable by log collectors. -
Log Collection: Log collectors (like Alloy, Fluentd, or Promtail) read logs from stdout/stderr of your containers.
-
Log Aggregation: Collected logs are forwarded to centralized log aggregation platforms for storage, search, and analysis.
The SDK's structured JSON logging works with any log collector that can parse JSON. Here are some popular options:
- Documentation: https://grafana.com/docs/alloy/latest/
- Lightweight, vendor-neutral log collector
- Part of the Grafana observability stack
- Supports OTLP and various log backends
- Documentation: https://docs.fluentd.org/
- Popular open-source log collector
- Large ecosystem of plugins
- Works with many log backends (Elasticsearch, Splunk, etc.)
- Documentation: https://vector.dev/docs/
- High-performance observability data pipeline
- Supports many sources and sinks
- Good performance characteristics
Once logs are collected, they're typically sent to aggregation platforms:
- Grafana Loki: https://grafana.com/docs/loki/latest/ - Log aggregation system inspired by Prometheus
- Elasticsearch + Kibana: https://www.elastic.co/elasticsearch/ - Popular ELK stack
- Splunk: https://www.splunk.com/ - Enterprise log management
- Datadog: https://www.datadoghq.com/ - Cloud monitoring and logging
- New Relic: https://newrelic.com/ - Application performance monitoring with logging
In a typical Kubernetes setup with Alloy:
- Application logs are written to stdout/stderr in JSON format
- Alloy DaemonSet collects logs from all pods
- Alloy forwards logs to your chosen backend (Loki, Elasticsearch, etc.)
- Dashboards in Grafana/Kibana visualize and query the logs
The SDK requires no additional configuration - it automatically outputs structured JSON in production environments, making it compatible with any log collector.
The SDK automatically enriches all logs with:
- Application name (from
AssemblyName) - Thread ID and Thread Name
- Log context (from
LogContext) - Timestamp and Log Level
These enrichments help with filtering and correlation in log aggregation systems.