Skip to content
This repository was archived by the owner on Sep 11, 2025. It is now read-only.

Commit 72bb2d4

Browse files
feat: update health endpoint (#924)
1 parent e865437 commit 72bb2d4

File tree

5 files changed

+82
-54
lines changed

5 files changed

+82
-54
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ NOTE: all releases may include dependency updates, not specifically mentioned
88

99
- feat: integrate try-as library [#912](https://github.com/hypermodeinc/modus/pull/912)
1010
- fix: check topic actor status before publishing events [#918](https://github.com/hypermodeinc/modus/pull/918)
11+
- feat: update health endpoint [#924](https://github.com/hypermodeinc/modus/pull/924)
1112

1213
## 2025-06-25 - Runtime v0.18.2
1314

runtime/actors/agents.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -323,27 +323,3 @@ func ListActiveAgents(ctx context.Context) ([]AgentInfo, error) {
323323

324324
return results, nil
325325
}
326-
327-
func ListLocalAgents(ctx context.Context) []AgentInfo {
328-
if _actorSystem == nil {
329-
return nil
330-
}
331-
332-
span, _ := utils.NewSentrySpanForCurrentFunc(ctx)
333-
defer span.Finish()
334-
335-
actors := _actorSystem.Actors()
336-
results := make([]AgentInfo, 0, len(actors))
337-
338-
for _, pid := range actors {
339-
if actor, ok := pid.Actor().(*wasmAgentActor); ok {
340-
results = append(results, AgentInfo{
341-
Id: actor.agentId,
342-
Name: actor.agentName,
343-
Status: actor.status,
344-
})
345-
}
346-
}
347-
348-
return results
349-
}

runtime/app/app.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,15 @@ func ModusHomeDir() string {
9797
}
9898

9999
func KubernetesNamespace() (string, bool) {
100+
return kubernetesNamespace()
101+
}
102+
103+
var kubernetesNamespace = sync.OnceValues(func() (string, bool) {
100104
if ns := os.Getenv("NAMESPACE"); ns != "" {
101105
return ns, true
102106
}
103107
if data, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil {
104108
return strings.TrimSpace(string(data)), true
105109
}
106110
return "", false
107-
}
111+
})

runtime/httpserver/health.go

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,34 @@
1010
package httpserver
1111

1212
import (
13-
"encoding/json"
14-
"fmt"
1513
"net/http"
14+
"runtime"
1615

17-
"github.com/hypermodeinc/modus/runtime/actors"
1816
"github.com/hypermodeinc/modus/runtime/app"
17+
"github.com/hypermodeinc/modus/runtime/logger"
18+
"github.com/hypermodeinc/modus/runtime/utils"
1919
)
2020

2121
var healthHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
22-
env := app.Config().Environment()
23-
ver := app.VersionNumber()
24-
agents := actors.ListLocalAgents(r.Context())
2522

26-
w.Header().Set("Content-Type", "application/json")
27-
w.WriteHeader(http.StatusOK)
28-
29-
// custom format the JSON response for easy readability
30-
_, _ = w.Write([]byte(`{
31-
"status": "ok",
32-
"environment": "` + env + `",
33-
"version": "` + ver + `",
34-
`))
35-
36-
if len(agents) == 0 {
37-
_, _ = w.Write([]byte(` "agents": []` + "\n"))
38-
} else {
39-
_, _ = w.Write([]byte(` "agents": [` + "\n"))
40-
for i, agent := range agents {
41-
if i > 0 {
42-
_, _ = w.Write([]byte(",\n"))
43-
}
44-
name, _ := json.Marshal(agent.Name)
45-
_, _ = w.Write(fmt.Appendf(nil, ` {"id": "%s", "name": %s, "status": "%s"}`, agent.Id, name, agent.Status))
46-
}
47-
_, _ = w.Write([]byte("\n ]\n"))
23+
data := []utils.KeyValuePair{
24+
{Key: "status", Value: "ok"},
25+
{Key: "environment", Value: app.Config().Environment()},
26+
{Key: "app_version", Value: app.VersionNumber()},
27+
{Key: "go_version", Value: runtime.Version()},
28+
}
29+
if ns, ok := app.KubernetesNamespace(); ok {
30+
data = append(data, utils.KeyValuePair{Key: "kubernetes_namespace", Value: ns})
4831
}
4932

50-
_, _ = w.Write([]byte("}\n"))
33+
jsonBytes, err := utils.MakeJsonObject(data, true)
34+
if err != nil {
35+
logger.Err(r.Context(), err).Msg("Failed to serialize health check response.")
36+
w.WriteHeader(http.StatusInternalServerError)
37+
return
38+
}
5139

40+
w.Header().Set("Content-Type", "application/json")
41+
w.WriteHeader(http.StatusOK)
42+
_, _ = w.Write(jsonBytes)
5243
})

runtime/utils/json.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,59 @@ func JsonDeserialize(data []byte, v any) error {
3131
dec.UseNumber()
3232
return dec.Decode(v)
3333
}
34+
35+
type KeyValuePair struct {
36+
Key string
37+
Value any
38+
}
39+
40+
// MakeJsonObject creates a JSON object from the given key-value pairs.
41+
func MakeJsonObject(pairs []KeyValuePair, pretty bool) ([]byte, error) {
42+
var buf bytes.Buffer
43+
44+
if pretty {
45+
buf.WriteString("{\n")
46+
for i, kv := range pairs {
47+
keyBytes, err := json.Marshal(kv.Key)
48+
if err != nil {
49+
return nil, err
50+
}
51+
valBytes, err := json.Marshal(kv.Value)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
buf.WriteString(" ")
57+
buf.Write(keyBytes)
58+
buf.WriteString(": ")
59+
buf.Write(valBytes)
60+
if i < len(pairs)-1 {
61+
buf.WriteByte(',')
62+
}
63+
buf.WriteByte('\n')
64+
}
65+
buf.WriteString("}\n")
66+
} else {
67+
buf.WriteByte('{')
68+
for i, kv := range pairs {
69+
keyBytes, err := json.Marshal(kv.Key)
70+
if err != nil {
71+
return nil, err
72+
}
73+
valBytes, err := json.Marshal(kv.Value)
74+
if err != nil {
75+
return nil, err
76+
}
77+
78+
buf.Write(keyBytes)
79+
buf.WriteByte(':')
80+
buf.Write(valBytes)
81+
if i < len(pairs)-1 {
82+
buf.WriteByte(',')
83+
}
84+
}
85+
buf.WriteByte('}')
86+
}
87+
88+
return buf.Bytes(), nil
89+
}

0 commit comments

Comments
 (0)