Skip to content

Observability

VaultSandbox Client includes built-in OpenTelemetry support for distributed tracing and metrics collection. This enables deep observability into your email testing operations, making it easier to debug issues, monitor performance, and integrate with your existing observability stack.

The SDK exposes telemetry through the VaultSandboxTelemetry static class:

  • ActivitySource: For distributed tracing (spans)
  • Meter: For metrics (counters and histograms)
Terminal window
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using VaultSandbox.Client;
// Configure tracing
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(VaultSandboxTelemetry.ActivitySource.Name)
.AddConsoleExporter()
.Build();
// Configure metrics
using var meterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter(VaultSandboxTelemetry.Meter.Name)
.AddConsoleExporter()
.Build();
// Use the client as normal
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl(Environment.GetEnvironmentVariable("VAULTSANDBOX_URL")!)
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
.Build();
var inbox = await client.CreateInboxAsync();
// Telemetry is automatically collected
public static class VaultSandboxTelemetry
{
public const string ServiceName = "VaultSandbox.Client";
public static readonly string ServiceVersion;
public static readonly ActivitySource ActivitySource;
public static readonly Meter Meter;
}
MemberTypeDescription
ServiceNamestringConstant service name: "VaultSandbox.Client"
ServiceVersionstringAssembly version for telemetry
ActivitySourceActivitySourceSource for distributed tracing activities/spans
MeterMeterMeter for metrics collection

The SDK creates spans for key operations, allowing you to trace the flow of email testing across your system.

using System.Diagnostics;
using VaultSandbox.Client;
// Register the activity source with your tracer provider
var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(VaultSandboxTelemetry.ActivitySource.Name)
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://localhost:4317");
})
.Build();

The following operations create spans:

  • CreateInboxAsync - Inbox creation
  • DeleteInboxAsync - Inbox deletion
  • GetEmailsAsync - Email listing
  • GetEmailAsync - Single email retrieval
  • GetEmailRawAsync - Raw email retrieval
  • WaitForEmailAsync - Email waiting
  • WaitForEmailCountAsync - Email count waiting
  • WatchAsync - Real-time monitoring
  • ExportAsync / ImportInboxAsync - Import/export operations

Spans include relevant attributes:

AttributeDescription
vaultsandbox.inbox.addressEmail address of the inbox
vaultsandbox.inbox.hashUnique inbox identifier
vaultsandbox.email.idEmail identifier (when applicable)
vaultsandbox.email.countNumber of emails (when applicable)
vaultsandbox.operationOperation name
Activity.TraceId: abc123def456
Activity.SpanId: 789xyz
Activity.DisplayName: CreateInbox
Activity.Kind: Client
Activity.StartTime: 2024-01-15T10:30:00.000Z
Activity.Duration: 00:00:00.150
Activity.Tags:
vaultsandbox.inbox.address: [email protected]
vaultsandbox.inbox.hash: abc123
vaultsandbox.operation: CreateInbox

The SDK collects metrics for monitoring email testing performance and usage.

Track the count of specific events:

Metric NameDescriptionUnit
vaultsandbox.inboxes.createdNumber of inboxes created{inbox}
vaultsandbox.inboxes.deletedNumber of inboxes deleted{inbox}
vaultsandbox.emails.receivedNumber of emails received{email}
vaultsandbox.emails.deletedNumber of emails deleted{email}
vaultsandbox.api.callsTotal API calls made{call}
vaultsandbox.api.errorsTotal API errors encountered{error}

Track distributions of durations:

Metric NameDescriptionUnit
vaultsandbox.email.wait.durationTime spent waiting for emailsms
vaultsandbox.decryption.durationTime spent decrypting emailsms
vaultsandbox.api.call.durationDuration of API callsms
using OpenTelemetry;
using OpenTelemetry.Metrics;
using VaultSandbox.Client;
var meterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter(VaultSandboxTelemetry.Meter.Name)
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://localhost:4317");
})
.Build();
Metric: vaultsandbox.inboxes.created
Value: 5
Metric: vaultsandbox.emails.received
Value: 12
Metric: vaultsandbox.email.wait.duration
Histogram:
Count: 10
Sum: 15234 ms
Min: 102 ms
Max: 5201 ms
Metric: vaultsandbox.api.call.duration
Histogram:
Count: 47
Sum: 8923 ms
Min: 45 ms
Max: 892 ms
Program.cs
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using VaultSandbox.Client;
var builder = WebApplication.CreateBuilder(args);
// Add OpenTelemetry tracing
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing
.AddSource(VaultSandboxTelemetry.ActivitySource.Name)
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter();
})
.WithMetrics(metrics =>
{
metrics
.AddMeter(VaultSandboxTelemetry.Meter.Name)
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter();
});
// Add VaultSandbox client
builder.Services.AddVaultSandboxClient(options =>
{
options.BaseUrl = builder.Configuration["VaultSandbox:BaseUrl"]!;
options.ApiKey = builder.Configuration["VaultSandbox:ApiKey"]!;
});
var app = builder.Build();
TestFixture.cs
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
using VaultSandbox.Client;
using Xunit;
public class TelemetryFixture : IDisposable
{
public TracerProvider TracerProvider { get; }
public MeterProvider MeterProvider { get; }
public TelemetryFixture()
{
TracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(VaultSandboxTelemetry.ActivitySource.Name)
.AddConsoleExporter()
.Build();
MeterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter(VaultSandboxTelemetry.Meter.Name)
.AddConsoleExporter()
.Build();
}
public void Dispose()
{
TracerProvider.Dispose();
MeterProvider.Dispose();
}
}
[CollectionDefinition("Telemetry")]
public class TelemetryCollection : ICollectionFixture<TelemetryFixture> { }
[Collection("Telemetry")]
public class EmailTests
{
[Fact]
public async Task Should_Trace_Email_Operations()
{
var client = VaultSandboxClientBuilder.Create()
.WithBaseUrl(Environment.GetEnvironmentVariable("VAULTSANDBOX_URL")!)
.WithApiKey(Environment.GetEnvironmentVariable("VAULTSANDBOX_API_KEY")!)
.Build();
var inbox = await client.CreateInboxAsync();
// Operations are automatically traced
await client.DeleteInboxAsync(inbox.EmailAddress);
}
}
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Trace;
using VaultSandbox.Client;
var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(VaultSandboxTelemetry.ActivitySource.Name)
.AddJaegerExporter(options =>
{
options.AgentHost = "localhost";
options.AgentPort = 6831;
})
.Build();
using OpenTelemetry;
using OpenTelemetry.Metrics;
using VaultSandbox.Client;
var meterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter(VaultSandboxTelemetry.Meter.Name)
.AddPrometheusExporter()
.Build();
using OpenTelemetry;
using OpenTelemetry.Trace;
using VaultSandbox.Client;
var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(VaultSandboxTelemetry.ActivitySource.Name)
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://tempo:4317");
options.Protocol = OtlpExportProtocol.Grpc;
})
.Build();

