- Add edge node bootstrap and runtime configuration - Update observability with test coverage - Add hostsetup test and template updates - Add m-edge-local-dev-config-runtime task tracking
107 lines
2.9 KiB
Go
107 lines
2.9 KiB
Go
package observability
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
)
|
|
|
|
// NewLogger creates a zap production logger at the given level that writes to stderr.
|
|
func NewLogger(level string, pretty bool) (*zap.Logger, error) {
|
|
lvl, err := zapcore.ParseLevel(level)
|
|
if err != nil {
|
|
lvl = zapcore.InfoLevel
|
|
}
|
|
|
|
cfg := zap.NewProductionConfig()
|
|
cfg.Level = zap.NewAtomicLevelAt(lvl)
|
|
if !pretty {
|
|
return cfg.Build()
|
|
}
|
|
|
|
encoder := zapcore.NewJSONEncoder(cfg.EncoderConfig)
|
|
sink := &prettyJSONWriteSyncer{out: zapcore.Lock(zapcore.AddSync(os.Stderr))}
|
|
core := zapcore.NewCore(encoder, sink, cfg.Level)
|
|
return zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel)), nil
|
|
}
|
|
|
|
// NewLoggerWithFile creates a zap production logger at the given level. If path
|
|
// is empty it behaves exactly like NewLogger. When path is set, the log file's
|
|
// parent directory is created and log entries are written to that file
|
|
// instead of stderr.
|
|
func NewLoggerWithFile(level string, pretty bool, path string) (*zap.Logger, error) {
|
|
if path == "" {
|
|
return NewLogger(level, pretty)
|
|
}
|
|
|
|
lvl, err := zapcore.ParseLevel(level)
|
|
if err != nil {
|
|
lvl = zapcore.InfoLevel
|
|
}
|
|
|
|
if dir := filepath.Dir(path); dir != "" && dir != "." {
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
return nil, fmt.Errorf("mkdir log dir %s: %w", dir, err)
|
|
}
|
|
}
|
|
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open log file %s: %w", path, err)
|
|
}
|
|
|
|
cfg := zap.NewProductionConfig()
|
|
cfg.Level = zap.NewAtomicLevelAt(lvl)
|
|
|
|
var sink zapcore.WriteSyncer = zapcore.Lock(zapcore.AddSync(file))
|
|
if pretty {
|
|
sink = &prettyJSONWriteSyncer{out: sink}
|
|
}
|
|
encoder := zapcore.NewJSONEncoder(cfg.EncoderConfig)
|
|
core := zapcore.NewCore(encoder, sink, cfg.Level)
|
|
return zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel)), nil
|
|
}
|
|
|
|
type prettyJSONWriteSyncer struct {
|
|
out zapcore.WriteSyncer
|
|
}
|
|
|
|
func (w *prettyJSONWriteSyncer) Write(p []byte) (int, error) {
|
|
trimmed := bytes.TrimSpace(p)
|
|
if len(trimmed) == 0 {
|
|
return len(p), nil
|
|
}
|
|
|
|
var pretty bytes.Buffer
|
|
if err := json.Indent(&pretty, trimmed, "", " "); err != nil {
|
|
_, writeErr := w.out.Write(p)
|
|
return len(p), writeErr
|
|
}
|
|
pretty.WriteByte('\n')
|
|
|
|
_, err := w.out.Write(pretty.Bytes())
|
|
return len(p), err
|
|
}
|
|
|
|
func (w *prettyJSONWriteSyncer) Sync() error {
|
|
return w.out.Sync()
|
|
}
|
|
|
|
// ServeMetrics starts a Prometheus /metrics endpoint on the given port.
|
|
// It blocks until the server exits.
|
|
func ServeMetrics(port int) error {
|
|
mux := http.NewServeMux()
|
|
mux.Handle("/metrics", promhttp.Handler())
|
|
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write([]byte("ok"))
|
|
})
|
|
addr := fmt.Sprintf(":%d", port)
|
|
return http.ListenAndServe(addr, mux)
|
|
}
|