Skip to main content

.NET Cost Tag Implementation

Understanding your cloud infrastructure costs is crucial for making informed business decisions. With Beakpoint Insights, you can implement cost tags in your .NET applications to gain granular visibility into your AWS spending. This guide walks you through implementing both cost calculation and attribution tags to transform your telemetry data into actionable cost insights.

Cost tags fall into two categories: cost calculation tags provide the technical metadata needed to compute actual infrastructure costs, while cost attribution tags give business context about why those costs were incurred. Together, they enable you to answer questions like "How much does our payment processing service cost?" or "Which customers are driving our highest compute expenses?"

Prerequisites

Before implementing cost tags in your .NET application, ensure you have:

  • A Beakpoint Insights account and API key
  • OpenTelemetry .NET packages installed in your project
  • Access to your AWS infrastructure metadata (for EC2, Lambda, or RDS resources)
  • Understanding of your application's business logic for attribution purposes

Your project will need these key packages:

<PackageReference Include="OpenTelemetry" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.11.1" />

EC2 Tag Implementation

For applications running on EC2 instances, you need to capture detailed instance metadata to calculate costs accurately. The AmazonEc2Demo repository provides a complete working implementation.

Here's how to implement EC2 cost calculation tags:

using Amazon.EC2;
using Amazon.Util;
using OpenTelemetry.Trace;

public static async Task<Dictionary<string, object>> GetEC2Attributes(
IConfiguration configuration,
IMemoryCache memoryCache)
{
// Get AWS credentials and region
var regionName = EC2InstanceMetadata.AvailabilityZone[..^1];
var credentials = new SessionAWSCredentials(
configuration["AWS:Credentials:AccessKeyId"],
configuration["AWS:Credentials:SecretAccessKey"],
configuration["AWS:Credentials:SessionToken"]
);

// Initialize EC2 client
var client = new AmazonEC2Client(credentials, new AmazonEC2Config
{
RegionEndpoint = RegionEndpoint.GetBySystemName(regionName)
});

// Get instance details (with caching for performance)
if (!memoryCache.TryGetValue($"{EC2InstanceMetadata.InstanceId}_instance",
out Instance? instance) || instance is null)
{
instance = await DescribeInstancesApiCall(client, EC2InstanceMetadata.InstanceId);
memoryCache.Set($"{EC2InstanceMetadata.InstanceId}_instance",
instance, new MemoryCacheEntryOptions());
}

// Return required cost calculation tags
return new Dictionary<string, object>
{
["aws.ec2.instance_id"] = instance.InstanceId,
["aws.ec2.instance_type"] = instance.InstanceType.Value,
["aws.region"] = regionName,
["aws.ec2.platform_details"] = await GetPlatformDetails(client, instance),
["aws.ec2.license_model"] = GetLicenseModel(instance),
["aws.ec2.tenancy"] = instance.Placement.Tenancy.Value
};
}

For EC2 instances, you'll also want to add cost attribution tags to understand business context:

public void AddEC2AttributionTags(Activity activity, User user, string serviceName)
{
activity?.SetTag("service.name", serviceName)
.SetTag("deployment.environment.name", "production")
.SetTag("app.user.org.id", user.OrganizationId)
.SetTag("cloud.provider", "aws");
}

Lambda Tag Implementation

AWS Lambda functions have different cost drivers than EC2 instances. According to AWS Lambda pricing, costs depend on memory allocation, architecture, and execution time.

Here's how to implement Lambda cost calculation tags:

using Amazon.Lambda.Core;
using OpenTelemetry.Trace;

public class LambdaActivityEnricher : BaseProcessor<Activity>
{
private readonly ILambdaContext _context;

public LambdaActivityEnricher(ILambdaContext context)
{
_context = context;
}

public override void OnStart(Activity activity)
{
// Required cost calculation tags for Lambda
activity.SetTag("aws.lambda.memory_size", _context.MemoryLimitInMB)
.SetTag("aws.lambda.arn", _context.InvokedFunctionArn)
.SetTag("aws.lambda.architecture", GetArchitecture())
.SetTag("aws.region", Environment.GetEnvironmentVariable("AWS_REGION"));

// Additional helpful attributes
activity.SetTag("aws.lambda.function_name", _context.FunctionName)
.SetTag("aws.lambda.request_id", _context.AwsRequestId);
}

private static string GetArchitecture()
{
return Environment.GetEnvironmentVariable("AWS_EXECUTION_ENV")?.Contains("arm64") == true
? "arm64"
: "x86_64";
}
}

