Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 38 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,52 +14,65 @@ To use, run the following command:
go get github.com/raystack/salt
```

## Pacakages
## Packages

### Configuration and Environment
- **`config`**
### Configuration
- **`config`**
Utilities for managing application configurations using environment variables, files, or defaults.

### CLI Utilities
- **`cli/cmdx`**
Command execution and management tools.
- **`cli/commander`**
Command execution, completion, help topics, and management tools.

- **`cli/printer`**
- **`cli/printer`**
Utilities for formatting and printing output to the terminal.

- **`cli/prompt`**
- **`cli/prompter`**
Interactive CLI prompts for user input.

- **`cli/terminal`**
Terminal utilities for colors, cursor management, and formatting.
- **`cli/terminator`**
Terminal utilities for browser, pager, and brew helpers.

- **`cli/version`**
- **`cli/releaser`**
Utilities for displaying and managing CLI tool versions.

### Authentication and Security
- **`auth/oidc`**
- **`auth/oidc`**
Helpers for integrating OpenID Connect authentication flows.

- **`auth/audit`**
- **`auth/audit`**
Auditing tools for tracking security events and compliance.

### Server and Infrastructure
- **`server`**
Utilities for setting up and managing HTTP or RPC servers.
- **`server/mux`**
gRPC-gateway multiplexer for serving gRPC and HTTP on a single port.

- **`db`**
- **`server/spa`**
Single-page application static file handler.

- **`db`**
Helpers for database connections, migrations, and query execution.

- **`telemetry`**
Observability tools for capturing application metrics and traces.
### Observability
- **`observability`**
OpenTelemetry initialization, metrics, and tracing setup.

### Development and Testing
- **`dockertestx`**
Tools for creating and managing Docker-based testing environments.
- **`observability/logger`**
Structured logging with Zap and Logrus adapters.

- **`observability/otelgrpc`**
OpenTelemetry gRPC client interceptors for metrics.

### Utilities
- **`log`**
Simplified logging utilities for structured and unstructured log messages.
- **`observability/otelhttpclient`**
OpenTelemetry HTTP client transport for metrics.

- **`utils`**
General-purpose utility functions for common programming tasks.
### Data Utilities
- **`data/rql`**
REST query language parser for filters, pagination, sorting, and search.

- **`data/jsondiff`**
JSON document diffing and reconstruction.

### Development and Testing
- **`testing/dockertestx`**
Docker-based test environment helpers for Postgres, Minio, SpiceDB, and more.
4 changes: 2 additions & 2 deletions auth/audit/repositories/dockertest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import (
_ "github.com/lib/pq"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
"github.com/raystack/salt/log"
"github.com/raystack/salt/observability/logger"
)

func newTestRepository(logger log.Logger) (*repositories.PostgresRepository, *dockertest.Pool, *dockertest.Resource, error) {
func newTestRepository(logger logger.Logger) (*repositories.PostgresRepository, *dockertest.Pool, *dockertest.Resource, error) {
host := "localhost"
port := "5433"
user := "test_user"
Expand Down
4 changes: 2 additions & 2 deletions auth/audit/repositories/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/jmoiron/sqlx/types"
"github.com/raystack/salt/log"
"github.com/raystack/salt/observability/logger"
"github.com/stretchr/testify/suite"
)

Expand All @@ -27,7 +27,7 @@ func TestPostgresRepository(t *testing.T) {

func (s *PostgresRepositoryTestSuite) SetupSuite() {
var err error
repository, pool, dockerResource, err := newTestRepository(log.NewLogrus())
repository, pool, dockerResource, err := newTestRepository(logger.NewLogrus())
if err != nil {
s.T().Fatal(err)
}
Expand Down
6 changes: 3 additions & 3 deletions jsondiff/README.md → data/jsondiff/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ A Go library for calculating differences between JSON documents and reconstructi
## Installation

```bash
go get github.com/raystack/salt/jsondiff
go get github.com/raystack/salt/data/jsondiff
```

## Usage
Expand All @@ -28,7 +28,7 @@ import (
"fmt"
"reflect"
"strings"
"github.com/raystack/salt/jsondiff"
"github.com/raystack/salt/data/jsondiff"
)

func main() {
Expand Down Expand Up @@ -105,7 +105,7 @@ import (
"fmt"
"reflect"
"strings"
"github.com/raystack/salt/jsondiff"
"github.com/raystack/salt/data/jsondiff"
)

func main() {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion log/logger.go → observability/logger/logger.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package log
package logger

import (
"io"
Expand Down
4 changes: 2 additions & 2 deletions log/logrus.go → observability/logger/logrus.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package log
package logger

import (
"io"
Expand Down Expand Up @@ -76,7 +76,7 @@ func LogrusWithWriter(writer io.Writer) Option {
// func (p *PlainFormatter) Format(entry *logrus.Entry) ([]byte, error) {
// return []byte(entry.Message), nil
// }
// l := log.NewLogrus(log.LogrusWithFormatter(&PlainFormatter{}))
// l := logger.NewLogrus(logger.LogrusWithFormatter(&PlainFormatter{}))
func LogrusWithFormatter(f logrus.Formatter) Option {
return func(logger interface{}) {
logger.(*Logrus).log.SetFormatter(f)
Expand Down
14 changes: 7 additions & 7 deletions log/logrus_test.go → observability/logger/logrus_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package log_test
package logger_test

import (
"bufio"
Expand All @@ -8,7 +8,7 @@ import (

"github.com/sirupsen/logrus"

"github.com/raystack/salt/log"
"github.com/raystack/salt/observability/logger"

"github.com/stretchr/testify/assert"
)
Expand All @@ -18,7 +18,7 @@ func TestLogrus(t *testing.T) {
var b bytes.Buffer
foo := bufio.NewWriter(&b)

logger := log.NewLogrus(log.LogrusWithLevel("debug"), log.LogrusWithWriter(foo), log.LogrusWithFormatter(&logrus.TextFormatter{
logger := logger.NewLogrus(logger.LogrusWithLevel("debug"), logger.LogrusWithWriter(foo), logger.LogrusWithFormatter(&logrus.TextFormatter{
DisableTimestamp: true,
}))
logger.Info("hello world")
Expand All @@ -30,7 +30,7 @@ func TestLogrus(t *testing.T) {
var b bytes.Buffer
foo := bufio.NewWriter(&b)

logger := log.NewLogrus(log.LogrusWithLevel("info"), log.LogrusWithWriter(foo), log.LogrusWithFormatter(&logrus.TextFormatter{
logger := logger.NewLogrus(logger.LogrusWithLevel("info"), logger.LogrusWithWriter(foo), logger.LogrusWithFormatter(&logrus.TextFormatter{
DisableTimestamp: true,
}))
logger.Debug("hello world")
Expand All @@ -42,7 +42,7 @@ func TestLogrus(t *testing.T) {
var b bytes.Buffer
foo := bufio.NewWriter(&b)

logger := log.NewLogrus(log.LogrusWithLevel("debug"), log.LogrusWithWriter(foo), log.LogrusWithFormatter(&logrus.TextFormatter{
logger := logger.NewLogrus(logger.LogrusWithLevel("debug"), logger.LogrusWithWriter(foo), logger.LogrusWithFormatter(&logrus.TextFormatter{
DisableTimestamp: true,
}))
logger.Debug("current values", "day", 11, "month", "aug")
Expand All @@ -54,7 +54,7 @@ func TestLogrus(t *testing.T) {
var b bytes.Buffer
foo := bufio.NewWriter(&b)

logger := log.NewLogrus(log.LogrusWithLevel("info"), log.LogrusWithWriter(foo), log.LogrusWithFormatter(&logrus.TextFormatter{
logger := logger.NewLogrus(logger.LogrusWithLevel("info"), logger.LogrusWithWriter(foo), logger.LogrusWithFormatter(&logrus.TextFormatter{
DisableTimestamp: true,
}))
var err = fmt.Errorf("request failed")
Expand All @@ -66,7 +66,7 @@ func TestLogrus(t *testing.T) {
var b bytes.Buffer
foo := bufio.NewWriter(&b)

logger := log.NewLogrus(log.LogrusWithLevel("info"), log.LogrusWithWriter(foo), log.LogrusWithFormatter(&logrus.TextFormatter{
logger := logger.NewLogrus(logger.LogrusWithLevel("info"), logger.LogrusWithWriter(foo), logger.LogrusWithFormatter(&logrus.TextFormatter{
DisableTimestamp: true,
}))
var err = fmt.Errorf("request failed")
Expand Down
2 changes: 1 addition & 1 deletion log/noop.go → observability/logger/noop.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package log
package logger

import (
"io"
Expand Down
2 changes: 1 addition & 1 deletion log/zap.go → observability/logger/zap.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package log
package logger

import (
"context"
Expand Down
22 changes: 11 additions & 11 deletions log/zap_test.go → observability/logger/zap_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package log_test
package logger_test

import (
"bufio"
Expand All @@ -15,7 +15,7 @@ import (

"go.uber.org/zap"

"github.com/raystack/salt/log"
"github.com/raystack/salt/observability/logger"
)

type zapBufWriter struct {
Expand All @@ -41,7 +41,7 @@ func (m zapClock) NewTicker(duration time.Duration) *time.Ticker {
return time.NewTicker(duration)
}

func buildBufferedZapOption(writer io.Writer, t time.Time, bufWriterKey string) log.Option {
func buildBufferedZapOption(writer io.Writer, t time.Time, bufWriterKey string) logger.Option {
config := zap.NewDevelopmentConfig()
config.DisableCaller = true
// register mock writer
Expand All @@ -52,7 +52,7 @@ func buildBufferedZapOption(writer io.Writer, t time.Time, bufWriterKey string)
customPath := fmt.Sprintf("%s:", bufWriterKey)
config.OutputPaths = []string{customPath}

return log.ZapWithConfig(config, zap.WithClock(&zapClock{
return logger.ZapWithConfig(config, zap.WithClock(&zapClock{
t: t,
}))
}
Expand All @@ -64,7 +64,7 @@ func TestZap(t *testing.T) {
var b bytes.Buffer
bWriter := bufio.NewWriter(&b)

zapper := log.NewZap(buildBufferedZapOption(bWriter, mockedTime, randomString(10)))
zapper := logger.NewZap(buildBufferedZapOption(bWriter, mockedTime, randomString(10)))
zapper.Info("hello", "wor", "ld")
bWriter.Flush()

Expand All @@ -75,9 +75,9 @@ func TestZap(t *testing.T) {
var b bytes.Buffer
bWriter := bufio.NewWriter(&b)

zapper := log.NewZap(buildBufferedZapOption(bWriter, mockedTime, randomString(10)))
zapper := logger.NewZap(buildBufferedZapOption(bWriter, mockedTime, randomString(10)))
ctx := zapper.NewContext(context.Background())
contextualLog := log.ZapFromContext(ctx)
contextualLog := logger.ZapFromContext(ctx)
contextualLog.Info("hello", "wor", "ld")
bWriter.Flush()

Expand All @@ -88,11 +88,11 @@ func TestZap(t *testing.T) {
var b bytes.Buffer
bWriter := bufio.NewWriter(&b)

zapper := log.NewZap(buildBufferedZapOption(bWriter, mockedTime, randomString(10)))
zapper := logger.NewZap(buildBufferedZapOption(bWriter, mockedTime, randomString(10)))
ctx := zapper.NewContext(context.Background())
ctx = log.ZapContextWithFields(ctx, zap.Int("one", 1))
ctx = log.ZapContextWithFields(ctx, zap.String("two", "two"))
log.ZapFromContext(ctx).Info("hello", "wor", "ld")
ctx = logger.ZapContextWithFields(ctx, zap.Int("one", 1))
ctx = logger.ZapContextWithFields(ctx, zap.String("two", "two"))
logger.ZapFromContext(ctx).Info("hello", "wor", "ld")
bWriter.Flush()

assert.Equal(t, mockedTime.Format("2006-01-02T15:04:05.000Z0700")+"\tINFO\thello\t{\"one\": 1, \"two\": \"two\", \"wor\": \"ld\"}\n", b.String())
Expand Down
10 changes: 5 additions & 5 deletions telemetry/opentelemetry.go → observability/opentelemetry.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package telemetry
package observability

import (
"context"
"fmt"
"time"

"github.com/raystack/salt/log"
"github.com/raystack/salt/observability/logger"
"go.opentelemetry.io/contrib/instrumentation/host"
"go.opentelemetry.io/contrib/instrumentation/runtime"
"go.opentelemetry.io/contrib/samplers/probability/consistent"
Expand All @@ -29,7 +29,7 @@ type OpenTelemetryConfig struct {
VerboseResourceLabelsEnabled bool `yaml:"verbose_resource_labels_enabled" mapstructure:"verbose_resource_labels_enabled" default:"false"`
}

func initOTLP(ctx context.Context, cfg Config, logger log.Logger) (func(), error) {
func initOTLP(ctx context.Context, cfg Config, logger logger.Logger) (func(), error) {
if !cfg.OpenTelemetry.Enabled {
logger.Info("OpenTelemetry monitoring is disabled.")
return noOp, nil
Expand Down Expand Up @@ -78,7 +78,7 @@ func initOTLP(ctx context.Context, cfg Config, logger log.Logger) (func(), error
}
return shutdownProviders, nil
}
func initGlobalMetrics(ctx context.Context, res *resource.Resource, cfg OpenTelemetryConfig, logger log.Logger) (func(), error) {
func initGlobalMetrics(ctx context.Context, res *resource.Resource, cfg OpenTelemetryConfig, logger logger.Logger) (func(), error) {
exporter, err := otlpmetricgrpc.New(ctx,
otlpmetricgrpc.WithEndpoint(cfg.CollectorAddr),
otlpmetricgrpc.WithCompressor(gzip.Name),
Expand All @@ -98,7 +98,7 @@ func initGlobalMetrics(ctx context.Context, res *resource.Resource, cfg OpenTele
}
}, nil
}
func initGlobalTracer(ctx context.Context, res *resource.Resource, cfg OpenTelemetryConfig, logger log.Logger) (func(), error) {
func initGlobalTracer(ctx context.Context, res *resource.Resource, cfg OpenTelemetryConfig, logger logger.Logger) (func(), error) {
exporter, err := otlptrace.New(ctx, otlptracegrpc.NewClient(
otlptracegrpc.WithEndpoint(cfg.CollectorAddr),
otlptracegrpc.WithInsecure(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"strings"
"time"

"github.com/raystack/salt/utils"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
Expand All @@ -30,7 +29,7 @@ type Meter struct {
attributes []attribute.KeyValue
}
type MeterOpts struct {
meterName string `default:"github.com/raystack/salt/telemetry/otelgrpc"`
meterName string `default:"github.com/raystack/salt/observability/otelgrpc"`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify where NewMeter is used and whether WithMeterName is passed at call sites.
rg -nP --type=go '\bNewMeter\s*\('
rg -nP --type=go '\bWithMeterName\s*\('

Repository: raystack/salt

Length of output: 233


🏁 Script executed:

# First, let's read the otelgrpc.go file to understand the actual implementation
cat -n observability/otelgrpc/otelgrpc.go

Repository: raystack/salt

Length of output: 6255


🏁 Script executed:

# Find all call sites of NewMeter across the repository
rg -A2 '\bNewMeter\(' --type=go

Repository: raystack/salt

Length of output: 271


🏁 Script executed:

# Check if there's any struct tag processing (reflect, struct tag parsing) in the file
rg -n 'reflect|struct|tag|default' observability/otelgrpc/otelgrpc.go --type=go

Repository: raystack/salt

Length of output: 216


🏁 Script executed:

# Search for all NewMeter call sites across the entire repository
rg 'NewMeter' --type=go -B2 -A2

Repository: raystack/salt

Length of output: 755


🏁 Script executed:

# Check if there's any struct tag processing library imported or used
rg 'reflect|defaults|struct' observability/otelgrpc/otelgrpc.go -A2 -B2

Repository: raystack/salt

Length of output: 373


🏁 Script executed:

# Find test files related to otelgrpc
fd -e test.go -e _test.go observability

Repository: raystack/salt

Length of output: 39


🏁 Script executed:

# Check all imports in otelgrpc.go to confirm no struct tag processing library
head -20 observability/otelgrpc/otelgrpc.go

Repository: raystack/salt

Length of output: 429


🏁 Script executed:

# One final check: search for any usage of WithMeterName in tests or examples
rg 'WithMeterName' --type=go -B3 -A3

Repository: raystack/salt

Length of output: 443


Default meter name is not actually applied in NewMeter.

Line 32's struct tag default does not initialize meterOpts.meterName; NewMeter still starts with an empty value on Line 42, so otel.Meter(...) on Line 46 receives "" unless an option is passed. Go does not automatically process struct tag defaults—they are pure metadata.

Proposed fix
 type MeterOpts struct {
-	meterName string `default:"github.com/raystack/salt/observability/otelgrpc"`
+	meterName string
 }
+
+const defaultMeterName = "github.com/raystack/salt/observability/otelgrpc"
 
 func NewMeter(hostName string, opts ...Option) Meter {
-	meterOpts := &MeterOpts{}
+	meterOpts := &MeterOpts{meterName: defaultMeterName}
 	for _, opt := range opts {
 		opt(meterOpts)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@observability/otelgrpc/otelgrpc.go` at line 32, The struct tag on
