Skip to main content

Backend Setup: C#

Enterprise Configuration

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

  1. Open the Sailfish dashboard
  2. Log in with your enterprise email
  3. Navigate to Settings > Configuration
  4. 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

OptionTypeRequiredDescription
SetApiKeystringYesYour Sailfish Enterprise API key
SetServiceIdentifierstringYesUnique identifier in <org>/<repo>/<path> format
SetServiceVersionstringNoVersion of your service
SetServiceDisplayNamestringNoDisplay name in the UI
SetGitShastringNoGit commit SHA (auto-detected from .git/HEAD)
SetGitOrgstringNoGit organization (auto-detected from .git/config)
SetGitRepostringNoGit repository name (auto-detected from .git/config)
SetGitProviderstringNoGit provider -- "github", "gitlab", "bitbucket" (auto-detected)
SetDomainsToNotPropagateHeadersToList<string>NoDomains to skip header propagation
SetRoutesToSkipNetworkHopsList<string>NoRoutes to skip inbound tracing

What Gets Captured Automatically

Once AddSailfish is called, these are captured with zero additional code:

  • Structured logs -- All ILogger calls (LogInformation, LogWarning, LogError, etc.)
  • Print statements -- All Console.WriteLine() and Console.Write() output
  • Inbound HTTP requests -- Timing, status codes, headers (via UseSailfish() middleware)
  • Outbound HTTP requests -- Via SailfishDelegatingHandler in the HttpClient pipeline
  • 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:

  • SailfishConfig as a singleton
  • SailfishLoggerProvider for structured log capture
  • SailfishDelegatingHandler for 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 PlatformEnvironment Variable
GitHub ActionsGITHUB_SHA
GitLab CICI_COMMIT_SHA
CircleCICIRCLE_SHA1
JenkinsGIT_COMMIT
Bitbucket PipelinesBITBUCKET_COMMIT
Travis CITRAVIS_COMMIT
Azure DevOpsBUILD_SOURCEVERSION
AWS CodeBuildCODEBUILD_RESOLVED_SOURCE_VERSION
VercelVERCEL_GIT_COMMIT_SHA

For custom CI systems, set it manually:

docker build --build-arg GIT_SHA=$(git rev-parse HEAD) .

Environment Variables

VariableDefaultDescription
SAILFISH_API_KEY--API key (alternative to SetApiKey())
SERVICE_IDENTIFIER--Service name (alternative to SetServiceIdentifier())
SF_DEBUGfalseEnable debug logging to stderr
SF_FUNCSPAN_CAPTURE_ARGUMENTSfalseCapture function arguments
SF_FUNCSPAN_CAPTURE_RETURN_VALUEfalseCapture return values
SF_FUNCSPAN_ARG_LIMIT_MB1Max argument capture size in MB
SF_FUNCSPAN_RETURN_LIMIT_MB1Max return value capture size in MB
SF_FUNCSPAN_ENABLE_SAMPLINGfalseEnable span sampling
SF_FUNCSPAN_SAMPLE_RATE1.0Function span sampling rate (0.0 to 1.0)
SF_FUNCSPAN_AUTOCAPTURE_ALL_CHILD_FUNCTIONSfalseEnable Harmony runtime auto-instrumentation
SF_NETWORKHOP_CAPTURE_REQUEST_BODYfalseCapture HTTP request bodies
SF_NETWORKHOP_CAPTURE_RESPONSE_BODYfalseCapture HTTP response bodies
SF_DISABLE_PRINT_CAPTUREfalseDisable 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

  1. Deploy your application with the Sailfish SDK configured
  2. Trigger some HTTP requests
  3. Open the Sailfish dashboard -- you should see telemetry appearing

Next Steps


Local Development

Looking to set up SF Veritas for local development with the Desktop App? See the Desktop App C# guide.