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 }