Add the enricher to your OpenTelemetry configuration:

public TracerProvider ConfigureLambdaTracing(ILambdaContext context)
{
return Sdk.CreateTracerProviderBuilder()
.AddSource("YourLambdaFunction")
.AddProcessor(new LambdaActivityEnricher(context))
.AddOtlpExporter(options => {
options.Endpoint = new Uri("https://otel.beakpointinsights.com/api/traces");
options.Headers = "x-bkpt-key=your_api_key_here";
options.Protocol = OtlpExportProtocol.HttpProtobuf;
})
.Build();
}

RDS Tag Implementation

For applications that query RDS databases, you need to capture database instance metadata. Based on the Beakpoint Insights RDS documentation, here are the required tags:

using Amazon.RDS;
using Amazon.RDS.Model;

public async Task<Dictionary<string, object>> GetRDSAttributes(
string dbInstanceIdentifier,
string region)
{
var client = new AmazonRDSClient(RegionEndpoint.GetBySystemName(region));

var response = await client.DescribeDBInstancesAsync(new DescribeDBInstancesRequest
{
DBInstanceIdentifier = dbInstanceIdentifier
});

var instance = response.DBInstances.First();

return new Dictionary<string, object>
{
["aws.rds.instance.id"] = instance.DBInstanceIdentifier,
["aws.rds.instance.class"] = instance.DBInstanceClass,
["aws.region"] = region,
["aws.rds.deployment.option"] = instance.MultiAZ ? "Multi-AZ" : "Single-AZ",
["aws.rds.engine"] = instance.Engine,
["aws.rds.engine_version"] = instance.EngineVersion,
["aws.rds.storage.type"] = instance.StorageType,
["aws.rds.license.model"] = instance.LicenseModel ?? "No License required"
};
}

Apply RDS tags to database operations:

public async Task<List<Order>> GetUserOrders(int userId)
{
using var activity = ActivitySource.StartActivity("get-user-orders");

// Add RDS cost calculation tags
var rdsAttributes = await GetRDSAttributes("mydbinstance", "us-east-1");
foreach (var attr in rdsAttributes)
{
activity?.SetTag(attr.Key, attr.Value);
}

// Add cost attribution tags
activity?.SetTag("service.name", "order-management")
.SetTag("code.function.name", "GetUserOrders")
.SetTag("app.user.id", userId.ToString());

// Your database query logic here
return await _dbContext.Orders.Where(o => o.UserId == userId).ToListAsync();
}

Attribution Tag Examples

Cost attribution tags provide business context that transforms technical metrics into actionable insights. Here are practical examples for different scenarios:

Multi-Tenant SaaS Application

public void AddSaaSAttributionTags(Activity activity, string tenantId, string feature)
{
activity?.SetTag("service.name", "saas-platform")
.SetTag("service.namespace", "customer-portal")
.SetTag("app.user.org.id", tenantId)
.SetTag("code.function.name", feature)
.SetTag("deployment.environment.name", "production");
}

E-commerce Payment Processing

public void AddPaymentAttributionTags(Activity activity, PaymentRequest request)
{
activity?.SetTag("service.name", "payment-processing")
.SetTag("service.version", "v2.1.0")
.SetTag("app.user.id", request.UserId)
.SetTag("app.user.org.id", request.MerchantId)
.SetTag("code.function.name", "ProcessPayment")
.SetTag("cloud.region", "us-east-1");
}

API Gateway with Regional Distribution

public void AddAPIAttributionTags(Activity activity, HttpContext context)
{
var userAgent = context.Request.Headers["User-Agent"].ToString();
var clientType = userAgent.Contains("Mobile") ? "mobile" : "web";

activity?.SetTag("service.name", "api-gateway")
.SetTag("service.namespace", "public-api")
.SetTag("code.function.name", context.Request.Path)
.SetTag("client.type", clientType)
.SetTag("deployment.environment.name", "production");
}