meterOptions.meterName isn't applied automatically, so update NewMeter to use
the intended default when meterOpts.meterName is empty: detect when
meterOptions.meterName == "" inside NewMeter and set it to
"github.com/raystack/salt/observability/otelgrpc" before calling otel.Meter;
alternatively initialize meterOptions with that default in the meterOptions
creation path so otel.Meter never receives an empty string. Ensure references
are to meterOptions.meterName and the NewMeter function.

}
type Option func(*MeterOpts)

Expand Down Expand Up @@ -78,7 +77,7 @@ func (m *Meter) RecordUnary(ctx context.Context, p UnaryParams) {
resSize := GetProtoSize(p.Res)
attrs := make([]attribute.KeyValue, len(m.attributes))
copy(attrs, m.attributes)
attrs = append(attrs, attribute.String("rpc.grpc.status_text", utils.StatusText(p.Err)))
attrs = append(attrs, attribute.String("rpc.grpc.status_text", StatusText(p.Err)))
attrs = append(attrs, attribute.String("network.type", netTypeFromCtx(ctx)))
attrs = append(attrs, ParseFullMethod(p.Method)...)
m.duration.Record(ctx,
Expand All @@ -94,7 +93,7 @@ func (m *Meter) RecordUnary(ctx context.Context, p UnaryParams) {
func (m *Meter) RecordStream(ctx context.Context, start time.Time, method string, err error) {
attrs := make([]attribute.KeyValue, len(m.attributes))
copy(attrs, m.attributes)
attrs = append(attrs, attribute.String("rpc.grpc.status_text", utils.StatusText(err)))
attrs = append(attrs, attribute.String("rpc.grpc.status_text", StatusText(err)))
attrs = append(attrs, attribute.String("network.type", netTypeFromCtx(ctx)))
attrs = append(attrs, ParseFullMethod(method)...)
m.duration.Record(ctx,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"reflect"
"testing"

"github.com/raystack/salt/telemetry/otelgrpc"
"github.com/raystack/salt/observability/otelgrpc"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.20.0"
Expand Down
Loading
Loading