You can create custom spans that integrate with VaultSandbox telemetry:

using System.Diagnostics;
using VaultSandbox.Client;
public class EmailTestService
{
private readonly IVaultSandboxClient _client;
public EmailTestService(IVaultSandboxClient client)
{
_client = client;
}
public async Task<bool> TestEmailFlowAsync(string scenario)
{
using var activity = VaultSandboxTelemetry.ActivitySource.StartActivity(
"TestEmailFlow",
ActivityKind.Internal);
activity?.SetTag("test.scenario", scenario);
try
{
var inbox = await _client.CreateInboxAsync();
activity?.SetTag("vaultsandbox.inbox.address", inbox.EmailAddress);
// Trigger email
await TriggerTestEmailAsync(inbox.EmailAddress);
// Wait for email
var email = await inbox.WaitForEmailAsync(new WaitForEmailOptions
{
Timeout = TimeSpan.FromSeconds(30)
});
activity?.SetTag("test.result", "success");
activity?.SetStatus(ActivityStatusCode.Ok);
await _client.DeleteInboxAsync(inbox.EmailAddress);
return true;
}
catch (Exception ex)
{
activity?.SetTag("test.result", "failure");
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.RecordException(ex);
throw;
}
}
}
var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(VaultSandboxTelemetry.ActivitySource.Name)
.SetSampler(new TraceIdRatioBasedSampler(0.1)) // Sample 10% of traces
.AddOtlpExporter()
.Build();
using OpenTelemetry.Resources;
var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource(VaultSandboxTelemetry.ActivitySource.Name)
.SetResourceBuilder(ResourceBuilder.CreateDefault()
.AddService("my-test-service", serviceVersion: "1.0.0")
.AddAttributes(new Dictionary<string, object>
{
["environment"] = "staging",
["team"] = "qa"
}))
.AddOtlpExporter()
.Build();
using System.Diagnostics;
// Set baggage at the start of a test
Baggage.SetBaggage("test.id", Guid.NewGuid().ToString());
Baggage.SetBaggage("test.name", "WelcomeEmailTest");
// Baggage is automatically propagated to child spans
var inbox = await client.CreateInboxAsync();

Set up alerts on these metrics:

  • vaultsandbox.api.errors - Alert on error rate spikes
  • vaultsandbox.email.wait.duration - Alert on slow email delivery
  • vaultsandbox.api.call.duration - Monitor API latency
public async Task ProcessUserRegistrationAsync(string email)
{
using var activity = Activity.Current?.Source.StartActivity("ProcessUserRegistration");
// Your application creates a user...
var user = await CreateUserAsync(email);
// VaultSandbox spans will be children of this activity
var inbox = await _client.CreateInboxAsync();
await SendWelcomeEmailAsync(user.Email);
var welcomeEmail = await inbox.WaitForEmailAsync(new WaitForEmailOptions
{
Timeout = TimeSpan.FromSeconds(30),
Subject = "Welcome"
});
// Verify email content...
}
  1. Verify the ActivitySource is registered:
.AddSource(VaultSandboxTelemetry.ActivitySource.Name)
  1. Check exporter configuration
  2. Verify network connectivity to collector
  1. Verify the Meter is registered:
.AddMeter(VaultSandboxTelemetry.Meter.Name)
  1. Ensure metrics are being collected (some exporters require explicit flushing)

If you see warnings about high cardinality, consider filtering attributes:

var meterProvider = Sdk.CreateMeterProviderBuilder()
.AddMeter(VaultSandboxTelemetry.Meter.Name)
.AddView(
instrumentName: "vaultsandbox.api.call.duration",
new ExplicitBucketHistogramConfiguration
{
Boundaries = new double[] { 50, 100, 200, 500, 1000, 2000, 5000 }
})
.AddOtlpExporter()
.Build();