Backend Setup: C#
This guide covers C# instrumentation for Sailfish Enterprise. Key differences from local development:
- API key: Use your Enterprise API key (shown as
"<ApiKey />"below -- auto-replaced when you're logged in) - GraphQL endpoint: Do not set it -- the SDK defaults to the Sailfish cloud endpoint automatically
- Service identifier: Use the
<org>/<repo>/<path-to-this-file>format (e.g.,acme-corp/web-api/Program.cs) - No environment gating: Enterprise telemetry runs in all environments (staging, production)
If you connected GitHub and received Auto-Installation PRs, the API key, service identifier, and graphql endpoint are already configured for you.
This guide walks you through instrumenting an ASP.NET Core application with the SF Veritas SDK. C# instrumentation captures logs, print statements, exceptions, HTTP request/response telemetry, and function execution spans.
Getting Your API Key
- Open the Sailfish dashboard
- Log in with your enterprise email
- Navigate to Settings > Configuration
- Copy your company's API key
Installation
Add the SF Veritas NuGet package to your project:
dotnet add package SailfishAI.SfVeritas
Or add it directly to your .csproj:
<PackageReference Include="SailfishAI.SfVeritas" Version="0.2.0" />
Basic Setup
Add the following to your application's startup code:
using SailfishAI.SfVeritas;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSailfish(cfg =>
{
cfg.SetApiKey("<see-api-key-from-your-account-settings-page>");
cfg.SetServiceIdentifier("acme-corp/web-api/Program.cs"); // Format: <org>/<repo>/<path-to-this-file>
cfg.SetServiceVersion("1.0.0");
});
builder.Logging.AddSailfishLogger();
var app = builder.Build();
app.UseSailfish();
app.MapGet("/", () => "Hello World!");
app.Run();
Configuration Options
| Option | Type | Required | Description |
|---|---|---|---|
SetApiKey | string | Yes | Your Sailfish Enterprise API key |
SetServiceIdentifier | string | Yes | Unique identifier in <org>/<repo>/<path> format |
SetServiceVersion | string | No | Version of your service |
SetServiceDisplayName | string | No | Display name in the UI |
SetGitSha | string | No | Git commit SHA (auto-detected from .git/HEAD) |
SetGitOrg | string | No | Git organization (auto-detected from .git/config) |
SetGitRepo | string | No | Git repository name (auto-detected from .git/config) |
SetGitProvider | string | No | Git provider -- "github", "gitlab", "bitbucket" (auto-detected) |
SetDomainsToNotPropagateHeadersTo | List<string> | No | Domains to skip header propagation |
SetRoutesToSkipNetworkHops | List<string> | No | Routes to skip inbound tracing |
What Gets Captured Automatically
Once AddSailfish is called, these are captured with zero additional code:
- Structured logs -- All
ILoggercalls (LogInformation,LogWarning,LogError, etc.) - Print statements -- All
Console.WriteLine()andConsole.Write()output - Inbound HTTP requests -- Timing, status codes, headers (via
UseSailfish()middleware) - Outbound HTTP requests -- Via
SailfishDelegatingHandlerin theHttpClientpipeline - Exceptions -- Unhandled exceptions via
AppDomain.UnhandledException+TaskScheduler.UnobservedTaskException - Function spans -- Automatic endpoint spans for Minimal API routes
DI-Based Setup
SF Veritas integrates with ASP.NET Core's dependency injection. The AddSailfish() extension method handles all service registration:
var builder = WebApplication.CreateBuilder(args);
// Registers SailfishConfig, ILoggerProvider, and HttpClient handler
builder.Services.AddSailfish(cfg =>
{
cfg.SetApiKey("<see-api-key-from-your-account-settings-page>");
cfg.SetServiceIdentifier("acme-corp/web-api/Program.cs"); // Format: <org>/<repo>/<path-to-this-file>
});
// Optional: add the Sailfish logger provider
builder.Logging.AddSailfishLogger();
var app = builder.Build();
// Adds inbound HTTP middleware
app.UseSailfish();
AddSailfish() registers:
SailfishConfigas a singletonSailfishLoggerProviderfor structured log captureSailfishDelegatingHandlerfor outbound HTTP tracing- A named
HttpClient("SailfishTracked") with the handler pre-configured
Automatic Function Instrumentation
SF Veritas provides two mechanisms for automatic function span capture:
1. DispatchProxy (Interface-Based)
Register services with AddSailfishAutoInstrumentation<TInterface, TImpl>() to automatically wrap every method call with a span:
// Define your service interface
public interface IOrderService
{
Task<Order> GetOrderAsync(string orderId);
Order CreateOrder(CreateOrderRequest request);
[SailfishIgnore] // Exclude from instrumentation
string GetVersion();
}
public class OrderService : IOrderService
{
public async Task<Order> GetOrderAsync(string orderId)
{
// This method is automatically instrumented
return await db.Orders.FindAsync(orderId);
}
public Order CreateOrder(CreateOrderRequest request)
{
// This method is automatically instrumented
return db.Orders.Add(request);
}
public string GetVersion() => "1.0.0"; // Excluded via [SailfishIgnore]
}
Register it in DI:
builder.Services.AddSailfishAutoInstrumentation<IOrderService, OrderService>();
This captures:
- Function name, arguments, and return values
- Execution timing (nanosecond precision)
- Async methods (
Task,Task<T>,ValueTask,ValueTask<T>) - Parent/child span relationships
2. Harmony Runtime Patching
When SF_FUNCSPAN_AUTOCAPTURE_ALL_CHILD_FUNCTIONS=true is set, SF Veritas uses the Harmony library to patch all public methods in your application assemblies at runtime. This provides comprehensive instrumentation without any code changes.
# Enable automatic instrumentation of all user-code methods
SF_FUNCSPAN_AUTOCAPTURE_ALL_CHILD_FUNCTIONS=true dotnet run
Endpoint Auto-Capture
For Minimal API applications, use AddSailfishEndpointFilter() to capture a span for every endpoint in a route group:
// Option 1: Auto-capture all endpoints
var group = app.MapGroup("/api").AddSailfishEndpointFilter();
group.MapGet("/users", GetUsers);
group.MapGet("/orders", GetOrders);
// Option 2: Per-endpoint opt-in
app.MapGet("/api/critical", HandleCritical).AddSailfishEndpointFilter();
Manual Function Tracing
For fine-grained control, use SailfishSpan directly with the using pattern:
Basic Span
using SailfishAI.SfVeritas;
public Order ProcessOrder(string orderId)
{
using var span = SailfishSpan.Start();
var order = db.Orders.Find(orderId);
span.SetReturnValue(order);
return order;
}
Span with Arguments
public Order ProcessOrder(string orderId, decimal amount)
{
using var span = SailfishSpan.Start(
arguments: new Dictionary<string, object?>
{
["orderId"] = orderId,
["amount"] = amount,
});
var result = DoWork(orderId, amount);
span.SetReturnValue(result);
return result;
}
Exception Reporting
try
{
await riskyOperation();
}
catch (Exception ex)
{
SailfishVeritas.CaptureException(ex, wasCaught: true);
throw; // Re-throw or handle
}
User Identification
SailfishVeritas.Identify("user-123", new Dictionary<string, object?>
{
["email"] = "user@example.com",
["name"] = "Jane Doe",
["plan"] = "enterprise",
});
Framework Examples
ASP.NET Core Minimal API
using SailfishAI.SfVeritas;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSailfish(cfg =>
{
cfg.SetApiKey("<see-api-key-from-your-account-settings-page>");
cfg.SetServiceIdentifier("acme-corp/minimal-api/Program.cs"); // Format: <org>/<repo>/<path-to-this-file>
});
builder.Logging.AddSailfishLogger();
var app = builder.Build();
app.UseSailfish();
app.MapGet("/api/users", () =>
{
Console.WriteLine("Fetching users"); // Appears in Sailfish
return Results.Ok(new { users = new string[] {} });
});
app.MapGet("/api/health", () => Results.Ok("ok"));
app.Run();
ASP.NET Core MVC / Controllers
using SailfishAI.SfVeritas;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSailfish(cfg =>
{
cfg.SetApiKey("<see-api-key-from-your-account-settings-page>");
cfg.SetServiceIdentifier("acme-corp/mvc-api/Program.cs"); // Format: <org>/<repo>/<path-to-this-file>
});
builder.Logging.AddSailfishLogger();
var app = builder.Build();
app.UseSailfish();
app.MapControllers();
app.Run();
Worker Service / Background Jobs
For non-web applications (background workers, console apps), use SailfishVeritas.Setup() directly:
using SailfishAI.SfVeritas;
SailfishVeritas.Setup(
apiKey: "<see-api-key-from-your-account-settings-page>",
serviceIdentifier: "acme-corp/worker/Program.cs" // Format: <org>/<repo>/<path-to-this-file>
);
// Your application logic...
// Graceful shutdown
SailfishVeritas.Shutdown();
Outbound HTTP Tracing
SF Veritas captures outbound HTTP requests through the SailfishDelegatingHandler. When registered via AddSailfish(), the named client "SailfishTracked" is available via DI:
public class MyService
{
private readonly HttpClient _client;
public MyService(IHttpClientFactory clientFactory)
{
_client = clientFactory.CreateClient("SailfishTracked");
}
public async Task<string> CallExternalApi()
{
var response = await _client.GetAsync("https://api.example.com/data");
return await response.Content.ReadAsStringAsync();
}
}
GIT_SHA
The SDK auto-detects GIT_SHA from common CI/CD platforms:
| CI Platform | Environment Variable |
|---|---|
| GitHub Actions | GITHUB_SHA |
| GitLab CI | CI_COMMIT_SHA |
| CircleCI | CIRCLE_SHA1 |
| Jenkins | GIT_COMMIT |
| Bitbucket Pipelines | BITBUCKET_COMMIT |
| Travis CI | TRAVIS_COMMIT |
| Azure DevOps | BUILD_SOURCEVERSION |
| AWS CodeBuild | CODEBUILD_RESOLVED_SOURCE_VERSION |
| Vercel | VERCEL_GIT_COMMIT_SHA |
For custom CI systems, set it manually:
docker build --build-arg GIT_SHA=$(git rev-parse HEAD) .
Environment Variables
| Variable | Default | Description |
|---|---|---|
SAILFISH_API_KEY | -- | API key (alternative to SetApiKey()) |
SERVICE_IDENTIFIER | -- | Service name (alternative to SetServiceIdentifier()) |
SF_DEBUG | false | Enable debug logging to stderr |
SF_FUNCSPAN_CAPTURE_ARGUMENTS | false | Capture function arguments |
SF_FUNCSPAN_CAPTURE_RETURN_VALUE | false | Capture return values |
SF_FUNCSPAN_ARG_LIMIT_MB | 1 | Max argument capture size in MB |
SF_FUNCSPAN_RETURN_LIMIT_MB | 1 | Max return value capture size in MB |
SF_FUNCSPAN_ENABLE_SAMPLING | false | Enable span sampling |
SF_FUNCSPAN_SAMPLE_RATE | 1.0 | Function span sampling rate (0.0 to 1.0) |
SF_FUNCSPAN_AUTOCAPTURE_ALL_CHILD_FUNCTIONS | false | Enable Harmony runtime auto-instrumentation |
SF_NETWORKHOP_CAPTURE_REQUEST_BODY | false | Capture HTTP request bodies |
SF_NETWORKHOP_CAPTURE_RESPONSE_BODY | false | Capture HTTP response bodies |
SF_DISABLE_PRINT_CAPTURE | false | Disable stdout/stderr capture |
SF_LOG_IGNORE_REGEX | -- | Regex pattern to suppress logs from telemetry |
Multi-Service Setup
When running multiple .NET services, give each a unique ServiceIdentifier:
// user-service/Program.cs
builder.Services.AddSailfish(cfg =>
{
cfg.SetApiKey("<see-api-key-from-your-account-settings-page>");
cfg.SetServiceIdentifier("acme-corp/user-service/Program.cs"); // Format: <org>/<repo>/<path-to-this-file>
});
// order-service/Program.cs
builder.Services.AddSailfish(cfg =>
{
cfg.SetApiKey("<ApiKey />");
cfg.SetServiceIdentifier("acme-corp/order-service/Program.cs"); // Format: <org>/<repo>/<path-to-this-file>
});
Verifying the Setup
- Deploy your application with the Sailfish SDK configured
- Trigger some HTTP requests
- Open the Sailfish dashboard -- you should see telemetry appearing
Next Steps
- Set up your frontend application (optional)
- Check the Sailfish dashboard to verify telemetry is flowing
Looking to set up SF Veritas for local development with the Desktop App? See the Desktop App C# guide.