Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add dev server and health (#2665)
* feat: add dev server and health * fix: fix ci * fix: fix comment. * feat: add enabled * remove no need test * feat: mv devServer to internal * feat: default enable pprof Co-authored-by: dylan.wang <dylan.wang@yijinin.com>
- Loading branch information
Showing
9 changed files
with
409 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package devserver | ||
|
||
// Config is config for inner http server. | ||
type Config struct { | ||
Enabled bool `json:",default=true"` | ||
Host string `json:",optional"` | ||
Port int `json:",default=6470"` | ||
MetricsPath string `json:",default=/metrics"` | ||
HealthPath string `json:",default=/healthz"` | ||
EnableMetrics bool `json:",default=true"` | ||
EnablePprof bool `json:",default=true"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package devserver | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"net/http/pprof" | ||
"sync" | ||
|
||
"github.com/felixge/fgprof" | ||
"github.com/prometheus/client_golang/prometheus/promhttp" | ||
|
||
"github.com/zeromicro/go-zero/core/logx" | ||
"github.com/zeromicro/go-zero/core/threading" | ||
"github.com/zeromicro/go-zero/internal/health" | ||
) | ||
|
||
var ( | ||
once sync.Once | ||
) | ||
|
||
// Server is inner http server, expose some useful observability information of app. | ||
// For example health check, metrics and pprof. | ||
type Server struct { | ||
config *Config | ||
server *http.ServeMux | ||
routes []string | ||
} | ||
|
||
// NewServer returns a new inner http Server. | ||
func NewServer(config *Config) *Server { | ||
return &Server{ | ||
config: config, | ||
server: http.NewServeMux(), | ||
} | ||
} | ||
|
||
func (s *Server) addRoutes() { | ||
// route path, routes list | ||
s.handleFunc("/", func(w http.ResponseWriter, _ *http.Request) { | ||
_ = json.NewEncoder(w).Encode(s.routes) | ||
}) | ||
// health | ||
s.handleFunc(s.config.HealthPath, health.CreateHttpHandler()) | ||
|
||
// metrics | ||
if s.config.EnableMetrics { | ||
s.handleFunc(s.config.MetricsPath, promhttp.Handler().ServeHTTP) | ||
} | ||
// pprof | ||
if s.config.EnablePprof { | ||
s.handleFunc("/debug/fgprof", fgprof.Handler().(http.HandlerFunc)) | ||
s.handleFunc("/debug/pprof/", pprof.Index) | ||
s.handleFunc("/debug/pprof/cmdline", pprof.Cmdline) | ||
s.handleFunc("/debug/pprof/profile", pprof.Profile) | ||
s.handleFunc("/debug/pprof/symbol", pprof.Symbol) | ||
s.handleFunc("/debug/pprof/trace", pprof.Trace) | ||
} | ||
} | ||
|
||
func (s *Server) handleFunc(pattern string, handler http.HandlerFunc) { | ||
s.server.HandleFunc(pattern, handler) | ||
s.routes = append(s.routes, pattern) | ||
} | ||
|
||
// StartAsync start inner http server background. | ||
func (s *Server) StartAsync() { | ||
s.addRoutes() | ||
threading.GoSafe(func() { | ||
addr := fmt.Sprintf("%s:%d", s.config.Host, s.config.Port) | ||
logx.Infof("Starting inner http server at %s", addr) | ||
if err := http.ListenAndServe(addr, s.server); err != nil { | ||
logx.Error(err) | ||
} | ||
}) | ||
} | ||
|
||
// StartAgent start inner http server by config. | ||
func StartAgent(c Config) { | ||
once.Do(func() { | ||
if c.Enabled { | ||
s := NewServer(&c) | ||
s.StartAsync() | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
package health | ||
|
||
import ( | ||
"net/http" | ||
"sync" | ||
|
||
"github.com/zeromicro/go-zero/core/syncx" | ||
) | ||
|
||
// defaultHealthManager is global comboHealthManager for byone self. | ||
var defaultHealthManager = newComboHealthManager() | ||
|
||
type ( | ||
// Probe represents readiness status of given component. | ||
Probe interface { | ||
// MarkReady sets a ready state for the endpoint handlers. | ||
MarkReady() | ||
// MarkNotReady sets a not ready state for the endpoint handlers. | ||
MarkNotReady() | ||
// IsReady return inner state for the component. | ||
IsReady() bool | ||
// Name return probe name identifier | ||
Name() string | ||
} | ||
|
||
// healthManager manage app healthy. | ||
healthManager struct { | ||
ready syncx.AtomicBool | ||
name string | ||
} | ||
|
||
// comboHealthManager folds given probes into one, reflects their statuses in a thread-safe way. | ||
comboHealthManager struct { | ||
mu sync.Mutex | ||
probes []Probe | ||
} | ||
) | ||
|
||
// AddProbe add components probe to global comboHealthManager. | ||
func AddProbe(probe Probe) { | ||
defaultHealthManager.addProbe(probe) | ||
} | ||
|
||
// NewHealthManager returns a new healthManager. | ||
func NewHealthManager(name string) Probe { | ||
return &healthManager{ | ||
name: name, | ||
} | ||
} | ||
|
||
// MarkReady sets a ready state for the endpoint handlers. | ||
func (h *healthManager) MarkReady() { | ||
h.ready.Set(true) | ||
} | ||
|
||
// MarkNotReady sets a not ready state for the endpoint handlers. | ||
func (h *healthManager) MarkNotReady() { | ||
h.ready.Set(false) | ||
} | ||
|
||
// IsReady return inner state for the component. | ||
func (h *healthManager) IsReady() bool { | ||
return h.ready.True() | ||
} | ||
|
||
// Name return probe name identifier | ||
func (h *healthManager) Name() string { | ||
return h.name | ||
} | ||
|
||
func newComboHealthManager() *comboHealthManager { | ||
return &comboHealthManager{} | ||
} | ||
|
||
// MarkReady sets components status to ready. | ||
func (p *comboHealthManager) MarkReady() { | ||
p.mu.Lock() | ||
defer p.mu.Unlock() | ||
|
||
for _, probe := range p.probes { | ||
probe.MarkReady() | ||
} | ||
} | ||
|
||
// MarkNotReady sets components status to not ready with given error as a cause. | ||
func (p *comboHealthManager) MarkNotReady() { | ||
p.mu.Lock() | ||
defer p.mu.Unlock() | ||
|
||
for _, probe := range p.probes { | ||
probe.MarkNotReady() | ||
} | ||
} | ||
|
||
// IsReady return composed status of all components. | ||
func (p *comboHealthManager) IsReady() bool { | ||
p.mu.Lock() | ||
defer p.mu.Unlock() | ||
|
||
for _, probe := range p.probes { | ||
if !probe.IsReady() { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func (p *comboHealthManager) verboseInfo() string { | ||
p.mu.Lock() | ||
defer p.mu.Unlock() | ||
|
||
var info string | ||
for _, probe := range p.probes { | ||
if probe.IsReady() { | ||
info += probe.Name() + " is ready; \n" | ||
} else { | ||
info += probe.Name() + " is not ready; \n" | ||
} | ||
} | ||
return info | ||
} | ||
|
||
// addProbe add components probe to comboHealthManager. | ||
func (p *comboHealthManager) addProbe(probe Probe) { | ||
p.mu.Lock() | ||
defer p.mu.Unlock() | ||
|
||
p.probes = append(p.probes, probe) | ||
} | ||
|
||
// CreateHttpHandler create health http handler base on given probe. | ||
func CreateHttpHandler() http.HandlerFunc { | ||
return func(w http.ResponseWriter, request *http.Request) { | ||
if defaultHealthManager.IsReady() { | ||
_, _ = w.Write([]byte("OK")) | ||
} else { | ||
http.Error(w, "Service Unavailable\n"+defaultHealthManager.verboseInfo(), http.StatusServiceUnavailable) | ||
} | ||
} | ||
} |
Oops, something went wrong.