To maintain security and reduce noise in production, follow these logging guidelines:
- Never log secrets or PII at INFO level. This includes tokens (
SOROBAN_SOURCE_SECRET, OAuth tokens, JWT secrets), emails, and KYC decision data. - Use
slog.Debugfor verbose data. Large payloads (like webhook bodies) and full header dumps must be logged atDebuglevel, notInfo. - Redact sensitive fields. When logging maps or structs that contain addresses or amounts, use the
logger.RedactMaphelper to sanitize the output before logging atInfo.
Example:
import "github.com/jagadeesh/grainlify/backend/internal/logger"
redactedArgs := logger.RedactMap(args)
slog.Info("interaction occurred", "args", redactedArgs) // Safe for INFO
slog.Debug("interaction detailed", "args", args) // Safe for DEBUGThe server will automatically restart when you make changes to any .go file.
# Quick start - recommended (handles PATH and installation automatically)
./run-dev.sh
# Or directly with air (if already installed)
air
# Or using make
make devWhat gets watched:
- All
.gofiles incmd/,internal/, and root - Automatically excludes:
tmp/,vendor/,testdata/,migrations/,.git/, test files - Restarts within 1 second of file changes
First time setup:
# Install air
go install github.com/air-verse/air@latest
# Add to PATH (add to ~/.zshrc or ~/.bashrc)
export PATH=$PATH:$HOME/go/bingo run ./cmd/api
# Or using make
make runIf air is not found, install it:
go install github.com/air-verse/air@latestMake sure ~/go/bin is in your PATH. Add this to your ~/.zshrc or ~/.bashrc:
export PATH=$PATH:$HOME/go/binAir configuration is in .air.toml. It watches for changes in:
- All
.gofiles - Excludes
tmp/,vendor/,testdata/,migrations/,.git/ - Excludes
*_test.gofiles
# Build binary
make build
# or
go build -o ./api ./cmd/api
# Run migrations
go run ./cmd/migrate
# Run worker
go run ./cmd/workergo test ./internal/handlers/... ./internal/ingest/...The handler tests (internal/handlers) are pure unit tests with a mock bus — no external dependencies.
DB integration tests in internal/ingest are gated behind the TEST_DB_URL environment variable.
When the variable is absent the tests are skipped automatically — they never fail in CI unless you opt in.
Set TEST_DB_URL to a throwaway Postgres database:
export TEST_DB_URL="postgres://user:pass@localhost:5432/grainlify_test?sslmode=disable"
go test ./internal/ingest/...The test harness calls migrate.Up automatically, so the target database only needs to exist (it does not need pre-created tables).
Each test cleans up the rows it inserts via t.Cleanup, so the schema stays clean between runs.
CI: add
TEST_DB_URLas a secret/environment variable in your pipeline to enable DB integration tests.
CI runs golangci-lint with the pinned version in .github/workflows/ci.yml.
Install the same version locally:
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2Then run:
golangci-lint run ./...
# or
make lintThe lint configuration is in .golangci.yml. Existing legacy findings are explicitly excluded there so new changes can be checked without forcing a broad cleanup in the first linting PR.