| title | category | order | keywords | |||||
|---|---|---|---|---|---|---|---|---|
Conditional Compilation |
core-features |
73 |
|
Documentation > Core Features > Conditional Compilation
Completely eliminate logging overhead in production builds using conditional compilation. When disabled, all logging code is removed at compile time with zero runtime cost.
Oproto.FluentDynamoDb supports the DISABLE_DYNAMODB_LOGGING compilation symbol to remove all logging code from generated methods. This provides:
- Zero runtime overhead - No logging calls in compiled code
- Zero allocations - No parameter boxing or string formatting
- Smaller binaries - Reduced IL code size
- Same functionality - Application behavior unchanged
Add to your .csproj file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<!-- Disable logging in Release builds -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DefineConstants>$(DefineConstants);DISABLE_DYNAMODB_LOGGING</DefineConstants>
</PropertyGroup>
</Project><PropertyGroup>
<DefineConstants>$(DefineConstants);DISABLE_DYNAMODB_LOGGING</DefineConstants>
</PropertyGroup><!-- Disable for Release and Production -->
<PropertyGroup Condition="'$(Configuration)' == 'Release' OR '$(Configuration)' == 'Production'">
<DefineConstants>$(DefineConstants);DISABLE_DYNAMODB_LOGGING</DefineConstants>
</PropertyGroup>
<!-- Enable for Debug and Staging -->
<PropertyGroup Condition="'$(Configuration)' == 'Debug' OR '$(Configuration)' == 'Staging'">
<!-- DISABLE_DYNAMODB_LOGGING not defined -->
</PropertyGroup>public static Dictionary<string, AttributeValue> ToDynamoDb<TSelf>(
TSelf entity,
IDynamoDbLogger? logger = null)
where TSelf : IDynamoDbEntity
{
#if !DISABLE_DYNAMODB_LOGGING
logger?.LogTrace(LogEventIds.MappingToDynamoDbStart,
"Starting ToDynamoDb mapping for {EntityType}",
typeof(TSelf).Name);
#endif
var typedEntity = (Product)(object)entity;
var item = new Dictionary<string, AttributeValue>();
#if !DISABLE_DYNAMODB_LOGGING
if (logger?.IsEnabled(LogLevel.Debug) == true)
{
logger.LogDebug(LogEventIds.MappingPropertyStart,
"Mapping property {PropertyName} of type {PropertyType}",
"Id", "String");
}
#endif
item["pk"] = new AttributeValue { S = typedEntity.Id };
#if !DISABLE_DYNAMODB_LOGGING
logger?.LogTrace(LogEventIds.MappingToDynamoDbComplete,
"Completed ToDynamoDb mapping for {EntityType} with {AttributeCount} attributes",
typeof(TSelf).Name, item.Count);
#endif
return item;
}When DISABLE_DYNAMODB_LOGGING is defined, the compiler removes all logging code:
public static Dictionary<string, AttributeValue> ToDynamoDb<TSelf>(
TSelf entity,
IDynamoDbLogger? logger = null)
where TSelf : IDynamoDbEntity
{
var typedEntity = (Product)(object)entity;
var item = new Dictionary<string, AttributeValue>();
item["pk"] = new AttributeValue { S = typedEntity.Id };
return item;
}The logger parameter remains for API compatibility, but is never used.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<!-- Development: Full logging -->
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<!-- DISABLE_DYNAMODB_LOGGING not defined -->
</PropertyGroup>
<!-- Staging: Full logging for troubleshooting -->
<PropertyGroup Condition="'$(Configuration)' == 'Staging'">
<!-- DISABLE_DYNAMODB_LOGGING not defined -->
</PropertyGroup>
<!-- Production: No logging overhead -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DefineConstants>$(DefineConstants);DISABLE_DYNAMODB_LOGGING</DefineConstants>
</PropertyGroup>
</Project><Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
<AWSProjectType>Lambda</AWSProjectType>
</PropertyGroup>
<!-- Lambda: Disable logging for minimal cold start -->
<PropertyGroup>
<DefineConstants>$(DefineConstants);DISABLE_DYNAMODB_LOGGING</DefineConstants>
</PropertyGroup>
</Project><Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<!-- Debug: Enable logging -->
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<!-- DISABLE_DYNAMODB_LOGGING not defined -->
</PropertyGroup>
<!-- Release: Disable logging -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DefineConstants>$(DefineConstants);DISABLE_DYNAMODB_LOGGING</DefineConstants>
</PropertyGroup>
</Project><Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
<!-- Disable logging for all targets in Release -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DefineConstants>$(DefineConstants);DISABLE_DYNAMODB_LOGGING</DefineConstants>
</PropertyGroup>
</Project>Mapping a typical entity (8 properties) 100,000 times:
| Configuration | Time (ms) | Allocations (MB) | Overhead |
|---|---|---|---|
| Logging Disabled | 245 | 152 | 0% (baseline) |
| NoOpLogger | 248 | 152 | +1.2% |
| Logger (Disabled) | 251 | 153 | +2.4% |
| Logger (Enabled) | 892 | 487 | +264% |
Key Findings:
- Conditional compilation: 0% overhead
- NoOpLogger: ~1% overhead (negligible)
- Logger with IsEnabled=false: ~2% overhead (minimal)
- Logger with logging enabled: ~264% overhead (expected)
Example Lambda function:
| Configuration | Binary Size | Reduction |
|---|---|---|
| Logging Enabled | 8.2 MB | - |
| Logging Disabled | 7.8 MB | -5% |
| Configuration | Cold Start (ms) | Improvement |
|---|---|---|
| Logging Enabled | 1,250 | - |
| Logging Disabled | 1,180 | -5.6% |
Use a decompiler (like ILSpy or dnSpy) to inspect generated code:
# Build in Release mode
dotnet build -c Release
# Inspect with ILSpy
ilspy YourAssembly.dllLook for the ToDynamoDb and FromDynamoDb methods - they should have no logging calls.
// This code works the same with or without logging
var product = new Product { Id = "test", Name = "Test Product" };
var item = Product.ToDynamoDb(product, logger);
// Logger parameter is ignored when DISABLE_DYNAMODB_LOGGING is defined
// No NullReferenceException even if logger is nullAdd a custom target to verify the symbol is defined:
<Target Name="VerifyLoggingDisabled" BeforeTargets="Build" Condition="'$(Configuration)' == 'Release'">
<Error Condition="!$(DefineConstants.Contains('DISABLE_DYNAMODB_LOGGING'))"
Text="DISABLE_DYNAMODB_LOGGING must be defined for Release builds" />
</Target>Always disable logging in production builds for optimal performance:
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DefineConstants>$(DefineConstants);DISABLE_DYNAMODB_LOGGING</DefineConstants>
</PropertyGroup>Keep logging enabled during development for debugging:
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<!-- DISABLE_DYNAMODB_LOGGING not defined -->
</PropertyGroup>Decide based on your needs:
Enable logging in staging:
- Troubleshoot production-like issues
- Validate behavior before production
- Monitor performance characteristics
Disable logging in staging:
- Match production configuration exactly
- Performance testing with production settings
- Minimize differences between staging and production
Add comments to your .csproj:
<!--
Logging Configuration:
- Debug: Logging enabled for development
- Staging: Logging enabled for troubleshooting
- Release: Logging disabled for optimal performance
-->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DefineConstants>$(DefineConstants);DISABLE_DYNAMODB_LOGGING</DefineConstants>
</PropertyGroup>Ensure your application works with and without logging:
# Test with logging enabled
dotnet test -c Debug
# Test with logging disabled
dotnet test -c ReleaseValidate the symbol is defined in production builds:
# GitHub Actions example
- name: Verify logging disabled in Release
run: |
if ! grep -q "DISABLE_DYNAMODB_LOGGING" YourProject.csproj; then
echo "Error: DISABLE_DYNAMODB_LOGGING not found in Release configuration"
exit 1
fiCheck 1: Verify the symbol is defined
dotnet build -c Release -v detailed | grep DISABLE_DYNAMODB_LOGGINGCheck 2: Verify the configuration condition
<!-- Correct -->
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DefineConstants>$(DefineConstants);DISABLE_DYNAMODB_LOGGING</DefineConstants>
</PropertyGroup>
<!-- Incorrect - case sensitive -->
<PropertyGroup Condition="'$(Configuration)' == 'release'">
<DefineConstants>$(DefineConstants);DISABLE_DYNAMODB_LOGGING</DefineConstants>
</PropertyGroup>Check 3: Clean and rebuild
dotnet clean
dotnet build -c ReleaseThe application should behave identically with or without logging. If not:
Check 1: Ensure you're not relying on logging side effects
// Bad - relying on logging side effect
logger?.LogInformation(eventId, "Processing {Count} items", items.Count);
ProcessItems(items); // Assumes items.Count was evaluated
// Good - explicit evaluation
var count = items.Count;
logger?.LogInformation(eventId, "Processing {Count} items", count);
ProcessItems(items);Check 2: Verify logger parameter is optional
// Good - logger is optional
Product.ToDynamoDb(entity);
Product.ToDynamoDb(entity, logger);
// Bad - logger is required
Product.ToDynamoDb(entity, logger); // Fails if logger is nullCheck 1: Verify logging is actually disabled
Use a profiler or decompiler to confirm logging code is removed.
Check 2: Measure correctly
// Warm up
for (int i = 0; i < 1000; i++)
Product.ToDynamoDb(entity);
// Measure
var sw = Stopwatch.StartNew();
for (int i = 0; i < 100000; i++)
Product.ToDynamoDb(entity);
sw.Stop();Check 3: Profile other bottlenecks
Logging may not be the primary bottleneck. Use a profiler to identify actual performance issues.
If you want logging in development but minimal overhead in production:
// Production - use NoOpLogger (default when no logger configured)
var table = new ProductsTable(client, "products");
// Or explicitly use NoOpLogger
var options = new FluentDynamoDbOptions()
.WithLogger(NoOpLogger.Instance);
var table = new ProductsTable(client, "products", options);
// ~1% overhead, no conditional compilation needed// Production
var table = new ProductsTable(client, "products", null);
// ~2% overhead, no conditional compilation needed// Production
builder.Services.AddLogging(logging =>
{
logging.SetMinimumLevel(LogLevel.Warning);
});
// Logging code runs but IsEnabled checks prevent most work
// ~5-10% overheadUse conditional compilation when:
- Every microsecond matters (high-throughput systems)
- Minimizing Lambda cold starts
- Reducing binary size for edge deployments
- Absolute zero overhead required
Use NoOpLogger when:
- Moderate performance requirements
- Want flexibility to enable logging without recompilation
- Simpler configuration
- ~1% overhead is acceptable
- Logging Configuration - Configure loggers
- Performance Optimization - Other optimization techniques
- Log Levels and Event IDs - Understand logging levels
- Troubleshooting Guide - Common issues
See Also: