Skip to content

Latest commit

 

History

History
505 lines (372 loc) · 13.3 KB

File metadata and controls

505 lines (372 loc) · 13.3 KB
title category order keywords
Conditional Compilation
core-features
73
logging
conditional compilation
performance
production
DISABLE_DYNAMODB_LOGGING

Documentation > Core Features > Conditional Compilation

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.

Overview

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

Quick Start

Disable Logging for Release Builds

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>

Disable Logging for All Builds

<PropertyGroup>
  <DefineConstants>$(DefineConstants);DISABLE_DYNAMODB_LOGGING</DefineConstants>
</PropertyGroup>

Disable Logging for Specific Configurations

<!-- 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>

How It Works

Generated Code Without Logging Disabled

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;
}

Generated Code With Logging Disabled

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.

Configuration Examples

ASP.NET Core Application

<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>

AWS Lambda Function

<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>

Console Application

<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>

Multi-Target 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>

Performance Impact

Benchmark Results

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)

Binary Size Impact

Example Lambda function:

Configuration Binary Size Reduction
Logging Enabled 8.2 MB -
Logging Disabled 7.8 MB -5%

Cold Start Impact (Lambda)

Configuration Cold Start (ms) Improvement
Logging Enabled 1,250 -
Logging Disabled 1,180 -5.6%

Verification

Verify Logging is Disabled

Use a decompiler (like ILSpy or dnSpy) to inspect generated code:

# Build in Release mode
dotnet build -c Release

# Inspect with ILSpy
ilspy YourAssembly.dll

Look for the ToDynamoDb and FromDynamoDb methods - they should have no logging calls.

Verify at Runtime

// 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 null

Build-Time Verification

Add 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>

Best Practices

1. Disable in Production

Always disable logging in production builds for optimal performance:

<PropertyGroup Condition="'$(Configuration)' == 'Release'">
  <DefineConstants>$(DefineConstants);DISABLE_DYNAMODB_LOGGING</DefineConstants>
</PropertyGroup>

2. Enable in Development

Keep logging enabled during development for debugging:

<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
  <!-- DISABLE_DYNAMODB_LOGGING not defined -->
</PropertyGroup>

3. Consider Staging Environments

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

4. Document Your Configuration

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>

5. Test Both Configurations

Ensure your application works with and without logging:

# Test with logging enabled
dotnet test -c Debug

# Test with logging disabled
dotnet test -c Release

6. Use CI/CD Validation

Validate 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
    fi

Troubleshooting

Logging Still Appears in Release Build

Check 1: Verify the symbol is defined

dotnet build -c Release -v detailed | grep DISABLE_DYNAMODB_LOGGING

Check 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 Release

Application Behaves Differently

The 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 null

Performance Not Improved

Check 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.

Alternatives to Conditional Compilation

NoOpLogger (Recommended for Most Cases)

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

Null Logger

// Production
var table = new ProductsTable(client, "products", null);

// ~2% overhead, no conditional compilation needed

High Minimum Log Level

// Production
builder.Services.AddLogging(logging =>
{
    logging.SetMinimumLevel(LogLevel.Warning);
});

// Logging code runs but IsEnabled checks prevent most work
// ~5-10% overhead

When to Use Conditional Compilation

Use 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

Next Steps


See Also: