From dd77671f86f72bf07023fbfa09a27fe6eedeea8b Mon Sep 17 00:00:00 2001 From: caihaomin Date: Thu, 30 Sep 2021 15:42:29 +0800 Subject: [PATCH] add tinylog to print log Signed-off-by: caihaomin --- pkg/config/config.go | 82 ++++++++++ pkg/constant/constant.go | 2 + pkg/httpserver/server.go | 8 + pkg/qos/qos.go | 4 + pkg/rubik/rubik.go | 26 ++- pkg/tinylog/tinylog.go | 306 +++++++++++++++++++++++++++++++++++ pkg/workerpool/workerpool.go | 5 +- rubik.go | 3 +- 8 files changed, 429 insertions(+), 7 deletions(-) create mode 100644 pkg/config/config.go create mode 100644 pkg/tinylog/tinylog.go diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..5fea26e --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,82 @@ +// Copyright (c) Huawei Technologies Co., Ltd. 2021. All rights reserved. +// rubik licensed under the Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. +// Author: Haomin Tsai +// Create: 2021-09-28 +// Description: config load + +package config + +import ( + "bytes" + "encoding/json" + "path/filepath" + + "isula.org/rubik/pkg/constant" + "isula.org/rubik/pkg/util" +) + +var ( + // CgroupRoot is cgroup mount point + CgroupRoot = constant.DefaultCgroupRoot +) + +// Config defines the configuration for rubik +type Config struct { + LogDriver string `json:"logDriver,omitempty"` + LogDir string `json:"logDir,omitempty"` + LogSize int `json:"logSize,omitempty"` + LogLevel string `json:"logLevel,omitempty"` + CgroupRoot string `json:"cgroupRoot,omitempty"` +} + +// NewConfig returns new config load from config file +func NewConfig(path string) (*Config, error) { + if path == "" { + path = constant.ConfigFile + } + + defaultLogSize := 1024 + cfg := Config{ + LogDriver: "stdio", + LogDir: constant.DefaultLogDir, + LogSize: defaultLogSize, + LogLevel: "info", + CgroupRoot: constant.DefaultCgroupRoot, + } + + defer func() { + CgroupRoot = cfg.CgroupRoot + }() + + if !util.PathExist(path) { + return &cfg, nil + } + + b, err := util.ReadSmallFile(filepath.Clean(path)) + if err != nil { + return nil, err + } + + reader := bytes.NewReader(b) + if err := json.NewDecoder(reader).Decode(&cfg); err != nil { + return nil, err + } + + return &cfg, nil +} + +// String return string format. +func (cfg *Config) String() string { + data, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return "{}" + } + return string(data) +} diff --git a/pkg/constant/constant.go b/pkg/constant/constant.go index 1fe066c..a6648f4 100644 --- a/pkg/constant/constant.go +++ b/pkg/constant/constant.go @@ -23,6 +23,8 @@ import ( const ( // RubikSock is path for rubik socket file RubikSock = "/run/rubik/rubik.sock" + // ConfigFile is rubik config file + ConfigFile = "/var/lib/rubik/config.json" // DefaultLogDir is default log dir DefaultLogDir = "/var/log/rubik" // ReadTimeout is timeout for http read diff --git a/pkg/httpserver/server.go b/pkg/httpserver/server.go index 0b83275..8edd01f 100644 --- a/pkg/httpserver/server.go +++ b/pkg/httpserver/server.go @@ -24,6 +24,7 @@ import ( "isula.org/rubik/api" "isula.org/rubik/pkg/constant" + log "isula.org/rubik/pkg/tinylog" "isula.org/rubik/pkg/version" "isula.org/rubik/pkg/workerpool" ) @@ -88,18 +89,25 @@ func RootHandler(w http.ResponseWriter, r *http.Request) { var err error var reqs api.SetQosRequest + log.WithCtx(ctx).Logf("Handle HTTP root request start") + err = json.NewDecoder(r.Body).Decode(&reqs) + log.DropError(r.Body.Close()) if err != nil { writeRootResponse(ctx, w, constant.ErrCodeFailed, "Decode request body failed") + log.WithCtx(ctx).Errorf("Decode request body failed: %v", err) return } + err = pool.PushTask(workerpool.NewQosTask(ctx, reqs)) if err != nil { writeRootResponse(ctx, w, constant.ErrCodeFailed, "set qos failed") + log.WithCtx(ctx).Errorf("Handle HTTP root request failed: %v", err) return } writeRootResponse(ctx, w, constant.DefaultSucceedCode, "") + log.WithCtx(ctx).Logf("Handle HTTP root request OK") } func ping(ctx context.Context, w http.ResponseWriter, r *http.Request) { diff --git a/pkg/qos/qos.go b/pkg/qos/qos.go index bbd6a3c..897db0d 100644 --- a/pkg/qos/qos.go +++ b/pkg/qos/qos.go @@ -26,6 +26,7 @@ import ( "isula.org/rubik/api" "isula.org/rubik/pkg/constant" + "isula.org/rubik/pkg/tinylog" "isula.org/rubik/pkg/util" ) @@ -132,6 +133,8 @@ func setQosLevel(root, file string, target int) error { // SetQos is used for setting pod's qos level following it's cgroup path func (pod *PodInfo) SetQos() error { + ctx := pod.Ctx + tinylog.WithCtx(ctx).Logf("Setting level=%d for pod %s", pod.QosLevel, pod.PodID) if pod.FullPath == nil { return errors.Errorf("Empty cgroup path of pod %s", pod.PodID) } @@ -149,6 +152,7 @@ func (pod *PodInfo) SetQos() error { } } + tinylog.WithCtx(ctx).Logf("Setting level=%d for pod %s OK", pod.QosLevel, pod.PodID) return nil } diff --git a/pkg/rubik/rubik.go b/pkg/rubik/rubik.go index 00cd42b..af06d5c 100644 --- a/pkg/rubik/rubik.go +++ b/pkg/rubik/rubik.go @@ -21,8 +21,10 @@ import ( "github.com/pkg/errors" + "isula.org/rubik/pkg/config" "isula.org/rubik/pkg/constant" "isula.org/rubik/pkg/httpserver" + log "isula.org/rubik/pkg/tinylog" "isula.org/rubik/pkg/workerpool" ) @@ -31,10 +33,20 @@ type Rubik struct { server *http.Server pool *workerpool.WorkerPool sock *net.Listener + config *config.Config } // NewRubik creates a new rubik object -func NewRubik() (*Rubik, error) { +func NewRubik(cfgPath string) (*Rubik, error) { + cfg, err := config.NewConfig(cfgPath) + if err != nil { + return nil, errors.Errorf("load config failed: %v", err) + } + + if err = log.InitConfig(cfg.LogDriver, cfg.LogDir, cfg.LogLevel, int64(cfg.LogSize)); err != nil { + return nil, errors.Errorf("init log config failed: %v", err) + } + sock, err := httpserver.NewSock() if err != nil { return nil, errors.Errorf("new sock failed: %v", err) @@ -45,30 +57,34 @@ func NewRubik() (*Rubik, error) { server: server, pool: pool, sock: sock, + config: cfg, }, nil } // Serve starts http server func (r *Rubik) Serve() error { + log.Logf("Start http server %s with cfg\n%v", constant.RubikSock, r.config) return r.server.Serve(*r.sock) } -func run() int { - rubik, err := NewRubik() +func run(fcfg string) int { + rubik, err := NewRubik(fcfg) if err != nil { fmt.Printf("new rubik failed: %v\n", err) + log.Errorf("http serve failed: %v", err) return constant.ErrCodeFailed } if err = rubik.Serve(); err != nil { + log.Errorf("http serve failed: %v", err) return constant.ErrCodeFailed } return 0 } // Run start rubik server -func Run() int { - ret := run() +func Run(fcfg string) int { + ret := run(fcfg) return ret } diff --git a/pkg/tinylog/tinylog.go b/pkg/tinylog/tinylog.go new file mode 100644 index 0000000..6c5ca98 --- /dev/null +++ b/pkg/tinylog/tinylog.go @@ -0,0 +1,306 @@ +// Copyright (c) Huawei Technologies Co., Ltd. 2021. All rights reserved. +// rubik licensed under the Mulan PSL v2. +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. +// Author: Haomin Tsai +// Create: 2021-09-28 +// Description: This file is used for rubik log + +// Package tinylog is for rubik log +package tinylog + +import ( + "context" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + "sync/atomic" + "time" + + "isula.org/rubik/pkg/constant" +) + +// CtxKey used for UUID +type CtxKey string + +const ( + // UUID is log uuid + UUID = "uuid" + + logStdio = 0 + logFile = 1 + logDriverStdio = "stdio" + logDriverFile = "file" + + logDebug = 0 + logInfo = 1 + logError = 2 + logStack = 20 + logStackFrom = 2 + logLevelInfo = "info" + logLevelStack = "stack" + + logFileNum = 10 + logSizeMin int64 = 10 // 10MB + logSizeMax int64 = 1024 * 1024 // 1TB + unitMB int64 = 1024 * 1024 +) + +var ( + logDriver = logStdio + logFname = filepath.Join(constant.DefaultLogDir, "rubik.log") + logLevel = 0 + logSize int64 = 1024 + logFileMaxSize int64 + logFileSize int64 + + lock = sync.Mutex{} +) + +func makeLogDir(logDir string) error { + if !filepath.IsAbs(logDir) { + return fmt.Errorf("log-dir %v must be an absolute path", logDir) + } + + if err := os.MkdirAll(logDir, constant.DefaultDirMode); err != nil { + return fmt.Errorf("create log directory %v failed", logDir) + } + + return nil +} + +// InitConfig init log config +func InitConfig(driver, logdir, level string, size int64) error { + if driver == "" { + driver = logDriverStdio + } + if driver != logDriverStdio && driver != logDriverFile { + return fmt.Errorf("invalid log driver %s", driver) + } + logDriver = logStdio + if driver == logDriverFile { + logDriver = logFile + } + + if level == "" { + level = logLevelInfo + } + levelstr, err := logLevelFromString(level) + if err != nil { + return err + } + logLevel = levelstr + + if size < logSizeMin || size > logSizeMax { + return fmt.Errorf("invalid log size %d", size) + } + logSize = size + logFileMaxSize = logSize / logFileNum + + if driver == "file" { + if err := makeLogDir(logdir); err != nil { + return err + } + logFname = filepath.Join(logdir, "rubik.log") + if f, err := os.Stat(logFname); err == nil { + atomic.StoreInt64(&logFileSize, f.Size()) + } + } + + return nil +} + +// DropError drop unused error +func DropError(args ...interface{}) { + argn := len(args) + if argn == 0 { + return + } + arg := args[argn-1] + if arg != nil { + fmt.Printf("drop error: %v\n", arg) + } +} + +func logLevelToString(level int) string { + switch level { + case logDebug: + return "debug" + case logInfo: + return "info" + case logError: + return "error" + case logStack: + return logLevelStack + default: + return "" + } +} + +func logLevelFromString(level string) (int, error) { + switch level { + case "debug": + return logDebug, nil + case "info", "": + return logInfo, nil + case "error": + return logError, nil + default: + return logInfo, fmt.Errorf("invalid log level %s", level) + } +} + +func logRename() { + for i := logFileNum - 1; i > 1; i-- { + old := logFname + fmt.Sprintf(".%d", i-1) + new := logFname + fmt.Sprintf(".%d", i) + if _, err := os.Stat(old); err == nil { + DropError(os.Rename(old, new)) + } + } + DropError(os.Rename(logFname, logFname+".1")) +} + +func logRotate(line int64) string { + if atomic.AddInt64(&logFileSize, line) > logFileMaxSize*unitMB { + logRename() + atomic.StoreInt64(&logFileSize, line) + } + + return logFname +} + +func writeLine(line string) { + if logDriver == logStdio { + fmt.Printf("%s", line) + return + } + + lock.Lock() + defer lock.Unlock() + + f, err := os.OpenFile(logRotate(int64(len(line))), os.O_CREATE|os.O_APPEND|os.O_WRONLY, constant.DefaultFileMode) + if err != nil { + return + } + + DropError(f.WriteString(line)) + DropError(f.Close()) +} + +func logf(level string, format string, args ...interface{}) { + tag := fmt.Sprintf("%s [rubik] level=%s ", time.Now().Format("2006-01-02 15:04:05.000"), level) + raw := fmt.Sprintf(format, args...) + "\n" + + depth := 1 + if level == logLevelStack { + depth = logStack + } + + for i := logStackFrom; i < logStackFrom+depth; i++ { + line := tag + raw + pc, file, linum, ok := runtime.Caller(i) + if ok { + fs := strings.Split(runtime.FuncForPC(pc).Name(), "/") + fs = strings.Split("."+fs[len(fs)-1], ".") + fn := fs[len(fs)-1] + line = tag + fmt.Sprintf("%s:%d:%s() ", file, linum, fn) + raw + } else if level == logLevelStack { + break + } + writeLine(line) + } +} + +// Logf log info level +func Logf(format string, args ...interface{}) { + if logInfo >= logLevel { + logf(logLevelToString(logInfo), format, args...) + } +} + +// Infof log info level +func Infof(format string, args ...interface{}) { + if logInfo >= logLevel { + logf(logLevelToString(logInfo), format, args...) + } +} + +// Debugf log debug level +func Debugf(format string, args ...interface{}) { + if logDebug >= logLevel { + logf(logLevelToString(logDebug), format, args...) + } +} + +// Errorf log error level +func Errorf(format string, args ...interface{}) { + if logError >= logLevel { + logf(logLevelToString(logError), format, args...) + } +} + +// Stackf log stack dump +func Stackf(format string, args ...interface{}) { + logf("stack", format, args...) +} + +// Entry is log entry +type Entry struct { + Ctx context.Context +} + +// WithCtx create entry with ctx +func WithCtx(ctx context.Context) *Entry { + return &Entry{ + Ctx: ctx, + } +} + +func (e *Entry) level(l int) string { + uuid, ok := e.Ctx.Value(CtxKey(UUID)).(string) + if ok { + return logLevelToString(l) + " UUID=" + uuid + } + return logLevelToString(l) +} + +// Logf write logs +func (e *Entry) Logf(f string, args ...interface{}) { + if logInfo < logLevel { + return + } + logf(e.level(logInfo), f, args...) +} + +// Infof write logs +func (e *Entry) Infof(f string, args ...interface{}) { + if logInfo < logLevel { + return + } + logf(e.level(logInfo), f, args...) +} + +// Debugf write verbose logs +func (e *Entry) Debugf(f string, args ...interface{}) { + if logDebug < logLevel { + return + } + logf(e.level(logDebug), f, args...) +} + +// Errorf write error logs +func (e *Entry) Errorf(f string, args ...interface{}) { + if logError < logLevel { + return + } + logf(e.level(logError), f, args...) +} diff --git a/pkg/workerpool/workerpool.go b/pkg/workerpool/workerpool.go index 8c285bd..9a493c5 100644 --- a/pkg/workerpool/workerpool.go +++ b/pkg/workerpool/workerpool.go @@ -20,8 +20,10 @@ import ( "github.com/pkg/errors" "isula.org/rubik/api" + "isula.org/rubik/pkg/config" "isula.org/rubik/pkg/constant" "isula.org/rubik/pkg/qos" + "isula.org/rubik/pkg/tinylog" ) type task interface { @@ -121,11 +123,12 @@ func (task *QosTask) do() error { } for podID, req := range task.req.Pods { - pod, nErr := qos.NewPodInfo(task.ctx, podID, constant.DefaultCgroupRoot, req) + pod, nErr := qos.NewPodInfo(task.ctx, podID, config.CgroupRoot, req) if nErr != nil { return nErr } if sErr = pod.SetQos(); sErr != nil { + tinylog.WithCtx(task.ctx).Errorf("Set pod %v qos level error: %v", podID, sErr) errFlag = true continue } diff --git a/rubik.go b/rubik.go index caf029a..8fc7369 100644 --- a/rubik.go +++ b/rubik.go @@ -16,9 +16,10 @@ package main import ( "os" + "isula.org/rubik/pkg/constant" "isula.org/rubik/pkg/rubik" ) func main() { - os.Exit(rubik.Run()) + os.Exit(rubik.Run(constant.ConfigFile)) } -- Gitee