mirror of https://github.com/dapr/cli.git
243 lines
6.1 KiB
Go
243 lines
6.1 KiB
Go
/*
|
||
Copyright 2021 The Dapr Authors
|
||
Licensed under the Apache License, Version 2.0 (the "License");
|
||
you may not use this file except in compliance with the License.
|
||
You may obtain a copy of the License at
|
||
http://www.apache.org/licenses/LICENSE-2.0
|
||
Unless required by applicable law or agreed to in writing, software
|
||
distributed under the License is distributed on an "AS IS" BASIS,
|
||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
See the License for the specific language governing permissions and
|
||
limitations under the License.
|
||
*/
|
||
//nolint
|
||
package print
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"os"
|
||
"reflect"
|
||
"regexp"
|
||
"runtime"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/briandowns/spinner"
|
||
"github.com/fatih/color"
|
||
)
|
||
|
||
const (
|
||
windowsOS = "windows"
|
||
)
|
||
|
||
type logStatus string
|
||
|
||
const (
|
||
LogSuccess logStatus = "success"
|
||
LogFailure logStatus = "failure"
|
||
LogWarning logStatus = "warning"
|
||
LogInfo logStatus = "info"
|
||
LogPending logStatus = "pending"
|
||
)
|
||
|
||
type Result bool
|
||
|
||
const (
|
||
Success Result = true
|
||
Failure Result = false
|
||
)
|
||
|
||
var (
|
||
Yellow = color.New(color.FgHiYellow, color.Bold).SprintFunc()
|
||
Green = color.New(color.FgHiGreen, color.Bold).SprintFunc()
|
||
Blue = color.New(color.FgHiBlue, color.Bold).SprintFunc()
|
||
Cyan = color.New(color.FgCyan, color.Bold, color.Underline).SprintFunc()
|
||
Red = color.New(color.FgHiRed, color.Bold).Add(color.Italic).SprintFunc()
|
||
White = color.New(color.FgWhite).SprintFunc()
|
||
WhiteBold = color.New(color.FgWhite, color.Bold).SprintFunc()
|
||
)
|
||
|
||
var logAsJSON bool
|
||
|
||
func EnableJSONFormat() {
|
||
logAsJSON = true
|
||
}
|
||
|
||
func IsJSONLogEnabled() bool {
|
||
return logAsJSON
|
||
}
|
||
|
||
// StatusEvent reports a event log with given status.
|
||
func StatusEvent(w io.Writer, status logStatus, fmtstr string, a ...any) {
|
||
if logAsJSON {
|
||
logJSON(w, string(status), fmt.Sprintf(fmtstr, a...))
|
||
return
|
||
}
|
||
if (w != os.Stdout && w != os.Stderr) || runtime.GOOS == windowsOS {
|
||
fmt.Fprintf(w, "%s\n", fmt.Sprintf(fmtstr, a...))
|
||
return
|
||
}
|
||
switch status {
|
||
case LogSuccess:
|
||
fmt.Fprintf(w, "✅ %s\n", fmt.Sprintf(fmtstr, a...))
|
||
case LogFailure:
|
||
fmt.Fprintf(w, "❌ %s\n", fmt.Sprintf(fmtstr, a...))
|
||
case LogWarning:
|
||
fmt.Fprintf(w, "⚠ %s\n", fmt.Sprintf(fmtstr, a...))
|
||
case LogPending:
|
||
fmt.Fprintf(w, "⌛ %s\n", fmt.Sprintf(fmtstr, a...))
|
||
case LogInfo:
|
||
fmt.Fprintf(w, "ℹ️ %s\n", fmt.Sprintf(fmtstr, a...))
|
||
default:
|
||
fmt.Fprintf(w, "%s\n", fmt.Sprintf(fmtstr, a...))
|
||
}
|
||
}
|
||
|
||
// SuccessStatusEvent reports on a success event.
|
||
func SuccessStatusEvent(w io.Writer, fmtstr string, a ...interface{}) {
|
||
if logAsJSON {
|
||
logJSON(w, string(LogSuccess), fmt.Sprintf(fmtstr, a...))
|
||
} else if runtime.GOOS == windowsOS {
|
||
fmt.Fprintf(w, "%s\n", fmt.Sprintf(fmtstr, a...))
|
||
} else {
|
||
fmt.Fprintf(w, "✅ %s\n", fmt.Sprintf(fmtstr, a...))
|
||
}
|
||
}
|
||
|
||
// FailureStatusEvent reports on a failure event.
|
||
func FailureStatusEvent(w io.Writer, fmtstr string, a ...interface{}) {
|
||
if logAsJSON {
|
||
logJSON(w, string(LogFailure), fmt.Sprintf(fmtstr, a...))
|
||
} else if runtime.GOOS == windowsOS {
|
||
fmt.Fprintf(w, "%s\n", fmt.Sprintf(fmtstr, a...))
|
||
} else {
|
||
fmt.Fprintf(w, "❌ %s\n", fmt.Sprintf(fmtstr, a...))
|
||
}
|
||
}
|
||
|
||
// WarningStatusEvent reports on a failure event.
|
||
func WarningStatusEvent(w io.Writer, fmtstr string, a ...interface{}) {
|
||
if logAsJSON {
|
||
logJSON(w, string(LogWarning), fmt.Sprintf(fmtstr, a...))
|
||
} else if runtime.GOOS == windowsOS {
|
||
fmt.Fprintf(w, "%s\n", fmt.Sprintf(fmtstr, a...))
|
||
} else {
|
||
fmt.Fprintf(w, "⚠ %s\n", fmt.Sprintf(fmtstr, a...))
|
||
}
|
||
}
|
||
|
||
// PendingStatusEvent reports on a pending event.
|
||
func PendingStatusEvent(w io.Writer, fmtstr string, a ...interface{}) {
|
||
if logAsJSON {
|
||
logJSON(w, string(LogPending), fmt.Sprintf(fmtstr, a...))
|
||
} else if runtime.GOOS == windowsOS {
|
||
fmt.Fprintf(w, "%s\n", fmt.Sprintf(fmtstr, a...))
|
||
} else {
|
||
fmt.Fprintf(w, "⌛ %s\n", fmt.Sprintf(fmtstr, a...))
|
||
}
|
||
}
|
||
|
||
// InfoStatusEvent reports status information on an event.
|
||
func InfoStatusEvent(w io.Writer, fmtstr string, a ...interface{}) {
|
||
if logAsJSON {
|
||
logJSON(w, string(LogInfo), fmt.Sprintf(fmtstr, a...))
|
||
} else if runtime.GOOS == windowsOS {
|
||
fmt.Fprintf(w, "%s\n", fmt.Sprintf(fmtstr, a...))
|
||
} else {
|
||
fmt.Fprintf(w, "ℹ️ %s\n", fmt.Sprintf(fmtstr, a...))
|
||
}
|
||
}
|
||
|
||
func Spinner(w io.Writer, fmtstr string, a ...interface{}) func(result Result) {
|
||
msg := fmt.Sprintf(fmtstr, a...)
|
||
var once sync.Once
|
||
var s *spinner.Spinner
|
||
|
||
if logAsJSON {
|
||
logJSON(w, string(LogPending), msg)
|
||
} else if runtime.GOOS == windowsOS {
|
||
fmt.Fprintf(w, "%s\n", msg)
|
||
|
||
return func(Result) {} // Return a dummy func
|
||
} else {
|
||
s = spinner.New(spinner.CharSets[0], 100*time.Millisecond)
|
||
s.Writer = w
|
||
s.Color("cyan")
|
||
s.Suffix = fmt.Sprintf(" %s", msg)
|
||
s.Start()
|
||
}
|
||
|
||
return func(result Result) {
|
||
once.Do(func() {
|
||
if s != nil {
|
||
s.Stop()
|
||
}
|
||
if result {
|
||
SuccessStatusEvent(w, msg)
|
||
} else {
|
||
FailureStatusEvent(w, msg)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func logJSON(w io.Writer, status, message string) {
|
||
type jsonLog struct {
|
||
Time time.Time `json:"time"`
|
||
Status string `json:"status"`
|
||
Message string `json:"msg"`
|
||
}
|
||
|
||
l := jsonLog{
|
||
Time: time.Now().UTC(),
|
||
Status: status,
|
||
Message: message,
|
||
}
|
||
jsonBytes, err := json.Marshal(&l)
|
||
if err != nil {
|
||
// Fall back on printing the simple message without JSON.
|
||
// This is unlikely.
|
||
fmt.Fprintln(w, message)
|
||
|
||
return
|
||
}
|
||
|
||
fmt.Fprintf(w, "%s\n", string(jsonBytes))
|
||
}
|
||
|
||
type CustomLogWriter struct {
|
||
W io.Writer
|
||
}
|
||
|
||
func (c CustomLogWriter) Write(p []byte) (int, error) {
|
||
write := func(w io.Writer, isStdIO bool) (int, error) {
|
||
b := p
|
||
if !isStdIO {
|
||
// below regex is used to replace the color codes from the logs collected in the log file.
|
||
reg := regexp.MustCompile("\x1b\\[[\\d;]+m")
|
||
b = reg.ReplaceAll(b, []byte(""))
|
||
}
|
||
n, err := w.Write(b)
|
||
if err != nil {
|
||
return n, err
|
||
}
|
||
if n != len(b) {
|
||
return n, io.ErrShortWrite
|
||
}
|
||
return len(b), nil
|
||
}
|
||
wIface := reflect.ValueOf(c.W).Interface()
|
||
switch wType := wIface.(type) {
|
||
case *os.File:
|
||
if wType == os.Stderr || wType == os.Stdout {
|
||
return write(c.W, true)
|
||
} else {
|
||
return write(c.W, false)
|
||
}
|
||
default:
|
||
return write(c.W, false)
|
||
}
|
||
}
|