Skip to content

Commit 4da5bcd

Browse files
committed
[health] improve health reporting
1 parent 945731b commit 4da5bcd

File tree

14 files changed

+326
-214
lines changed

14 files changed

+326
-214
lines changed

api/requests.http

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44
@phone={{$dotenv PHONE}}
55

66
###
7-
GET {{baseUrl}}/health HTTP/1.1
7+
GET {{baseUrl}}/3rdparty/v1/health HTTP/1.1
88

99
###
10-
GET {{baseUrl}}/3rdparty/v1/health HTTP/1.1
10+
GET {{baseUrl}}/3rdparty/v1/health/startup HTTP/1.1
11+
12+
###
13+
GET {{baseUrl}}/3rdparty/v1/health/ready HTTP/1.1
14+
15+
###
16+
GET {{baseUrl}}/3rdparty/v1/health/live HTTP/1.1
1117

1218
###
1319
POST {{baseUrl}}/3rdparty/v1/messages?skipPhoneValidation=false&deviceActiveWithin=240 HTTP/1.1
@@ -189,3 +195,12 @@ Content-Type: application/json
189195
###
190196
GET http://localhost:3000/metrics HTTP/1.1
191197

198+
###
199+
GET http://localhost:3000/health/startup HTTP/1.1
200+
201+
###
202+
GET http://localhost:3000/health/ready HTTP/1.1
203+
204+
###
205+
GET http://localhost:3000/health/live HTTP/1.1
206+

build/package/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,6 @@ USER guest
4848
ENTRYPOINT ["/docker-entrypoint.sh"]
4949

5050
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
51-
CMD curl -fs http://localhost:3000/health
51+
CMD curl -fs http://localhost:3000/health/live
5252

5353
CMD [ "/app/app" ]

deployments/helm-chart/templates/deployment.yaml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,21 +92,31 @@ spec:
9292
resources:
9393
{{- toYaml .Values.resources | nindent 12 }}
9494
livenessProbe:
95+
httpGet:
96+
path: /health/live
97+
port: http
9598
initialDelaySeconds: 30
9699
periodSeconds: 10
97100
timeoutSeconds: 5
98101
failureThreshold: 3
102+
103+
readinessProbe:
99104
httpGet:
100-
path: /health
105+
path: /health/ready
101106
port: http
102-
readinessProbe:
103107
initialDelaySeconds: 10
104108
periodSeconds: 5
105109
timeoutSeconds: 3
106110
failureThreshold: 3
111+
112+
startupProbe:
107113
httpGet:
108-
path: /health
114+
path: /health/startup
109115
port: http
116+
initialDelaySeconds: 60
117+
periodSeconds: 10
118+
timeoutSeconds: 5
119+
failureThreshold: 6
110120
{{- if .Values.gateway.config.enabled }}
111121
volumeMounts:
112122
- name: config-volume

