mirror of
https://github.com/minio/mc
synced 2026-08-29 14:37:42 -04:00
app.Before receives a cli.Context but without flags parsed. There is no point calling ctx.Bool() or ctx.IsSet() at that stage. However, flags are parsed in commands, so minio initialization and setting global variables (globalJSON, globalQuiet, etc..) can be moved to the Before function of all commands. Avoid setting command.Before for non leaf commands, otherwise Before function will be called multiples times until it reaches the leaf command.
215 lines
6.1 KiB
Go
215 lines
6.1 KiB
Go
/*
|
|
* MinIO Client (C) 2019 MinIO, Inc.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/fatih/color"
|
|
"github.com/minio/cli"
|
|
json "github.com/minio/mc/pkg/colorjson"
|
|
"github.com/minio/mc/pkg/probe"
|
|
"github.com/minio/minio/pkg/console"
|
|
"github.com/minio/minio/pkg/madmin"
|
|
)
|
|
|
|
const logTimeFormat string = "15:04:05 MST 01/02/2006"
|
|
|
|
var adminConsoleFlags = []cli.Flag{
|
|
cli.IntFlag{
|
|
Name: "limit, l",
|
|
Usage: "show last n log entries",
|
|
Value: 10,
|
|
},
|
|
cli.StringFlag{
|
|
Name: "type, t",
|
|
Usage: "list error logs by type. Valid options are '[minio, application, all]'",
|
|
Value: "all",
|
|
},
|
|
}
|
|
|
|
var adminConsoleCmd = cli.Command{
|
|
Name: "console",
|
|
Usage: "show console logs for MinIO server",
|
|
Action: mainAdminConsole,
|
|
Before: initBeforeRunningCmd,
|
|
Flags: append(adminConsoleFlags, globalFlags...),
|
|
HideHelpCommand: true,
|
|
CustomHelpTemplate: `NAME:
|
|
{{.HelpName}} - {{.Usage}}
|
|
|
|
USAGE:
|
|
{{.HelpName}} [FLAGS] TARGET [NODENAME]
|
|
|
|
FLAGS:
|
|
{{range .VisibleFlags}}{{.}}
|
|
{{end}}
|
|
EXAMPLES:
|
|
1. Show console logs for a MinIO server with alias 'play'
|
|
{{.Prompt}} {{.HelpName}} play
|
|
|
|
2. Show last 5 log entries for node 'node1' on MinIO server with alias 'cluster1'
|
|
{{.Prompt}} {{.HelpName}} --limit 5 cluster1 node1
|
|
|
|
3. Show application error logs on MinIO server with alias 'play'
|
|
{{.Prompt}} {{.HelpName}} --type application play
|
|
`,
|
|
}
|
|
|
|
func checkAdminLogSyntax(ctx *cli.Context) {
|
|
if len(ctx.Args()) == 0 || len(ctx.Args()) > 3 {
|
|
cli.ShowCommandHelpAndExit(ctx, "console", 1) // last argument is exit code
|
|
}
|
|
}
|
|
|
|
// Extend madmin.LogInfo to add String() and JSON() methods
|
|
type logMessage struct {
|
|
madmin.LogInfo
|
|
}
|
|
|
|
// JSON - jsonify loginfo
|
|
func (l logMessage) JSON() string {
|
|
logJSON, err := json.MarshalIndent(&l, "", " ")
|
|
fatalIf(probe.NewError(err), "Unable to marshal into JSON.")
|
|
|
|
return string(logJSON)
|
|
|
|
}
|
|
func getLogTime(lt string) string {
|
|
tm, err := time.Parse(time.RFC3339Nano, lt)
|
|
if err != nil {
|
|
return lt
|
|
}
|
|
return tm.Format(logTimeFormat)
|
|
}
|
|
|
|
// String - return colorized loginfo as string.
|
|
func (l logMessage) String() string {
|
|
var hostStr string
|
|
var b = &strings.Builder{}
|
|
if l.NodeName != "" {
|
|
hostStr = fmt.Sprintf("%s ", colorizedNodeName(l.NodeName))
|
|
}
|
|
log := l.LogInfo
|
|
if log.ConsoleMsg != "" {
|
|
if strings.HasPrefix(log.ConsoleMsg, "\n") {
|
|
fmt.Fprintf(b, "%s\n", hostStr)
|
|
log.ConsoleMsg = strings.TrimPrefix(log.ConsoleMsg, "\n")
|
|
}
|
|
fmt.Fprintf(b, "%s %s", hostStr, log.ConsoleMsg)
|
|
return b.String()
|
|
}
|
|
if l.API != nil {
|
|
apiString := "API: " + l.API.Name + "("
|
|
if l.API.Args != nil && l.API.Args.Bucket != "" {
|
|
apiString = apiString + "bucket=" + l.API.Args.Bucket
|
|
}
|
|
if l.API.Args != nil && l.API.Args.Object != "" {
|
|
apiString = apiString + ", object=" + l.API.Args.Object
|
|
}
|
|
apiString += ")"
|
|
fmt.Fprintf(b, "\n%s %s", hostStr, console.Colorize("API", apiString))
|
|
}
|
|
if l.Time != "" {
|
|
fmt.Fprintf(b, "\n%s Time: %s", hostStr, getLogTime(l.Time))
|
|
}
|
|
if l.DeploymentID != "" {
|
|
fmt.Fprintf(b, "\n%s DeploymentID: %s", hostStr, l.DeploymentID)
|
|
}
|
|
if l.RequestID != "" {
|
|
fmt.Fprintf(b, "\n%s RequestID: %s", hostStr, l.RequestID)
|
|
}
|
|
if l.RemoteHost != "" {
|
|
fmt.Fprintf(b, "\n%s RemoteHost: %s", hostStr, l.RemoteHost)
|
|
}
|
|
if l.UserAgent != "" {
|
|
fmt.Fprintf(b, "\n%s UserAgent: %s", hostStr, l.UserAgent)
|
|
}
|
|
if l.Trace != nil {
|
|
if l.Trace.Message != "" {
|
|
fmt.Fprintf(b, "\n%s Error: %s", hostStr, console.Colorize("LogMessage", l.Trace.Message))
|
|
}
|
|
if l.Trace.Variables != nil {
|
|
for key, value := range l.Trace.Variables {
|
|
if value != "" {
|
|
fmt.Fprintf(b, "\n%s %s=%s", hostStr, key, value)
|
|
}
|
|
}
|
|
}
|
|
if l.Trace.Source != nil {
|
|
traceLength := len(l.Trace.Source)
|
|
for i, element := range l.Trace.Source {
|
|
fmt.Fprintf(b, "\n%s %8v: %s", hostStr, traceLength-i, element)
|
|
}
|
|
}
|
|
}
|
|
logMsg := strings.TrimPrefix(b.String(), "\n")
|
|
return fmt.Sprintf("%s\n", logMsg)
|
|
}
|
|
|
|
// mainAdminConsole - the entry function of console command
|
|
func mainAdminConsole(ctx *cli.Context) error {
|
|
// Check for command syntax
|
|
checkAdminLogSyntax(ctx)
|
|
console.SetColor("LogMessage", color.New(color.Bold, color.FgRed))
|
|
console.SetColor("Api", color.New(color.Bold, color.FgWhite))
|
|
for _, c := range colors {
|
|
console.SetColor(fmt.Sprintf("Node%d", c), color.New(c))
|
|
}
|
|
aliasedURL := ctx.Args().Get(0)
|
|
var node string
|
|
if len(ctx.Args()) > 1 {
|
|
node = ctx.Args().Get(1)
|
|
}
|
|
var limit int
|
|
if ctx.IsSet("limit") {
|
|
limit = ctx.Int("limit")
|
|
if limit <= 0 {
|
|
fatalIf(errInvalidArgument().Trace(ctx.Args()...), "please set a proper limit, for example: '--limit 5' to display last 5 logs, omit this flag to display all available logs")
|
|
}
|
|
}
|
|
logType := strings.ToLower(ctx.String("type"))
|
|
if logType != "minio" && logType != "application" && logType != "all" {
|
|
fatalIf(errInvalidArgument().Trace(ctx.Args()...), "Invalid value for --type flag. Valid options are [minio, application, all]")
|
|
}
|
|
// Create a new MinIO Admin Client
|
|
client, err := newAdminClient(aliasedURL)
|
|
if err != nil {
|
|
fatalIf(err.Trace(aliasedURL), "Cannot initialize admin client.")
|
|
return nil
|
|
}
|
|
|
|
ctxt, cancel := context.WithCancel(globalContext)
|
|
defer cancel()
|
|
|
|
// Start listening on all console log activity.
|
|
logCh := client.GetLogs(ctxt, node, limit, logType)
|
|
for logInfo := range logCh {
|
|
if logInfo.Err != nil {
|
|
fatalIf(probe.NewError(logInfo.Err), "Cannot listen to console logs")
|
|
}
|
|
// drop nodeName from output if specified as cli arg
|
|
if node != "" {
|
|
logInfo.NodeName = ""
|
|
}
|
|
printMsg(logMessage{logInfo})
|
|
}
|
|
return nil
|
|
}
|