LogBasset follows the standard Go project layout:
logbasset/
├── cmd/logbasset/main.go # Main application entry point
├── internal/
│ ├── app/ # Application logic & version info
│ ├── cli/ # CLI command implementations
│ ├── client/ # API client (split: client.go, basic_query.go, power_query.go, numeric_query.go, facet_query.go, timeseries_query.go, tail.go, types.go)
│ ├── config/ # Configuration management
│ ├── logging/ # Structured logging with logrus
│ ├── output/ # Output formatting (JSON, CSV, table)
│ └── errors/ # Centralized error handling
├── pkg/ # Public APIs (if needed)
├── configs/ # Config templates
├── scripts/ # Build scripts
├── docs/ # Documentation
└── examples/ # Usage examples
LogBasset now uses a structured configuration system with Viper:
- Environment variables (prefixed with
scalyr_) - Configuration files (YAML format)
- Command-line flags
- Default values
LogBasset uses structured logging with logrus:
- Log levels: debug, info, warn, error (default: info)
- Configurable via
--log-levelflag,scalyr_log_levelenv var, or config file - Structured output with timestamps and contextual fields
- Debug level shows API request/response details when verbose mode is enabled
./logbasset.yaml(current directory)~/.logbasset/logbasset.yaml(user home)~/.config/logbasset/logbasset.yaml(XDG config)
- API token presence validation
- Server URL format validation
- Priority value validation (high|low)
- Automatic type conversion and defaults
// Get configuration with validation
cfg, err := config.New()
if err != nil {
return err
}
// Override with flags
cfg.SetFromFlags(token, server, verbose, priority)
// Validate final config
if err := cfg.Validate(); err != nil {
return err
}
// Get client
client := cfg.GetClient()make build- Build the CLI tool (output to bin/)make test- Run all testsmake test-verbose- Run tests with verbose outputmake test-client- Run tests for client package onlymake test-cli- Run tests for CLI package onlymake fmt- Format codemake vet- Static analysismake lint- Run linter (requires golangci-lint)make clean- Clean build artifactsmake deps- Install and tidy dependenciesmake build-all- Build for multiple platformsgo test -v ./internal/client -run TestNew- Run a single test function
All commands now support timeout and cancellation:
# Set custom timeout (examples)
logbasset query "error" --timeout 60s
logbasset tail "error" --timeout 2m --lines 100
logbasset power-query "count by server" --start 1h --timeout 30s
# Graceful cancellation with Ctrl+C
logbasset tail ".*" --lines 1000
# Press Ctrl+C to stop cleanly
# Default timeout is 30 seconds for all operationsLogBasset uses a structured error handling system with custom error types and standard exit codes:
AuthError- API token authentication issues (exit code 4)NetworkError- Network/connection failures (exit code 3)ConfigError- Configuration problems (exit code 5)ValidationError- Input validation failures (exit code 6)UsageError- Command usage errors (exit code 2)APIError- API response errors (exit code 1)ParseError- JSON parsing failures (exit code 1)ContextError- Context cancellation/timeout errors (exit code 1)
- Use structured errors from
internal/errorspackage instead offmt.Errorf - Include helpful suggestions in error messages
- Use appropriate exit codes for different error types
- Wrap underlying errors with context using the
Causefield
// Instead of:
return fmt.Errorf("API token is required")
// Use:
return errors.NewAuthError("API token is required", nil)
// With cause:
return errors.NewNetworkError("failed to connect", err)
// Context-related errors:
return errors.NewContextError("operation timed out", context.DeadlineExceeded)0- Success1- General error2- Usage error/command misuse3- Network error4- Authentication error5- Configuration error6- Validation error
- Use Go standard formatting (gofmt)
- Package names: lowercase, single word (e.g.,
client,cli) - Types: PascalCase (e.g.,
QueryParams,Client) - Functions/methods: PascalCase for exported, camelCase for unexported
- Variables: camelCase (e.g.,
httpClient,requestParams) - Constants: PascalCase or ALL_CAPS (e.g.,
DefaultServer,APIVersion) - Use structured errors from
internal/errorspackage instead offmt.Errorf - Import ordering: standard library, third-party, local packages
- Use interfaces for testability (e.g., HTTP client abstractions)
- Struct initialization: use field names for clarity
- Context propagation: pass
context.Contextas first parameter to functions making external calls - Test files: use
testify/assertandtestify/requirefor assertions - Test function names:
TestFunctionNameorTestFunctionName_Scenario - Always defer
resp.Body.Close()immediately after checking error for HTTP responses - Use table-driven tests with
tests := []struct{}pattern for multiple test cases
LogBasset implements comprehensive context propagation for:
- Global
--timeoutflag available for all commands (default: 30s) - Context deadlines are properly enforced in all HTTP operations
- Clear error messages when operations time out
- All commands support graceful cancellation with Ctrl+C (SIGINT/SIGTERM)
- Tail command exits cleanly when cancelled without error messages
- Context cancellation is properly propagated through the entire call stack
// CLI commands create context with timeout
ctx, cancel := context.WithTimeout(context.Background(), getTimeout())
defer cancel()
// Signal handling for graceful cancellation
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigChan
cancel()
}()
// All client methods accept and use context
result, err := client.Query(ctx, params)- Context cancellation errors provide user-friendly messages
- Timeout errors suggest using
--timeoutflag to increase duration - Distinguished between user cancellation (Ctrl+C) and timeout scenarios
LogBasset has comprehensive test coverage across all packages:
- Client package: 69.5% coverage with HTTP client abstraction
- Output package: 98.9% coverage for all formatters
- Config package: 89.1% coverage for configuration management
- Errors package: 75.9% coverage for error handling
- App package: 100.0% coverage for version information
The codebase uses interface abstractions for better testability:
// HTTPClient interface allows dependency injection for testing
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
// ClientInterface defines the contract for the API client
type ClientInterface interface {
Query(ctx context.Context, params QueryParams) (*QueryResponse, error)
// ... other methods
}
// MockHTTPClient for testing
type MockHTTPClient struct {
DoFunc func(req *http.Request) (*http.Response, error)
}The client supports dependency injection for better testing:
// Standard constructor for production use
client := client.New("token", "server", false)
// Constructor with custom HTTP client for testing
mockClient := &MockHTTPClient{...}
client := client.NewWithHTTPClient("token", "server", false, mockClient)Tests use Go's httptest package for realistic HTTP testing:
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Verify request structure
assert.Equal(t, "POST", r.Method)
assert.Equal(t, "/api/query", r.URL.Path)
// Return mock response
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status": "success"}`))
}))
defer server.Close()
client := client.New("test-token", server.URL, false)
// Test client methods...- Use table-driven tests for multiple test cases
- Test both success and error scenarios
- Verify request structure and response parsing
- Test interface implementations with compile-time checks
- Cover edge cases like empty data, invalid input, network errors
- When you generate or update the CHANGELOD.md, you must be concise
- Unless I ask you to bump the version, new additions to the CHANGELOG.md must be filled under [Unreleased] section on top.