Backend Setup: Go
This guide walks you through instrumenting a Go application with the SF Veritas SDK for Sailfish Enterprise. Go instrumentation captures logs, print statements, exceptions/panics, HTTP request/response telemetry, and function execution spans.
If you connected GitHub and received Auto-Installation PRs, the API key, service identifier, and graphql endpoint are already configured for you. Merge the PR and you're done.
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
Install the SF Veritas Go module:
go get github.com/SailfishAI/sf-veritas-go@latest
Basic Setup
Add the following to your application's main() function:
package main
import (
"net/http"
sfveritas "github.com/SailfishAI/sf-veritas-go"
)
func main() {
sfveritas.SetupInterceptors(sfveritas.Options{
APIKey: "<see-api-key-from-your-account-settings-page>",
ServiceIdentifier: "acme-corp/go-api/cmd/server/main.go", // Format: <org>/<repo>/<path-to-this-file>
ServiceVersion: "1.0.0",
})
defer sfveritas.Shutdown()
mux := http.NewServeMux()
mux.HandleFunc("/api/users", handleUsers)
// Wrap your handler with SF Veritas middleware for HTTP tracing
http.ListenAndServe(":8080", sfveritas.Middleware(mux))
}
Configuration Options
| Option | Type | Required | Description |
|---|---|---|---|
APIKey | string | Yes | Your Sailfish Enterprise API key |
ServiceIdentifier | string | Yes | Unique identifier in <org>/<repo>/<path> format |
ServiceVersion | string | No | Version of your service |
What Gets Captured Automatically
Once SetupInterceptors is called, these are captured with zero additional code:
- Structured logs -- All
slog.Info(),slog.Warn(),slog.Error()calls - Print statements -- All
fmt.Println(),fmt.Printf(),log.Println()output - Inbound HTTP requests -- Timing, status codes, headers (via
sfveritas.Middleware) - Outbound HTTP requests -- All
http.Get(),http.Post(),client.Do()calls viahttp.DefaultTransport - Panics -- Recovered panics in HTTP handlers with full stack traces
- Exceptions -- Manually reported errors with
sfveritas.TransmitError()
Automatic Function Instrumentation (toolexec)
For full function-level tracing in the Flamechart -- including automatic argument capture, return values, and local variable capture on panic -- use the sfveritas-instrument compile-time tool.
How It Works
Go's -toolexec flag lets you intercept the compiler. sfveritas-instrument parses your source code's AST (Abstract Syntax Tree) at compile time and injects lightweight instrumentation into every function:
- Function entry -- Records the function name, file, line, and captures all argument values
- Panic recovery -- A
deferblock captures all local variable values if a panic occurs - Function exit -- Records timing and ends the span
The transformation happens at compile time, not runtime. Your source files are never modified on disk.
Install the Tool
go install github.com/SailfishAI/sf-veritas-go/cmd/sfveritas-instrument@latest
Verify it's installed:
which sfveritas-instrument
# Should print: $GOPATH/bin/sfveritas-instrument
Usage with go build
go build -toolexec="sfveritas-instrument" -o myapp ./...
Usage with go run
go run -toolexec="sfveritas-instrument" .
Usage with go test
go test -toolexec="sfveritas-instrument" ./...
Integration Patterns
Makefile
TOOLEXEC := -toolexec="sfveritas-instrument"
.PHONY: dev prod test
# Build with instrumentation
dev:
go run $(TOOLEXEC) .
# Production build (also instrumented for Enterprise telemetry)
prod:
GIT_SHA=$$(git rev-parse HEAD) go build $(TOOLEXEC) -o myapp ./...
# Tests with instrumentation
test:
go test $(TOOLEXEC) ./...
Air Hot-Reload
Air is a popular live-reload tool for Go. Add -toolexec to the build command in .air.toml:
# .air.toml
root = "."
tmp_dir = "tmp"
[build]
cmd = "go build -toolexec='sfveritas-instrument' -o ./tmp/main ."
bin = "tmp/main"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor"]
exclude_regex = ["_test\\.go"]
include_ext = ["go", "tpl", "tmpl", "html"]
kill_delay = "0s"
[misc]
clean_on_exit = true
Docker Compose
# Dockerfile
FROM golang:1.22-alpine
WORKDIR /app
# Install the instrumentation tool
RUN go install github.com/SailfishAI/sf-veritas-go/cmd/sfveritas-instrument@latest
# Copy module files first for caching
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Build with instrumentation
ARG GIT_SHA
ENV GIT_SHA=$GIT_SHA
RUN go build -toolexec="sfveritas-instrument" -o /app/server .
CMD ["/app/server"]
# docker-compose.yml
services:
api:
build:
context: .
args:
GIT_SHA: ${GIT_SHA:-$(git rev-parse HEAD)}
ports:
- "8080:8080"
IDE Run Buttons
VS Code
Add to .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Run with SF Veritas",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}",
"buildFlags": "-toolexec=sfveritas-instrument"
}
]
}
GoLand / IntelliJ
- Open Run/Debug Configurations
- In the Go Build configuration, add to Go tool arguments:
-toolexec=sfveritas-instrument
Is toolexec Production-Safe?
Yes, and for Enterprise you should use it in production. The overhead is minimal (~1-2 microseconds per function call) and the telemetry data powers Sailfish's issue detection and debugging.
| Concern | Answer |
|---|---|
| Does toolexec modify my source files? | No. It operates on a copy during compilation. Your .go files are never changed. |
| Does the output binary differ? | Yes -- instrumented functions have extra span tracking code. This adds a small overhead per function call. |
| What's the overhead? | Each instrumented function call adds ~1-2 microseconds for span creation/closure. |
What if sfveritas-instrument isn't installed? | The build fails with exec: "sfveritas-instrument": executable file not found. |
Framework Examples
net/http (stdlib)
package main
import (
"fmt"
"net/http"
sfveritas "github.com/SailfishAI/sf-veritas-go"
)
func main() {
sfveritas.SetupInterceptors(sfveritas.Options{
APIKey: "<see-api-key-from-your-account-settings-page>",
ServiceIdentifier: "acme-corp/my-api/cmd/server/main.go", // Format: <org>/<repo>/<path-to-this-file>
})
defer sfveritas.Shutdown()
mux := http.NewServeMux()
mux.HandleFunc("/api/users", getUsers)
mux.HandleFunc("/api/health", healthCheck)
fmt.Println("Server starting on :8080")
http.ListenAndServe(":8080", sfveritas.Middleware(mux))
}
func getUsers(w http.ResponseWriter, r *http.Request) {
fmt.Println("Fetching users") // Appears in Sailfish
w.Write([]byte(`{"users": []}`))
}
func healthCheck(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
}
Run with instrumentation:
go run -toolexec="sfveritas-instrument" .
Gin
package main
import (
"github.com/gin-gonic/gin"
sfveritas "github.com/SailfishAI/sf-veritas-go"
)
func main() {
sfveritas.SetupInterceptors(sfveritas.Options{
APIKey: "<see-api-key-from-your-account-settings-page>",
ServiceIdentifier: "acme-corp/gin-api/cmd/server/main.go", // Format: <org>/<repo>/<path-to-this-file>
})
defer sfveritas.Shutdown()
r := gin.Default()
r.GET("/api/users", getUsers)
r.Run(":8080")
}
func getUsers(c *gin.Context) {
c.JSON(200, gin.H{"users": []string{}})
}
Echo
package main
import (
"net/http"
"github.com/labstack/echo/v4"
sfveritas "github.com/SailfishAI/sf-veritas-go"
)
func main() {
sfveritas.SetupInterceptors(sfveritas.Options{
APIKey: "<see-api-key-from-your-account-settings-page>",
ServiceIdentifier: "acme-corp/echo-api/cmd/server/main.go", // Format: <org>/<repo>/<path-to-this-file>
})
defer sfveritas.Shutdown()
e := echo.New()
e.GET("/api/users", getUsers)
// Start with SF Veritas middleware wrapping Echo
http.ListenAndServe(":8080", sfveritas.Middleware(e))
}
func getUsers(c echo.Context) error {
return c.JSON(200, map[string]interface{}{"users": []string{}})
}
Fiber
package main
import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/adaptor"
sfveritas "github.com/SailfishAI/sf-veritas-go"
)
func main() {
sfveritas.SetupInterceptors(sfveritas.Options{
APIKey: "<see-api-key-from-your-account-settings-page>",
ServiceIdentifier: "acme-corp/fiber-api/cmd/server/main.go", // Format: <org>/<repo>/<path-to-this-file>
})
defer sfveritas.Shutdown()
app := fiber.New()
// Use the adaptor to wrap SF Veritas middleware
app.Use(adaptor.HTTPMiddleware(func(next http.Handler) http.Handler {
return sfveritas.Middleware(next)
}))
app.Get("/api/users", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"users": []string{}})
})
app.Listen(":8080")
}
Manual Function Tracing
If you prefer not to use -toolexec, or need fine-grained control over which functions are traced, use the manual API:
Basic Span
func ProcessOrder(ctx context.Context, orderID string) error {
span := sfveritas.StartSpan(ctx, "ProcessOrder")
defer func() { span.End(nil) }()
// Use span.Context() for child operations
result, err := validateOrder(span.Context(), orderID)
if err != nil {
sfveritas.TransmitError(span.Context(), err)
return err
}
span.End(result)
return nil
}
Span with Arguments
func ProcessOrder(ctx context.Context, orderID string, amount float64) error {
span := sfveritas.StartSpanWithArgs(ctx, "ProcessOrder", map[string]interface{}{
"orderID": orderID,
"amount": amount,
})
defer func() { span.End(nil) }()
// ... function body ...
return nil
}
TraceFunc Helper
result, err := sfveritas.TraceFunc(ctx, "ProcessOrder", func(ctx context.Context) (string, error) {
return doWork(ctx)
})
Environment Variables
| Variable | Default | Description |
|---|---|---|
SF_API_KEY | -- | API key (alternative to Options.APIKey) |
SF_SERVICE_IDENTIFIER | -- | Service name (alternative to Options.ServiceIdentifier) |
SF_DEBUG | false | Enable debug logging to stderr |
SF_FUNCSPAN_SAMPLE_RATE | 1.0 | Function span sampling rate (0.0 to 1.0) |
SF_FUNCSPAN_ENABLE_SAMPLING | false | Enable span sampling |
SF_LOG_IGNORE_REGEX | -- | Regex pattern to suppress logs from telemetry |
SF_NETWORKHOP_CAPTURE_REQUEST_BODY | false | Capture HTTP request bodies |
SF_NETWORKHOP_CAPTURE_RESPONSE_BODY | false | Capture HTTP response bodies |
SF_NETWORKHOP_REQUEST_LIMIT_MB | 1 | Max request body capture size in MB |
SF_NETWORKHOP_RESPONSE_LIMIT_MB | 1 | Max response body capture size in MB |
SF_DISABLE_PRINT_CAPTURE | false | Disable stdout pipe capture |
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_EXCLUDED_DOMAINS | -- | Comma-separated domains to skip outbound tracing |
SF_DISABLE_INBOUND_NETWORK_TRACING_ON_ROUTES | -- | Comma-separated glob patterns for routes to skip |
SF_INSTRUMENT_DEBUG | false | Enable debug output from the toolexec wrapper |
Configuration File
Create a .sailfish file in your project root for per-file and per-function span configuration:
{
"files": {
"*.go": {
"capture_arguments": true,
"capture_return_value": true,
"sample_rate": 1.0
},
"*_test.go": {
"sample_rate": 0.0
}
},
"functions": {
"ProcessOrder": {
"capture_arguments": true,
"capture_return_value": true,
"arg_limit_mb": 2,
"return_limit_mb": 2
},
"healthCheck": {
"sample_rate": 0.0
}
}
}
File Configuration
| Option | Type | Description |
|---|---|---|
capture_arguments | bool | Capture function arguments |
capture_return_value | bool | Capture return values |
arg_limit_mb | int | Max argument size in MB |
return_limit_mb | int | Max return value size in MB |
sample_rate | float | Sampling rate (0.0 to 1.0) |
Function Configuration
Same options as file configuration but applied to specific function names. Function-level config takes priority over file-level config.
Verifying the Setup
- Deploy your application with the Sailfish SDK configured
- Trigger some HTTP requests
- Open the Sailfish dashboard -- you should see telemetry appearing
Debug Mode
Enable debug output to verify instrumentation is working:
SF_DEBUG=true go run -toolexec="sfveritas-instrument" .
You'll see output like:
[sfveritas] Initializing Go Go BACKEND collector v0.1.0
[sfveritas] Endpoint: https://api-service.sailfish.ai/graphql/
[sfveritas] Setup complete. Interceptors active.
Troubleshooting
No logs appearing
- Check the API key: Ensure
APIKeyis set to your Enterprise API key - Check the service identifier: Ensure
ServiceIdentifieruses the<org>/<repo>/<path>format - Check terminal output: Look for
[sfveritas]initialization messages
sfveritas-instrument: command not found
- Ensure
$GOPATH/binis in your$PATH:export PATH=$PATH:$(go env GOPATH)/bin - Re-install:
go install github.com/SailfishAI/sf-veritas-go/cmd/sfveritas-instrument@latest
Connection errors
- Ensure your deployment can reach
https://api-service.sailfish.ai - Check that outbound HTTPS (port 443) is not blocked by a firewall or network policy
Build errors with toolexec
- Verify Go version: Requires Go 1.22+
- Check the tool is built:
sfveritas-instrument --helpshould not error - Enable debug: Set
SF_INSTRUMENT_DEBUG=trueto see which files are being instrumented
Multi-Service Setup
When running multiple Go services, give each a unique ServiceIdentifier following the <org>/<repo>/<path> format:
// user-service/main.go
sfveritas.SetupInterceptors(sfveritas.Options{
APIKey: "<see-api-key-from-your-account-settings-page>",
ServiceIdentifier: "acme-corp/user-service/cmd/server/main.go", // Format: <org>/<repo>/<path-to-this-file>
})
// order-service/main.go
sfveritas.SetupInterceptors(sfveritas.Options{
APIKey: "<ApiKey />",
ServiceIdentifier: "acme-corp/order-service/cmd/server/main.go", // Format: <org>/<repo>/<path-to-this-file>
})
Use the service filter in the Sailfish dashboard to switch between services.
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 Go guide.