github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/admin-logs.go (about)

     1  // Copyright (c) 2015-2022 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/fatih/color"
    27  	"github.com/minio/cli"
    28  	json "github.com/minio/colorjson"
    29  	"github.com/minio/madmin-go/v3"
    30  	"github.com/minio/mc/pkg/probe"
    31  	"github.com/minio/pkg/v2/console"
    32  )
    33  
    34  const logTimeFormat string = "15:04:05 MST 01/02/2006"
    35  
    36  var logsShowFlags = []cli.Flag{
    37  	cli.IntFlag{
    38  		Name:  "last, l",
    39  		Usage: "show last n log entries",
    40  		Value: 10,
    41  	},
    42  	cli.StringFlag{
    43  		Name:  "type, t",
    44  		Usage: "list error logs by type. Valid options are '[minio, application, all]'",
    45  		Value: "all",
    46  	},
    47  }
    48  
    49  var adminLogsCmd = cli.Command{
    50  	Name:            "logs",
    51  	Usage:           "show MinIO logs",
    52  	OnUsageError:    onUsageError,
    53  	Action:          mainAdminLogs,
    54  	Before:          setGlobalsFromContext,
    55  	Flags:           append(logsShowFlags, globalFlags...),
    56  	HideHelpCommand: true,
    57  	CustomHelpTemplate: `NAME:
    58    {{.HelpName}} - {{.Usage}}
    59  USAGE:
    60    {{.HelpName}} [FLAGS] TARGET [NODENAME]
    61  FLAGS:
    62    {{range .VisibleFlags}}{{.}}
    63    {{end}}
    64  EXAMPLES:
    65    1. Show logs for a MinIO server with alias 'myminio'
    66       {{.Prompt}} {{.HelpName}} myminio
    67    2. Show last 5 log entries for node 'node1' for a MinIO server with alias 'myminio'
    68       {{.Prompt}} {{.HelpName}} --last 5 myminio node1
    69    3. Show application errors in logs for a MinIO server with alias 'myminio'
    70       {{.Prompt}} {{.HelpName}} --type application myminio
    71  `,
    72  }
    73  
    74  func checkLogsShowSyntax(ctx *cli.Context) {
    75  	if len(ctx.Args()) == 0 || len(ctx.Args()) > 3 {
    76  		showCommandHelpAndExit(ctx, 1) // last argument is exit code
    77  	}
    78  }
    79  
    80  // Extend madmin.LogInfo to add String() and JSON() methods
    81  type logMessage struct {
    82  	Status string `json:"status"`
    83  	madmin.LogInfo
    84  }
    85  
    86  // JSON - jsonify loginfo
    87  func (l logMessage) JSON() string {
    88  	l.Status = "success"
    89  	logJSON, e := json.MarshalIndent(&l, "", " ")
    90  	fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
    91  
    92  	return string(logJSON)
    93  }
    94  
    95  func getLogTime(lt string) string {
    96  	tm, e := time.Parse(time.RFC3339Nano, lt)
    97  	if e != nil {
    98  		return lt
    99  	}
   100  	return tm.Format(logTimeFormat)
   101  }
   102  
   103  // String - return colorized loginfo as string.
   104  func (l logMessage) String() string {
   105  	var hostStr string
   106  	b := &strings.Builder{}
   107  	if l.NodeName != "" {
   108  		hostStr = fmt.Sprintf("%s ", colorizedNodeName(l.NodeName))
   109  	}
   110  	log := l.LogInfo
   111  	if log.ConsoleMsg != "" {
   112  		if strings.HasPrefix(log.ConsoleMsg, "\n") {
   113  			fmt.Fprintf(b, "%s\n", hostStr)
   114  			log.ConsoleMsg = strings.TrimPrefix(log.ConsoleMsg, "\n")
   115  		}
   116  		fmt.Fprintf(b, "%s %s", hostStr, log.ConsoleMsg)
   117  		return b.String()
   118  	}
   119  	if l.API != nil {
   120  		apiString := "API: " + l.API.Name + "("
   121  		if l.API.Args != nil && l.API.Args.Bucket != "" {
   122  			apiString = apiString + "bucket=" + l.API.Args.Bucket
   123  		}
   124  		if l.API.Args != nil && l.API.Args.Object != "" {
   125  			apiString = apiString + ", object=" + l.API.Args.Object
   126  		}
   127  		apiString += ")"
   128  		fmt.Fprintf(b, "\n%s %s", hostStr, console.Colorize("API", apiString))
   129  	}
   130  	if l.Time != "" {
   131  		fmt.Fprintf(b, "\n%s Time: %s", hostStr, getLogTime(l.Time))
   132  	}
   133  	if l.DeploymentID != "" {
   134  		fmt.Fprintf(b, "\n%s DeploymentID: %s", hostStr, l.DeploymentID)
   135  	}
   136  	if l.RequestID != "" {
   137  		fmt.Fprintf(b, "\n%s RequestID: %s", hostStr, l.RequestID)
   138  	}
   139  	if l.RemoteHost != "" {
   140  		fmt.Fprintf(b, "\n%s RemoteHost: %s", hostStr, l.RemoteHost)
   141  	}
   142  	if l.UserAgent != "" {
   143  		fmt.Fprintf(b, "\n%s UserAgent: %s", hostStr, l.UserAgent)
   144  	}
   145  	if l.Trace != nil {
   146  		if l.Trace.Message != "" {
   147  			fmt.Fprintf(b, "\n%s Error: %s", hostStr, console.Colorize("LogMessage", l.Trace.Message))
   148  		}
   149  		if l.Trace.Variables != nil {
   150  			for key, value := range l.Trace.Variables {
   151  				if value != "" {
   152  					fmt.Fprintf(b, "\n%s %s=%s", hostStr, key, value)
   153  				}
   154  			}
   155  		}
   156  		if l.Trace.Source != nil {
   157  			traceLength := len(l.Trace.Source)
   158  			for i, element := range l.Trace.Source {
   159  				fmt.Fprintf(b, "\n%s %8v: %s", hostStr, traceLength-i, element)
   160  			}
   161  		}
   162  	}
   163  	logMsg := strings.TrimPrefix(b.String(), "\n")
   164  	return fmt.Sprintf("%s\n", logMsg)
   165  }
   166  
   167  // mainAdminLogs - the entry function of admin logs
   168  func mainAdminLogs(ctx *cli.Context) error {
   169  	// Check for command syntax
   170  	checkLogsShowSyntax(ctx)
   171  	console.SetColor("LogMessage", color.New(color.Bold, color.FgRed))
   172  	console.SetColor("Api", color.New(color.Bold, color.FgWhite))
   173  	for _, c := range colors {
   174  		console.SetColor(fmt.Sprintf("Node%d", c), color.New(c))
   175  	}
   176  	aliasedURL := ctx.Args().Get(0)
   177  	var node string
   178  	if len(ctx.Args()) > 1 {
   179  		node = ctx.Args().Get(1)
   180  	}
   181  	var last int
   182  	if ctx.IsSet("last") {
   183  		last = ctx.Int("last")
   184  		if last <= 0 {
   185  			fatalIf(errInvalidArgument().Trace(ctx.Args()...), "please set a proper limit, for example: '--last 5' to display last 5 logs, omit this flag to display all available logs")
   186  		}
   187  	}
   188  	logType := strings.ToLower(ctx.String("type"))
   189  	if logType != "minio" && logType != "application" && logType != "all" {
   190  		fatalIf(errInvalidArgument().Trace(ctx.Args()...), "Invalid value for --type flag. Valid options are [minio, application, all]")
   191  	}
   192  	// Create a new MinIO Admin Client
   193  	client, err := newAdminClient(aliasedURL)
   194  	if err != nil {
   195  		fatalIf(err.Trace(aliasedURL), "Unable to initialize admin client.")
   196  		return nil
   197  	}
   198  
   199  	ctxt, cancel := context.WithCancel(globalContext)
   200  	defer cancel()
   201  
   202  	// Start listening on all console log activity.
   203  	logCh := client.GetLogs(ctxt, node, last, logType)
   204  	for logInfo := range logCh {
   205  		if logInfo.Err != nil {
   206  			fatalIf(probe.NewError(logInfo.Err), "Unable to listen to console logs")
   207  		}
   208  		// drop nodeName from output if specified as cli arg
   209  		if node != "" {
   210  			logInfo.NodeName = ""
   211  		}
   212  		if logInfo.DeploymentID != "" {
   213  			printMsg(logMessage{LogInfo: logInfo})
   214  		}
   215  	}
   216  	return nil
   217  }