Skip to content

Commit 72f6583

Browse files
authored
Merge pull request #2348 from gofr-dev/fix/gofr_telemetry
2 parents 18fcdf6 + 7e80da4 commit 72f6583

File tree

3 files changed

+188
-0
lines changed

3 files changed

+188
-0
lines changed

pkg/gofr/container/container.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ func (c *Container) Create(conf config.Config) {
116116

117117
c.metricsManager = metrics.NewMetricsManager(exporters.Prometheus(c.GetAppName(), c.GetAppVersion()), c.Logger)
118118

119+
exporters.SendFrameworkStartupTelemetry(c.GetAppName(), c.GetAppVersion())
120+
119121
// Register framework metrics
120122
c.registerFrameworkMetrics()
121123

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package exporters
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"net/http"
8+
"os"
9+
"runtime"
10+
"time"
11+
12+
"github.com/google/uuid"
13+
14+
"gofr.dev/pkg/gofr/version"
15+
)
16+
17+
const (
18+
defaultTelemetryEndpoint = "https://gofr.dev/telemetry/v1/metrics"
19+
defaultAppName = "gofr-app"
20+
requestTimeout = 10 * time.Second
21+
)
22+
23+
// TelemetryData represents the JSON telemetry payload.
24+
type TelemetryData struct {
25+
Timestamp string `json:"timestamp"`
26+
EventID string `json:"event_id"`
27+
Source string `json:"source"`
28+
ServiceName string `json:"service_name,omitempty"`
29+
ServiceVersion string `json:"service_version,omitempty"`
30+
RawDataSize int `json:"raw_data_size"`
31+
FrameworkVersion string `json:"framework_version,omitempty"`
32+
GoVersion string `json:"go_version,omitempty"`
33+
OS string `json:"os,omitempty"`
34+
Architecture string `json:"architecture,omitempty"`
35+
StartupTime string `json:"startup_time,omitempty"`
36+
}
37+
38+
// SendFrameworkStartupTelemetry sends telemetry data.
39+
func SendFrameworkStartupTelemetry(appName, appVersion string) {
40+
if os.Getenv("GOFR_TELEMETRY") == "false" {
41+
return
42+
}
43+
44+
go sendTelemetryData(appName, appVersion)
45+
}
46+
47+
func sendTelemetryData(appName, appVersion string) {
48+
if appName == "" {
49+
appName = defaultAppName
50+
}
51+
52+
if appVersion == "" {
53+
appVersion = "unknown"
54+
}
55+
56+
now := time.Now().UTC()
57+
58+
data := TelemetryData{
59+
Timestamp: now.Format(time.RFC3339),
60+
EventID: uuid.New().String(),
61+
Source: "gofr-framework",
62+
ServiceName: appName,
63+
ServiceVersion: appVersion,
64+
RawDataSize: 0,
65+
FrameworkVersion: version.Framework,
66+
GoVersion: runtime.Version(),
67+
OS: runtime.GOOS,
68+
Architecture: runtime.GOARCH,
69+
StartupTime: now.Format(time.RFC3339),
70+
}
71+
72+
sendToEndpoint(&data, defaultTelemetryEndpoint)
73+
}
74+
75+
func sendToEndpoint(data *TelemetryData, endpoint string) {
76+
jsonData, err := json.Marshal(data)
77+
if err != nil {
78+
return
79+
}
80+
81+
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
82+
defer cancel()
83+
84+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(jsonData))
85+
if err != nil {
86+
return
87+
}
88+
89+
req.Header.Set("Content-Type", "application/json")
90+
91+
client := &http.Client{Timeout: 10 * time.Second}
92+
93+
resp, err := client.Do(req)
94+
if err != nil {
95+
return
96+
}
97+
98+
resp.Body.Close()
99+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package exporters
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"net/http"
7+
"net/http/httptest"
8+
"runtime"
9+
"testing"
10+
"time"
11+
12+
"github.com/google/uuid"
13+
"github.com/stretchr/testify/assert"
14+
15+
"gofr.dev/pkg/gofr/version"
16+
)
17+
18+
func TestSendFrameworkStartupTelemetry_Disabled(t *testing.T) {
19+
t.Setenv("GOFR_TELEMETRY", "true")
20+
21+
requestMade := false
22+
23+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
24+
requestMade = true
25+
26+
w.WriteHeader(http.StatusOK)
27+
}))
28+
defer server.Close()
29+
30+
SendFrameworkStartupTelemetry("test-app", "1.0.0")
31+
time.Sleep(100 * time.Millisecond)
32+
33+
assert.False(t, requestMade, "Expected no telemetry when disabled")
34+
}
35+
36+
func TestSendFrameworkStartupTelemetry_DefaultValues(t *testing.T) {
37+
var receivedData TelemetryData
38+
39+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
40+
body, err := io.ReadAll(r.Body)
41+
assert.NoError(t, err)
42+
43+
err = json.Unmarshal(body, &receivedData)
44+
assert.NoError(t, err)
45+
46+
w.WriteHeader(http.StatusOK)
47+
}))
48+
defer server.Close()
49+
50+
// Test with empty values to verify defaults.
51+
testSendTelemetryData("", "", server.URL)
52+
time.Sleep(100 * time.Millisecond)
53+
54+
assert.Equal(t, defaultAppName, receivedData.ServiceName)
55+
assert.Equal(t, "unknown", receivedData.ServiceVersion)
56+
assert.Equal(t, "gofr-framework", receivedData.Source)
57+
assert.Equal(t, 0, receivedData.RawDataSize)
58+
}
59+
60+
// Helper function that replicates sendTelemetryData but with configurable endpoint.
61+
func testSendTelemetryData(appName, appVersion, endpoint string) {
62+
if appName == "" {
63+
appName = defaultAppName
64+
}
65+
66+
if appVersion == "" {
67+
appVersion = "unknown"
68+
}
69+
70+
now := time.Now().UTC()
71+
72+
data := TelemetryData{
73+
Timestamp: now.Format(time.RFC3339),
74+
EventID: uuid.New().String(),
75+
Source: "gofr-framework",
76+
ServiceName: appName,
77+
ServiceVersion: appVersion,
78+
RawDataSize: 0,
79+
FrameworkVersion: version.Framework,
80+
GoVersion: runtime.Version(),
81+
OS: runtime.GOOS,
82+
Architecture: runtime.GOARCH,
83+
StartupTime: now.Format(time.RFC3339),
84+
}
85+
86+
sendToEndpoint(&data, endpoint)
87+
}

0 commit comments

Comments
 (0)