github.com/vmware/govmomi@v0.51.0/cli/flags/debug.go (about) 1 // © Broadcom. All Rights Reserved. 2 // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. 3 // SPDX-License-Identifier: Apache-2.0 4 5 package flags 6 7 import ( 8 "bufio" 9 "context" 10 "flag" 11 "fmt" 12 "io" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "reflect" 17 "strings" 18 "sync" 19 "text/tabwriter" 20 "time" 21 22 "github.com/dougm/pretty" 23 24 "github.com/vmware/govmomi/vim25/debug" 25 "github.com/vmware/govmomi/vim25/soap" 26 "github.com/vmware/govmomi/vim25/types" 27 ) 28 29 type cmdFormat struct { 30 path string 31 err error 32 args []string 33 } 34 35 func (c *cmdFormat) lookPath(file string, args ...string) { 36 c.args = args 37 c.path, c.err = exec.LookPath(file) 38 } 39 40 func (c *cmdFormat) cmd() (*exec.Cmd, error) { 41 if c.err != nil { 42 return nil, c.err 43 } 44 return exec.Command(c.path, c.args...), nil 45 } 46 47 type DebugFlag struct { 48 common 49 50 enable bool 51 trace bool 52 verbose bool 53 dump bool 54 xml cmdFormat 55 json cmdFormat 56 } 57 58 var debugFlagKey = flagKey("debug") 59 60 func NewDebugFlag(ctx context.Context) (*DebugFlag, context.Context) { 61 if v := ctx.Value(debugFlagKey); v != nil { 62 return v.(*DebugFlag), ctx 63 } 64 65 v := &DebugFlag{} 66 ctx = context.WithValue(ctx, debugFlagKey, v) 67 return v, ctx 68 } 69 70 func (flag *DebugFlag) Verbose() bool { 71 return flag.verbose 72 } 73 74 func (flag *DebugFlag) Register(ctx context.Context, f *flag.FlagSet) { 75 flag.RegisterOnce(func() { 76 env := "GOVC_DEBUG" 77 enable := false 78 switch env := strings.ToLower(os.Getenv(env)); env { 79 case "1", "true": 80 enable = true 81 } 82 83 usage := fmt.Sprintf("Store debug logs [%s]", env) 84 f.BoolVar(&flag.enable, "debug", enable, usage) 85 f.BoolVar(&flag.trace, "trace", false, "Write SOAP/REST traffic to stderr") 86 f.BoolVar(&flag.verbose, "verbose", false, "Write request/response data to stderr") 87 }) 88 } 89 90 type cmdFormatCloser struct { 91 rc io.Closer 92 in io.Closer 93 cmd *exec.Cmd 94 wg *sync.WaitGroup 95 } 96 97 func (c *cmdFormatCloser) Close() error { 98 _ = c.rc.Close() 99 _ = c.in.Close() 100 c.wg.Wait() 101 return c.cmd.Wait() 102 } 103 104 func (flag *DebugFlag) newFormatReader(rc io.ReadCloser, w io.Writer, ext string) (io.ReadCloser, error) { 105 var err error 106 var cmd *exec.Cmd 107 108 switch ext { 109 case "json": 110 cmd, err = flag.json.cmd() 111 if err != nil { 112 return nil, err 113 } 114 case "xml": 115 cmd, err = flag.xml.cmd() 116 if err != nil { 117 return nil, err 118 } 119 default: 120 return nil, fmt.Errorf("unsupported type %s", ext) 121 } 122 123 cmd.Stderr = os.Stderr 124 125 stdin, err := cmd.StdinPipe() 126 if err != nil { 127 return nil, err 128 } 129 130 stdout, err := cmd.StdoutPipe() 131 if err != nil { 132 return nil, err 133 } 134 135 err = cmd.Start() 136 if err != nil { 137 return nil, err 138 } 139 140 var wg sync.WaitGroup 141 wg.Add(1) 142 go func() { 143 _, _ = io.Copy(w, stdout) 144 wg.Done() 145 }() 146 147 return debug.ReadCloser{ 148 Reader: io.TeeReader(rc, stdin), 149 Closer: &cmdFormatCloser{rc, stdin, cmd, &wg}, 150 }, nil 151 } 152 153 func (flag *DebugFlag) debugTrace(rc io.ReadCloser, w io.Writer, ext string) io.ReadCloser { 154 fr, err := flag.newFormatReader(rc, w, ext) 155 if err != nil { 156 return debug.NewTeeReader(rc, w) 157 } 158 return fr 159 } 160 161 func (flag *DebugFlag) Process(ctx context.Context) error { 162 // Base path for storing debug logs. 163 r := os.Getenv("GOVC_DEBUG_PATH") 164 165 if flag.trace { 166 if flag.verbose { 167 flag.dump = true // output req/res as Go code 168 return nil 169 } 170 r = "-" 171 flag.enable = true 172 if os.Getenv("GOVC_DEBUG_FORMAT") != "false" { 173 debugXML := os.Getenv("GOVC_DEBUG_XML") 174 if debugXML == "" { 175 debugXML = "xmlstarlet" 176 } 177 flag.xml.lookPath(debugXML, "fo") 178 179 debugJSON := os.Getenv("GOVC_DEBUG_JSON") 180 if debugJSON == "" { 181 debugJSON = "jq" 182 } 183 flag.json.lookPath(debugJSON, ".") 184 185 soap.Trace = flag.debugTrace 186 } 187 } 188 189 if !flag.enable { 190 return nil 191 } 192 193 return flag.ProcessOnce(func() error { 194 switch r { 195 case "-": 196 debug.SetProvider(&debug.LogProvider{}) 197 return nil 198 case "": 199 r = home 200 } 201 r = filepath.Join(r, "debug") 202 203 // Path for this particular run. 204 run := os.Getenv("GOVC_DEBUG_PATH_RUN") 205 if run == "" { 206 now := time.Now().Format("2006-01-02T15-04-05.999999999") 207 r = filepath.Join(r, now) 208 } else { 209 // reuse the same path 210 r = filepath.Join(r, run) 211 _ = os.RemoveAll(r) 212 } 213 214 err := os.MkdirAll(r, 0700) 215 if err != nil { 216 return err 217 } 218 219 p := debug.FileProvider{ 220 Path: r, 221 } 222 223 debug.SetProvider(&p) 224 return nil 225 }) 226 } 227 228 type dump struct { 229 roundTripper soap.RoundTripper 230 } 231 232 func (d *dump) RoundTrip(ctx context.Context, req, res soap.HasFault) error { 233 vreq := reflect.ValueOf(req).Elem().FieldByName("Req").Elem() 234 235 pretty.Fprintf(os.Stderr, "%# v\n", vreq.Interface()) 236 237 err := d.roundTripper.RoundTrip(ctx, req, res) 238 if err != nil { 239 if fault := res.Fault(); fault != nil { 240 pretty.Fprintf(os.Stderr, "%# v\n", fault) 241 } 242 return err 243 } 244 245 vres := reflect.ValueOf(res).Elem().FieldByName("Res").Elem() 246 pretty.Fprintf(os.Stderr, "%# v\n", vres.Interface()) 247 248 return nil 249 } 250 251 type verbose struct { 252 roundTripper soap.RoundTripper 253 } 254 255 func (*verbose) mor(ref types.ManagedObjectReference) string { 256 if strings.HasPrefix(ref.Value, "session") { 257 ref.Value = "session[...]" 258 return ref.String() 259 } 260 return ref.Value 261 } 262 263 func (*verbose) str(val reflect.Value) string { 264 if !val.IsValid() { 265 return "" 266 } 267 268 switch val.Kind() { 269 case reflect.Ptr, reflect.Interface: 270 if val.IsNil() { 271 return "nil" 272 } 273 } 274 275 p := "" 276 277 switch pval := val.Interface().(type) { 278 case fmt.Stringer: 279 p = pval.String() 280 case string: 281 if len(pval) > 45 { 282 pval = pval[:42] + "..." 283 } 284 p = fmt.Sprintf("%s", pval) 285 case []string: 286 p = fmt.Sprintf("%v", pval) 287 case []types.ManagedObjectReference: 288 refs := make([]string, len(pval)) 289 for i := range pval { 290 refs[i] = pval[i].Value 291 } 292 p = fmt.Sprintf("%v", refs) 293 default: 294 return "" 295 } 296 297 return p 298 } 299 300 func (v *verbose) value(val types.AnyType) string { 301 rval := reflect.ValueOf(val) 302 if rval.Kind() == reflect.Ptr && !rval.IsNil() { 303 rval = rval.Elem() 304 } 305 if rval.Kind() == reflect.Struct { 306 if strings.HasPrefix(rval.Type().Name(), "ArrayOf") { 307 rval = rval.Field(0) 308 val = rval.Interface() 309 } 310 } 311 s := v.str(rval) 312 if s != "" { 313 return s 314 } 315 return v.prettyPrint(val) 316 } 317 318 func (v *verbose) propertyValue(obj types.ManagedObjectReference, name string, pval types.AnyType) string { 319 val := v.value(pval) 320 if obj.Type != "Task" && !strings.HasPrefix(obj.Value, "session") { 321 if len(val) > 512 { 322 val = fmt.Sprintf("`govc collect -dump %s %s`", obj, name) 323 } 324 } 325 return fmt.Sprintf("%s\t%s:\t%s", v.mor(obj), name, val) 326 } 327 328 func (v *verbose) missingSet(o types.ManagedObjectReference, m []types.MissingProperty) []string { 329 var s []string 330 for _, p := range m { 331 s = append(s, fmt.Sprintf("%s\t%s:\t%s", v.mor(o), p.Path, v.prettyPrint(p.Fault.Fault))) 332 } 333 return s 334 } 335 336 func (v *verbose) updateSet(u *types.UpdateSet) []string { 337 var s []string 338 if u == nil { 339 return s 340 } 341 for _, f := range u.FilterSet { 342 for _, o := range f.ObjectSet { 343 for _, c := range o.ChangeSet { 344 s = append(s, v.propertyValue(o.Obj, c.Name, c.Val)) 345 } 346 s = append(s, v.missingSet(o.Obj, o.MissingSet)...) 347 } 348 } 349 return s 350 } 351 352 func (v *verbose) objectContent(content []types.ObjectContent) []string { 353 var s []string 354 for _, o := range content { 355 for _, p := range o.PropSet { 356 s = append(s, v.propertyValue(o.Obj, p.Name, p.Val)) 357 } 358 s = append(s, v.missingSet(o.Obj, o.MissingSet)...) 359 } 360 return s 361 } 362 363 func (v *verbose) prettyPrint(val any) string { 364 p := pretty.Sprintf("%# v\n", val) 365 var res []string 366 scanner := bufio.NewScanner(strings.NewReader(p)) 367 for scanner.Scan() { 368 line := scanner.Text() 369 if strings.Contains(line, "nil,") || strings.Contains(line, "(nil),") { 370 continue // nil pointer field 371 } 372 if strings.Contains(line, `"",`) { 373 continue // empty string field 374 } 375 if strings.Contains(line, `{},`) { 376 continue // empty embedded struct 377 } 378 if strings.Contains(line, "[context]") { 379 continue // noisy base64 encoded backtrace 380 } 381 res = append(res, line) 382 } 383 return strings.Join(res, "\n") 384 } 385 386 func (v *verbose) table(vals []string) { 387 tw := tabwriter.NewWriter(os.Stderr, 2, 0, 1, ' ', 0) 388 for _, val := range vals { 389 fmt.Fprintf(tw, "...%s\n", val) 390 } 391 tw.Flush() 392 } 393 394 func (v *verbose) RoundTrip(ctx context.Context, req, res soap.HasFault) error { 395 vreq := reflect.ValueOf(req).Elem().FieldByName("Req").Elem() 396 param := []string{""} 397 switch f := vreq.Field(0).Interface().(type) { 398 case types.ManagedObjectReference: 399 param[0] = v.mor(f) 400 default: 401 param[0] = fmt.Sprintf("%v", f) 402 } 403 404 for i := 1; i < vreq.NumField(); i++ { 405 val := vreq.Field(i) 406 407 if val.Kind() == reflect.Interface { 408 val = val.Elem() 409 } 410 411 p := v.str(val) 412 if p == "" { 413 switch val.Kind() { 414 case reflect.Ptr, reflect.Slice, reflect.Struct: 415 p = val.Type().String() 416 default: 417 p = fmt.Sprintf("%v", val.Interface()) 418 } 419 } 420 421 param = append(param, p) 422 } 423 424 fmt.Fprintf(os.Stderr, "%s(%s)...\n", vreq.Type().Name(), strings.Join(param, ", ")) 425 426 err := v.roundTripper.RoundTrip(ctx, req, res) 427 if err != nil { 428 if fault := res.Fault(); fault != nil { 429 fmt.Fprintln(os.Stderr, v.prettyPrint(fault)) 430 } else { 431 fmt.Fprintf(os.Stderr, "...%s\n", err) 432 } 433 return err 434 } 435 436 vres := reflect.ValueOf(res).Elem().FieldByName("Res").Elem() 437 ret := vres.FieldByName("Returnval") 438 var s any = "void" 439 440 if ret.IsValid() { 441 switch x := ret.Interface().(type) { 442 case types.ManagedObjectReference: 443 s = v.mor(x) 444 case *types.UpdateSet: 445 s = v.updateSet(x) 446 case []types.ObjectContent: 447 s = v.objectContent(x) 448 case fmt.Stringer: 449 s = x.String() 450 default: 451 s = v.value(x) 452 } 453 } 454 if vals, ok := s.([]string); ok { 455 v.table(vals) 456 } else { 457 fmt.Fprintf(os.Stderr, "...%s\n", s) 458 } 459 fmt.Fprintln(os.Stderr) 460 461 return err 462 }