internal/sms-gateway/app.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ var Module = fx.Module(
3737
"server",
3838
logger.Module,
3939
appconfig.Module,
40-
appdb.Module,
40+
appdb.Module(),
4141
http.Module,
4242
validator.Module,
4343
openapi.Module(),
@@ -49,7 +49,7 @@ var Module = fx.Module(
4949
pubsub.Module(),
5050
events.Module,
5151
messages.Module(),
52-
health.Module,
52+
health.Module(),
5353
webhooks.Module,
5454
settings.Module,
5555
devices.Module,

internal/sms-gateway/handlers/health.go

Lines changed: 72 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,75 +5,102 @@ import (
55
"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/base"
66
"github.com/android-sms-gateway/server/internal/sms-gateway/modules/health"
77
"github.com/android-sms-gateway/server/internal/version"
8-
"github.com/capcom6/go-helpers/maps"
8+
"github.com/go-playground/validator/v10"
99
"github.com/gofiber/fiber/v2"
10-
"go.uber.org/fx"
10+
"github.com/samber/lo"
1111
"go.uber.org/zap"
1212
)
1313

14-
type healthHanlderParams struct {
15-
fx.In
16-
17-
HealthSvc *health.Service
18-
19-
Logger *zap.Logger
20-
}
21-
2214
type healthHandler struct {
2315
base.Handler
2416

2517
healthSvc *health.Service
18+
}
2619

27-
logger *zap.Logger
20+
func newHealthHandler(
21+
healthSvc *health.Service,
22+
logger *zap.Logger,
23+
validator *validator.Validate,
24+
) *healthHandler {
25+
return &healthHandler{
26+
Handler: base.Handler{
27+
Logger: logger,
28+
Validator: validator,
29+
},
30+
healthSvc: healthSvc,
31+
}
2832
}
2933

30-
// @Summary Health check
31-
// @Description Checks if service is healthy
34+
// @Summary Liveness probe
35+
// @Description Checks if service is running (liveness probe)
3236
// @Tags System
3337
// @Produce json
34-
// @Success 200 {object} smsgateway.HealthResponse "Health check result"
35-
// @Failure 500 {object} smsgateway.HealthResponse "Service is unhealthy"
36-
// @Router /3rdparty/v1/health [get]
38+
// @Success 200 {object} smsgateway.HealthResponse "Service is alive"
39+
// @Failure 503 {object} smsgateway.HealthResponse "Service is not alive"
40+
// @Router /health/live [get]
41+
//
42+
// Liveness probe
43+
func (h *healthHandler) getLiveness(c *fiber.Ctx) error {
44+
return writeProbe(c, h.healthSvc.CheckLiveness(c.Context()))
45+
}
46+
47+
// @Summary Readiness probe
48+
// @Description Checks if service is ready to serve traffic (readiness probe)
49+
// @Tags System
50+
// @Produce json
51+
// @Success 200 {object} smsgateway.HealthResponse "Service is ready"
52+
// @Failure 503 {object} smsgateway.HealthResponse "Service is not ready"
53+
// @Router /health/ready [get]
54+
// @Router /3rdparty/v1/health [get]
3755
//
38-
// Health check
39-
func (h *healthHandler) getHealth(c *fiber.Ctx) error {
40-
check, err := h.healthSvc.HealthCheck(c.Context())
41-
if err != nil {
42-
return err
56+
// Readiness probe
57+
func (h *healthHandler) getReadiness(c *fiber.Ctx) error {
58+
return writeProbe(c, h.healthSvc.CheckReadiness(c.Context()))
59+
}
60+
61+
// @Summary Startup probe
62+
// @Description Checks if service has completed initialization (startup probe)
63+
// @Tags System
64+
// @Produce json
65+
// @Success 200 {object} smsgateway.HealthResponse "Service has completed initialization"
66+
// @Failure 503 {object} smsgateway.HealthResponse "Service has not completed initialization"
67+
// @Router /health/startup [get]
68+
//
69+
// Startup probe
70+
func (h *healthHandler) getStartup(c *fiber.Ctx) error {
71+
return writeProbe(c, h.healthSvc.CheckStartup(c.Context()))
72+
}
73+
74+
func writeProbe(c *fiber.Ctx, r health.CheckResult) error {
75+
status := fiber.StatusOK
76+
if r.Status() == health.StatusFail {
77+
status = fiber.StatusServiceUnavailable
4378
}
79+
return c.Status(status).JSON(makeResponse(r))
80+
}
4481

45-
res := smsgateway.HealthResponse{
46-
Status: smsgateway.HealthStatus(check.Status),
82+
func makeResponse(result health.CheckResult) smsgateway.HealthResponse {
83+
return smsgateway.HealthResponse{
84+
Status: smsgateway.HealthStatus(result.Status()),
4785
Version: version.AppVersion,
4886
ReleaseID: version.AppReleaseID(),
49-
Checks: maps.MapValues(
50-
check.Checks,
51-
func(c health.CheckDetail) smsgateway.HealthCheck {
87+
Checks: lo.MapValues(
88+
result.Checks,
89+
func(value health.CheckDetail, key string) smsgateway.HealthCheck {
5290
return smsgateway.HealthCheck{
53-
Description: c.Description,
54-
ObservedUnit: c.ObservedUnit,
55-
ObservedValue: c.ObservedValue,
56-
Status: smsgateway.HealthStatus(c.Status),
91+
Description: value.Description,
92+
ObservedUnit: value.ObservedUnit,
93+
ObservedValue: value.ObservedValue,
94+
Status: smsgateway.HealthStatus(value.Status),
5795
}
5896
},
5997
),
6098
}
61-
62-
if check.Status == health.StatusFail {
63-
return c.Status(fiber.StatusInternalServerError).JSON(res)
64-
}
65-
66-
return c.Status(fiber.StatusOK).JSON(res)
6799
}
68100

69101
func (h *healthHandler) Register(router fiber.Router) {
70-
router.Get("/health", h.getHealth)
71-
}
72-
73-
func newHealthHandler(params healthHanlderParams) *healthHandler {
74-
return &healthHandler{
75-
Handler: base.Handler{Logger: params.Logger.Named("HealthHandler"), Validator: nil},
76-
healthSvc: params.HealthSvc,
77-
logger: params.Logger,
78-
}
102+
router.Get("/health", h.getReadiness)
103+
router.Get("/health/live", h.getLiveness)
104+
router.Get("/health/ready", h.getReadiness)
105+
router.Get("/health/startup", h.getStartup)
79106
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package db
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"sync/atomic"
7+
8+
healthmod "github.com/android-sms-gateway/server/internal/sms-gateway/modules/health"
9+
)
10+
11+
type health struct {
12+
db *sql.DB
13+
14+
failedPings atomic.Int64
15+
}
16+
17+
func newHealth(db *sql.DB) *health {
18+
return &health{
19+
db: db,
20+
}
21+
}
22+
23+
// Name implements HealthProvider.
24+
func (h *health) Name() string {
25+
return "db"
26+
}
27+
28+
// LiveProbe implements HealthProvider.
29+
func (h *health) LiveProbe(ctx context.Context) (healthmod.Checks, error) {
30+
return nil, nil
31+
}
32+
33+
// ReadyProbe implements HealthProvider.
34+
func (h *health) ReadyProbe(ctx context.Context) (healthmod.Checks, error) {
35+
pingCheck := healthmod.CheckDetail{
36+
Description: "Database ping",
37+
ObservedUnit: "failed pings",
38+
ObservedValue: 0,
39+
Status: healthmod.StatusPass,
40+
}
41+
42+
if err := h.db.PingContext(ctx); err != nil {
43+
h.failedPings.Add(1)
44+
45+
pingCheck.Status = healthmod.StatusFail
46+
} else {
47+
h.failedPings.Store(0)
48+
}
49+
50+
pingCheck.ObservedValue = int(h.failedPings.Load())
51+
52+
return healthmod.Checks{"ping": pingCheck}, nil
53+
}
54+
55+
// StartedProbe implements HealthProvider.
56+
func (h *health) StartedProbe(ctx context.Context) (healthmod.Checks, error) {
57+
return nil, nil
58+
}
59+
60+
var _ healthmod.HealthProvider = (*health)(nil)

internal/sms-gateway/modules/db/module.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,20 @@ package db
33
import (
44
"github.com/jaevor/go-nanoid"
55
"go.uber.org/fx"
6+
7+
healthmod "github.com/android-sms-gateway/server/internal/sms-gateway/modules/health"
68
)
79

810
type IDGen func() string
911

10-
var Module = fx.Module(
11-
"db",
12-
fx.Provide(func() (IDGen, error) {
13-
return nanoid.Standard(21)
14-
}),
15-
)
12+
func Module() fx.Option {
13+
return fx.Module(
14+
"db",
15+
fx.Provide(
16+
healthmod.AsHealthProvider(newHealth),
17+
),
18+
fx.Provide(func() (IDGen, error) {
19+
return nanoid.Standard(21)
20+
}),
21+
)
22+
}

internal/sms-gateway/modules/health/cli.go

Lines changed: 0 additions & 49 deletions
This file was deleted.

0 commit comments

Comments
 (0)