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.
We have comprehensive HTTP integration tests covering the assembled Fiber app (routing, middleware like requestid, CORS, recover, rate limiters, auth/role gates, and response shapes) without requiring a database.
Run the API integration tests:
go test -v ./internal/api/... -raceThese integration tests drive the app via app.Test(httptest.NewRequest(...)) using a mock database seam (db.DBPool) and a mock message bus. The test covers:
- Public endpoints: (e.g.
/health,/projects) returning successful statuses. - Route Precedence: Explicitly asserting that specific routes (like
/projects/mineand/projects/pending-setup) resolve before parameterized routes (like/projects/:id). - Auth and Role Gates: Asserting that
RequireAuthandRequireRoleblock invalid/missing tokens (with 401 Unauthorized) and insufficient roles (with 403 Forbidden). - Error Responses: Asserting that error envelopes use the standard structure (
ErrorEnvelope) and includerequest_idfor traceability.
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.