From 1ff7fa4115b62fd5d1e339f22c994e09f50f53e9 Mon Sep 17 00:00:00 2001 From: lixiaojun Date: Mon, 6 May 2019 20:07:41 +0800 Subject: [PATCH 1/2] add rotate log --- Makefile | 2 +- base/config.go | 2 +- base/log.go | 120 +++++++++++++++++++++++++++++++++++++++++++++++++ base/util.go | 39 ---------------- cmd/uhost.go | 6 +-- go.mod | 1 + main.go | 4 +- ux/document.go | 115 ++++++++++++++--------------------------------- 8 files changed, 163 insertions(+), 126 deletions(-) create mode 100644 base/log.go diff --git a/Makefile b/Makefile index 51280997dd..043696fa02 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export VERSION=0.1.15 +export VERSION=0.1.16 .PHONY : build build: diff --git a/base/config.go b/base/config.go index 5998339d8e..40a9d076a7 100644 --- a/base/config.go +++ b/base/config.go @@ -30,7 +30,7 @@ const DefaultBaseURL = "https://api.ucloud.cn/" const DefaultProfile = "default" //Version 版本号 -const Version = "0.1.15" +const Version = "0.1.16" //ConfigIns 配置实例, 程序加载时生成 var ConfigIns = &AggConfig{} diff --git a/base/log.go b/base/log.go new file mode 100644 index 0000000000..3860790317 --- /dev/null +++ b/base/log.go @@ -0,0 +1,120 @@ + +package base + +import ( + "os" + "sync" + "fmt" + + log "github.com/sirupsen/logrus" + "github.com/ucloud/ucloud-sdk-go/ucloud/request" +) + +//Logger 日志 +var logger *log.Logger +var mu sync.Mutex + +func init() { + initLog() +} + +func initLog(){ + file, err := os.OpenFile(GetLogFilePath(), os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644) + if err != nil { + fmt.Println("open log file failed: ", err) + return + } + logger = log.New() + logger.SetNoLock() + logger.AddHook(NewLogRotateHook(file)) + logger.SetOutput(file) +} + +//GetLogFilePath 获取日志文件路径 +func GetLogFilePath() string { + return GetHomePath() + fmt.Sprintf("/%s/cli.log", ConfigPath) +} + +//Log 记录日志 +func Log(logs []string) { + mu.Lock() + defer mu.Unlock() + logger.Info("=============================================================") + for _, line := range logs { + logger.Info(line) + } +} + +//LogRotateHook rotate log file +type LogRotateHook struct { + MaxSize int64 + Cut float32 + LogFile *os.File + mux sync.Mutex +} + +//Levels fires hook +func (hook *LogRotateHook) Levels() []log.Level { + return log.AllLevels +} + +//Fire do someting when hook is triggered +func (hook *LogRotateHook) Fire(entry *log.Entry) error { + hook.mux.Lock() + defer hook.mux.Unlock() + info, err := hook.LogFile.Stat() + if err != nil { + return err + } + + if info.Size() <= hook.MaxSize { + return nil + } + hook.LogFile.Sync() + offset := int64(float32(hook.MaxSize) * hook.Cut) + buf := make([]byte, info.Size()-offset) + _, err = hook.LogFile.ReadAt(buf, offset) + if err != nil { + return err + } + + nfile, err := os.Create(GetLogFilePath() + ".tmp") + if err != nil { + return err + } + nfile.Write(buf) + nfile.Close() + + err = os.Rename(GetLogFilePath()+".tmp", GetLogFilePath()) + if err != nil { + return err + } + + mfile, err := os.OpenFile(GetLogFilePath(), os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644) + if err != nil { + fmt.Println("open log file failed: ", err) + return err + } + entry.Logger.SetOutput(mfile) + initLog() + return nil +} + +//NewLogRotateHook create a LogRotateHook +func NewLogRotateHook(file *os.File) *LogRotateHook { + return &LogRotateHook{ + MaxSize: 1024*1024, //1MB + Cut: 0.2, + LogFile: file, + } +} + +//ToQueryMap tranform request to map +func ToQueryMap(req request.Common) map[string]string { + reqMap, err := request.ToQueryMap(req) + if err != nil { + return nil + } + delete(reqMap, "Password") + return reqMap +} diff --git a/base/util.go b/base/util.go index 8ff971f8a3..d48e4dc11c 100644 --- a/base/util.go +++ b/base/util.go @@ -11,7 +11,6 @@ import ( "runtime" "strconv" "strings" - "sync" "time" "unicode" @@ -19,7 +18,6 @@ import ( uerr "github.com/ucloud/ucloud-sdk-go/ucloud/error" "github.com/ucloud/ucloud-sdk-go/ucloud/helpers/waiter" "github.com/ucloud/ucloud-sdk-go/ucloud/log" - "github.com/ucloud/ucloud-sdk-go/ucloud/request" "github.com/ucloud/ucloud-sdk-go/ucloud/response" "github.com/ucloud/ucloud-cli/model" @@ -41,43 +39,6 @@ var SdkClient *sdk.Client //BizClient 用于调用业务接口 var BizClient *Client -//Logger 日志 -var Logger = log.New() -var mu sync.Mutex - -func init() { - file, err := os.Create(GetLogFilePath()) - if err != nil { - return - } - Logger.SetOutput(file) -} - -//GetLogFilePath 获取日志文件路径 -func GetLogFilePath() string { - return GetHomePath() + fmt.Sprintf("/%s/cli.log", ConfigPath) -} - -//Log 记录日志 -func Log(logs []string) { - mu.Lock() - defer mu.Unlock() - Logger.Info("=============================================================") - for _, line := range logs { - Logger.Info(line) - } -} - -//ToQueryMap tranform request to map -func ToQueryMap(req request.Common) map[string]string { - reqMap, err := request.ToQueryMap(req) - if err != nil { - return nil - } - // delete(reqMap, "Password") - return reqMap -} - //GetHomePath 获取家目录 func GetHomePath() string { if runtime.GOOS == "windows" { diff --git a/cmd/uhost.go b/cmd/uhost.go index 300bd3b86b..4f4218fed9 100644 --- a/cmd/uhost.go +++ b/cmd/uhost.go @@ -20,6 +20,7 @@ import ( "io" "strings" "sync" + "time" "github.com/spf13/cobra" @@ -291,6 +292,8 @@ func createUhostWrapper(req *uhost.CreateUHostInstanceRequest, eipReq *unet.Allo tokens <- struct{}{} defer func() { <-tokens + //设置延时,使报错能渲染出来 + time.Sleep(time.Second / 5) wg.Done() }() @@ -309,14 +312,12 @@ func createUhost(req *uhost.CreateUHostInstanceRequest, eipReq *unet.AllocateEIP if err != nil { logs = append(logs, fmt.Sprintf("err:%v", err)) block.Append(base.ParseError(err)) - block.AppendDone() return false, logs } logs = append(logs, fmt.Sprintf("resp:%#v", resp)) if len(resp.UHostIds) != 1 { block.Append(fmt.Sprintf("expect uhost count 1 , accept %d", len(resp.UHostIds))) - block.AppendDone() return false, logs } @@ -367,7 +368,6 @@ func createUhost(req *uhost.CreateUHostInstanceRequest, eipReq *unet.AllocateEIP } } } - block.AppendDone() return true, logs } diff --git a/go.mod b/go.mod index 9cfdedb8b4..8e36a4afb9 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.12 require ( github.com/kr/pretty v0.1.0 // indirect + github.com/sirupsen/logrus v1.3.0 github.com/spf13/cobra v0.0.3 github.com/spf13/pflag v1.0.3 github.com/ucloud/ucloud-sdk-go v0.8.2 diff --git a/main.go b/main.go index b6ce89c431..9df448afa8 100644 --- a/main.go +++ b/main.go @@ -14,7 +14,9 @@ package main -import "github.com/ucloud/ucloud-cli/cmd" +import ( + "github.com/ucloud/ucloud-cli/cmd" +) func main() { cmd.Execute() diff --git a/ux/document.go b/ux/document.go index 59fd7ec2d6..98b3060cbe 100644 --- a/ux/document.go +++ b/ux/document.go @@ -1,7 +1,6 @@ package ux import ( - "context" "fmt" "io" "os" @@ -21,10 +20,6 @@ type document struct { once sync.Once out io.Writer ticker *time.Ticker - ctx context.Context - cancel context.CancelFunc - allBlockFull chan bool - Done chan bool disable bool } @@ -51,7 +46,7 @@ func (d *document) SetWriter(out io.Writer) { func (d *document) Content() []string { var lines []string for _, block := range d.blocks { - for _, line := range block.lines { + for _, line := range <-block.getLines { lines = append(lines, line) } } @@ -69,8 +64,7 @@ func (d *document) Render() { d.mux.RLock() for _, block := range d.blocks { block.printLineNum = 0 - block.mux.Lock() - for _, line := range block.lines { + for _, line := range <-block.getLines { fmt.Fprintln(d.out, line) if width != 0 { lineNum := len(line)/width + 1 @@ -79,14 +73,12 @@ func (d *document) Render() { block.printLineNum++ } } - block.mux.Unlock() fmt.Fprintf(d.out, "\n") block.printLineNum++ } d.mux.RUnlock() } }() - go d.checkBlockDone() }) } @@ -94,56 +86,13 @@ func (d *document) Append(b *Block) { d.Render() d.mux.Lock() defer d.mux.Unlock() - if d.cancel != nil { - d.cancel() - } - d.ctx, d.cancel = context.WithCancel(context.Background()) - go d.checkBlockFull(d.ctx) d.blocks = append(d.blocks, b) } -func (d *document) checkBlockFull(ctx context.Context) { - allFull := make(chan struct{}) - go func() { - for _, b := range d.blocks { - select { - case <-b.full: - case <-ctx.Done(): - } - } - close(allFull) - }() - - select { - case <-ctx.Done(): - return - case <-allFull: - d.allBlockFull <- true - return - } -} - -func (d *document) checkBlockDone() { - <-d.allBlockFull - allStable := make(chan struct{}) - go func() { - for _, b := range d.blocks { - <-b.stable - } - close(allStable) - }() - <-allStable - //等待最后一帧渲染 - <-time.After(time.Millisecond * 200) - close(d.Done) -} - func newDocument(out io.Writer) *document { doc := &document{ out: out, framesPerSecond: 20, - Done: make(chan bool), - allBlockFull: make(chan bool), } doc.ticker = time.NewTicker(time.Second / time.Duration(doc.framesPerSecond)) return doc @@ -157,27 +106,19 @@ type Block struct { spinner *Spin spinnerIndex int printLineNum int //已打印到屏幕上的行数 - mux sync.Mutex lines []string - stable chan struct{} //标识此块已稳定,不再轮询 - full chan struct{} //标识此块不再添加新的内容, 一般轮询完成后才标识为Full, 不太合理,fixme + updateLine chan updateBlockLine + getLines chan []string } //Update lines in Block func (b *Block) Update(text string, index int) { - b.mux.Lock() - b.lines[index] = text - b.mux.Unlock() + b.updateLine <- updateBlockLine{text, index} } //Append text to Block func (b *Block) Append(text string) { - b.lines = append(b.lines, text) -} - -//AppendDone 表示不再往Block内部添加内容 -func (b *Block) AppendDone() { - close(b.full) + b.updateLine <- updateBlockLine{text, -1} } //SetSpin set spin for block @@ -185,35 +126,31 @@ func (b *Block) SetSpin(s *Spin) error { if b.spinner != nil { return fmt.Errorf("block has spinner already") } - b.stable = make(chan struct{}) b.spinner = s - b.spinnerIndex = len(b.lines) - b.lines = append(b.lines, "loading") + b.spinnerIndex = len(<-b.getLines) strsCh := b.spinner.renderToString() go func() { for text := range strsCh { - if len(b.lines) == 0 { + if len(<-b.getLines) == 0 { b.Append(text) } else { b.Update(text, b.spinnerIndex) } } - close(b.stable) }() return nil } +type updateBlockLine struct { + line string + index int +} + //NewSpinBlock create a new Block with spinner func NewSpinBlock(s *Spin) *Block { - block := &Block{ - lines: []string{}, - stable: make(chan struct{}), - full: make(chan struct{}), - } + block := NewBlock() if s != nil { block.SetSpin(s) - } else { - close(block.stable) } return block } @@ -221,10 +158,26 @@ func NewSpinBlock(s *Spin) *Block { //NewBlock create a new Block without spinner. block.Stable closed func NewBlock() *Block { block := &Block{ - lines: []string{}, - stable: make(chan struct{}), - full: make(chan struct{}), + lines: []string{}, + updateLine: make(chan updateBlockLine, 0), + getLines: make(chan []string, 0), } - close(block.stable) + + go func() { + for { + select { + case updateLine := <-block.updateLine: + index, line := updateLine.index, updateLine.line + if index < 0 { + block.lines = append(block.lines, line) + } else { + block.lines[index] = line + } + + case block.getLines <- block.lines: + } + } + }() + return block } From 3d62aac0390fb1543af6608567d646846cc0eeb0 Mon Sep 17 00:00:00 2001 From: lixiaojun Date: Tue, 7 May 2019 14:15:00 +0800 Subject: [PATCH 2/2] update changelog --- CHANGELOG.md | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40502200fc..ad091bd658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,41 @@ ## Change Log +v0.1.16 +* Support log rotation. Log file path $HOME/.ucloud/cli.log. +* Bugfix for display nothing when uhost create failed + +v0.1.15 +* Update documents +* Add test for uhost + +v0.1.14 +* Create uhost concurrently + +v0.1.13 +* Update version of ucloud-sdk-go to fix bug + +v0.1.12 +* Preliminary support umem + +v0.1.11 +* Use go modules to manage dependencies +* Fix bug for uhost clone + +v0.1.10 +* Support udb mysql + +v0.1.9 +* Better flag value completion with local cache and multiple resource ID completion +* Command structure adjustment + - ucloud bw-pkg => ucloud bw pkg + - ucloud shared-bw => ucloud bw shared + - ucloud ulb-vserver => ucloud ulb vserver + - ucloud ulb-ssl-certificate => ucloud ulb ssl + - ucloud ulb-vserver add-node/update-node/delete-node/list-node => ucloud ulb vserver backend add/update/delete/list + - ucloud ulb-vserver add-policy/list-policy/update-policy/delete-policy => ucloud ulb vserver policy add/list/update/delete + +v0.1.8 +* Support ulb + v0.1.7 * Add udpn, firewall, shared bandwidth and bandwidth package; Refactor vpc, subnet and eip @@ -9,16 +46,16 @@ v0.1.5 * support batch operation. v0.1.4 -* add udisk. -* polling udisk and uhost long time operation -* async complete resource-id +* Support udisk. +* Polling udisk and uhost long time operation +* Async complete resource-id v0.1.3 -* integrate auto completion. +* Integrate auto completion. * Support uhost create, stop, delete and so on. v0.1.2 -* simplify config and completion. +* Simplify config and completion. v0.1.1 * UHost list; EIP list,delete and allocate; GlobalSSH list,delete,modify and create. \ No newline at end of file