From 8e4d66a99834eee85834d08589cc44c671b30d02 Mon Sep 17 00:00:00 2001 From: Jay Brown Date: Mon, 10 Mar 2025 20:16:44 +0000 Subject: [PATCH] Merged in feature/versionTimestamps (pull request #98) add GetVersionTimestamp * add GetVersionTimestamp * docs --- cmd/metricsExample_test/main.go | 13 +++-- .../serviceconfig/observability/config.go | 44 ++++++++++++++++ .../observability/config_test.go | 50 +++++++++++++++++++ 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/cmd/metricsExample_test/main.go b/cmd/metricsExample_test/main.go index 050eb210..4831939d 100644 --- a/cmd/metricsExample_test/main.go +++ b/cmd/metricsExample_test/main.go @@ -20,10 +20,11 @@ import ( "math/rand" "os" "os/signal" - "runtime/debug" "syscall" "time" + "queryorchestration/internal/serviceconfig/observability" + "queryorchestration/internal/serviceconfig/observability/prometheus" ) @@ -53,12 +54,10 @@ func getMetricsConfig() prometheus.MetricsConfig { } func main() { - info, ok := debug.ReadBuildInfo() - if !ok { - fmt.Println("Failed to read build info") - return - } - fmt.Printf("Version: %s\n", info.Main.Version) + rawVersion := observability.GetVersion() + fmt.Printf("Version: %s\n", rawVersion) + timeVersion := observability.GetVersionTimestamp() + fmt.Printf("Version Timestamp: %s\n", timeVersion.String()) ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/internal/serviceconfig/observability/config.go b/internal/serviceconfig/observability/config.go index 17549151..43c3bfe3 100644 --- a/internal/serviceconfig/observability/config.go +++ b/internal/serviceconfig/observability/config.go @@ -3,6 +3,8 @@ package observability import ( "context" "log/slog" + "sync" + "time" "runtime/debug" @@ -17,6 +19,48 @@ const ( SystemName = "doczy" ) +var startupTime time.Time +var startupTimeOnce sync.Once + +func GetVersionTimestamp() time.Time { + startupTimeOnce.Do(func() { + info, ok := debug.ReadBuildInfo() + if !ok { + startupTime = time.Now() + return + } + + // Look for vcs.time in build settings + for _, setting := range info.Settings { + if setting.Key == "vcs.time" { + // Parse the build timestamp + buildTime, err := time.Parse(time.RFC3339, setting.Value) + if err == nil { + startupTime = buildTime + return + } + break + } + } + + // Return current time if no build time found + startupTime = time.Now() + }) + + return startupTime +} + +// GetVersion returns the version of the application in the Golang build info format. +// This is a raw format and will change depending on the build environment and the git tags. +// When building from a commit with an associated Git tag (e.g., v1.2.3), +// +// the runtime version uses the tag name without a timestamp. +// +// The "+dirty" suffix is appended when there are uncommitted changes +// +// present in the version control system (VCS) at the time of building. +// +// An example of a version v0.0.2-0.20250310175756-a91372dc0602+dirty. func GetVersion() string { info, ok := debug.ReadBuildInfo() if !ok { diff --git a/internal/serviceconfig/observability/config_test.go b/internal/serviceconfig/observability/config_test.go index eab37d10..05a9ccf5 100644 --- a/internal/serviceconfig/observability/config_test.go +++ b/internal/serviceconfig/observability/config_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "testing" + "time" "queryorchestration/internal/serviceconfig/observability" @@ -32,3 +33,52 @@ func TestSetOtel(t *testing.T) { } }() } + +func TestGetVersionTimestamp(t *testing.T) { + // Test 1: First call should set the timestamp + t1 := observability.GetVersionTimestamp() + if t1.IsZero() { + t.Error("GetVersionTimestamp returned zero time") + } + + // Test 2: Second call should return exactly the same time + time.Sleep(time.Millisecond * 100) // Small delay to ensure time would be different if not cached + t2 := observability.GetVersionTimestamp() + if !t1.Equal(t2) { + t.Errorf("Timestamps should be identical but got t1=%v, t2=%v", t1, t2) + } + + // Test 3: Even after a longer delay, should still return the same time + time.Sleep(time.Second) // Longer delay + t3 := observability.GetVersionTimestamp() + if !t1.Equal(t3) { + t.Errorf("Timestamp changed after delay: original=%v, new=%v", t1, t3) + } + + // Test 4: Returned time should not be in the future + if t1.After(time.Now()) { + t.Errorf("Timestamp is in the future: %v", t1) + } +} + +func TestGetVersion(t *testing.T) { + // Test 1: Basic call + version := observability.GetVersion() + if version == "" { + t.Error("Version should not be empty") + } + + // Test 2: Should either be "unknown" or follow semver-like pattern + if version != "unknown" { + // This is a loose check - you might want to make it more strict + if len(version) < 3 { + t.Errorf("Version '%s' seems too short to be valid", version) + } + } + + // Test 3: Multiple calls should be consistent + version2 := observability.GetVersion() + if version != version2 { + t.Errorf("Version not consistent between calls: '%s' != '%s'", version, version2) + } +}