Batch Processing Jobs

public void AddBatchJobAttributionTags(Activity activity, BatchJob job)
{
activity?.SetTag("service.name", "data-processing")
.SetTag("service.namespace", "analytics")
.SetTag("code.function.name", job.JobType)
.SetTag("batch.job.id", job.Id)
.SetTag("deployment.environment.name", "production");
}

Validation and Testing

Proper validation ensures your cost tags are working correctly and providing accurate insights.

Tag Completeness Validation

Create a validation helper to ensure all required tags are present:

public class CostTagValidator
{
private static readonly Dictionary<string, string[]> RequiredTagsByService = new()
{
["aws.lambda"] = new[] { "aws.lambda.memory_size", "aws.lambda.architecture", "aws.region" },
["aws.ec2"] = new[] { "aws.ec2.instance_type", "aws.ec2.instance_id", "aws.region" },
["aws.rds"] = new[] { "aws.rds.instance.id", "aws.rds.instance.class", "aws.region" }
};

public static bool ValidateActivity(Activity activity, string serviceType)
{
if (!RequiredTagsByService.TryGetValue(serviceType, out var requiredTags))
return false;

return requiredTags.All(tag => activity.Tags.Any(t => t.Key == tag));
}
}

Unit Testing Cost Tags

Test your tagging logic with unit tests:

[Test]
public void Should_Add_Required_Lambda_Tags()
{
// Arrange
var mockContext = new Mock<ILambdaContext>();
mockContext.Setup(c => c.MemoryLimitInMB).Returns(512);
mockContext.Setup(c => c.InvokedFunctionArn).Returns("arn:aws:lambda:us-east-1:123456789012:function:test");

using var activity = new Activity("test-activity").Start();
var enricher = new LambdaActivityEnricher(mockContext.Object);

// Act
enricher.OnStart(activity);

// Assert
Assert.That(CostTagValidator.ValidateActivity(activity, "aws.lambda"), Is.True);
Assert.That(activity.Tags.First(t => t.Key == "aws.lambda.memory_size").Value, Is.EqualTo("512"));
}

Integration Testing

Verify your tags appear correctly in Beakpoint Insights:

[Test]
public async Task Should_Send_Tagged_Traces_To_Beakpoint()
{
// Configure test tracer with actual endpoint
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource("IntegrationTest")
.AddOtlpExporter(options => {
options.Endpoint = new Uri("https://otel.beakpointinsights.com/api/traces");
options.Headers = "x-bkpt-key=your_test_api_key";
})
.Build();

using var activity = TestActivitySource.StartActivity("integration-test");

// Add your tags
activity?.SetTag("service.name", "integration-test")
.SetTag("test.run.id", Guid.NewGuid().ToString());

// Verify in Beakpoint Insights dashboard that traces appear with correct tags
}

Monitoring Tag Quality

Set up alerts for missing or invalid tags:

public class TagQualityMonitor
{
private readonly ILogger<TagQualityMonitor> _logger;

public TagQualityMonitor(ILogger<TagQualityMonitor> logger)
{
_logger = logger;
}

public void ValidateActivityTags(Activity activity)
{
var missingTags = new List<string>();

if (!activity.Tags.Any(t => t.Key == "service.name"))
missingTags.Add("service.name");

if (!activity.Tags.Any(t => t.Key.StartsWith("aws.")))
missingTags.Add("aws cost calculation tags");

if (missingTags.Any())
{
_logger.LogWarning("Activity {ActivityName} missing required tags: {MissingTags}",
activity.DisplayName, string.Join(", ", missingTags));
}
}
}

Implementing comprehensive cost tags in your .NET applications transforms abstract telemetry into concrete business insights. Start with the core calculation tags for your infrastructure type, add meaningful attribution tags for your business context, and validate everything works correctly. With proper implementation, you'll gain unprecedented visibility into your AWS costs and can make data-driven decisions about infrastructure optimization.

The examples in this guide, particularly the working AmazonEc2Demo repository, provide proven patterns you can adapt for your specific use cases. Remember to follow OpenTelemetry's semantic conventions where applicable and maintain consistency in your tagging strategy across all services.


SEO Meta Description: