func (s *DebugLogSuite) TestTailFallback(c *gc.C) { s.PatchValue(&runSSHCommand, func(sshCmd *SSHCommand, ctx *cmd.Context) error { fmt.Fprintf(ctx.Stdout, "%s", sshCmd.Args) return nil }) s.PatchValue(&getDebugLogAPI, func(envName string) (DebugLogAPI, error) { return &fakeDebugLogAPI{err: errors.NotSupportedf("testing")}, nil }) ctx, err := testing.RunCommand(c, &DebugLogCommand{}, []string{"-n", "100"}) c.Assert(err, gc.IsNil) c.Check(testing.Stderr(ctx), gc.Equals, "Server does not support new stream log, falling back to tail\n") c.Check(testing.Stdout(ctx), gc.Equals, "[tail -n -100 -f /var/log/juju/all-machines.log]") }
// WatchDebugLog returns a ReadCloser that the caller can read the log // lines from. Only log lines that match the filtering specified in // the DebugLogParams are returned. It returns an error that satisfies // errors.IsNotImplemented when the API server does not support the // end-point. // // TODO(dimitern) We already have errors.IsNotImplemented - why do we // need to define a different error for this purpose here? func (c *Client) WatchDebugLog(args DebugLogParams) (io.ReadCloser, error) { // The websocket connection just hangs if the server doesn't have the log // end point. So do a version check, as version was added at the same time // as the remote end point. _, err := c.AgentVersion() if err != nil { return nil, errors.NotSupportedf("WatchDebugLog") } // Prepare URL. attrs := url.Values{} if args.Replay { attrs.Set("replay", fmt.Sprint(args.Replay)) } if args.Limit > 0 { attrs.Set("maxLines", fmt.Sprint(args.Limit)) } if args.Backlog > 0 { attrs.Set("backlog", fmt.Sprint(args.Backlog)) } if args.Level != loggo.UNSPECIFIED { attrs.Set("level", fmt.Sprint(args.Level)) } attrs["includeEntity"] = args.IncludeEntity attrs["includeModule"] = args.IncludeModule attrs["excludeEntity"] = args.ExcludeEntity attrs["excludeModule"] = args.ExcludeModule target := url.URL{ Scheme: "wss", Host: c.st.addr, Path: "/log", RawQuery: attrs.Encode(), } cfg, err := websocket.NewConfig(target.String(), "http://localhost/") cfg.Header = utils.BasicAuthHeader(c.st.tag, c.st.password) cfg.TlsConfig = &tls.Config{RootCAs: c.st.certPool, ServerName: "anything"} connection, err := websocketDialConfig(cfg) if err != nil { return nil, err } // Read the initial error and translate to a real error. // Read up to the first new line character. We can't use bufio here as it // reads too much from the reader. line := make([]byte, 4096) n, err := connection.Read(line) if err != nil { return nil, fmt.Errorf("unable to read initial response: %v", err) } line = line[0:n] logger.Debugf("initial line: %q", line) var errResult params.ErrorResult err = json.Unmarshal(line, &errResult) if err != nil { return nil, fmt.Errorf("unable to unmarshal initial response: %v", err) } if errResult.Error != nil { return nil, errResult.Error } return connection, nil }