log.go 3.31 KB
Newer Older
1 2 3 4
package commands

import (
	"fmt"
5 6
	"io"
	"strings"
7

8 9
	cmds "github.com/ipfs/go-ipfs/commands"
	u "github.com/ipfs/go-ipfs/util"
10

11
	tail "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/ActiveState/tail"
12 13
)

14 15 16 17 18 19
// Golang os.Args overrides * and replaces the character argument with
// an array which includes every file in the user's CWD. As a
// workaround, we use 'all' instead. The util library still uses * so
// we convert it at this step.
var logAllKeyword = "all"

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
20
var LogCmd = &cmds.Command{
21 22 23 24 25 26 27 28 29 30
	Helptext: cmds.HelpText{
		Tagline: "Interact with the daemon log output",
		ShortDescription: `
'ipfs log' contains utility commands to affect or read the logging
output of a running daemon.
`,
	},

	Subcommands: map[string]*cmds.Command{
		"level": logLevelCmd,
31
		"tail":  logTailCmd,
32 33 34 35
	},
}

var logLevelCmd = &cmds.Command{
36 37 38
	Helptext: cmds.HelpText{
		Tagline: "Change the logging level",
		ShortDescription: `
39
'ipfs log level' is a utility command used to change the logging
40 41
output of a running daemon.
`,
42
	},
43

44
	Arguments: []cmds.Argument{
45 46 47
		// TODO use a different keyword for 'all' because all can theoretically
		// clash with a subsystem name
		cmds.StringArg("subsystem", true, false, fmt.Sprintf("the subsystem logging identifier. Use '%s' for all subsystems.", logAllKeyword)),
rht's avatar
rht committed
48
		cmds.StringArg("level", true, false, "one of: debug, info, warning, error, fatal, panic"),
49
	},
50
	Run: func(req cmds.Request, res cmds.Response) {
51

52
		args := req.Arguments()
53
		subsystem, level := args[0], args[1]
54 55 56 57 58 59

		if subsystem == logAllKeyword {
			subsystem = "*"
		}

		if err := u.SetLogLevel(subsystem, level); err != nil {
60 61
			res.SetError(err, cmds.ErrNormal)
			return
62 63
		}

64
		s := fmt.Sprintf("Changed log level of '%s' to '%s'", subsystem, level)
65
		log.Info(s)
66
		res.SetOutput(&MessageOutput{s})
67
	},
68 69
	Marshalers: cmds.MarshalerMap{
		cmds.Text: MessageTextMarshaler,
70
	},
71
	Type: MessageOutput{},
72
}
73

74
var logTailCmd = &cmds.Command{
75 76 77
	Helptext: cmds.HelpText{
		Tagline: "Read the logs",
		ShortDescription: `
78
'ipfs log tail' is a utility command used to read log output as it is written.
79 80 81
`,
	},

82
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
83
		path := fmt.Sprintf("%s/logs/events.log", req.InvocContext().ConfigRoot)
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

		outChan := make(chan interface{})

		go func() {
			defer close(outChan)

			t, err := tail.TailFile(path, tail.Config{
				Location:  &tail.SeekInfo{0, 2},
				Follow:    true,
				MustExist: true,
				Logger:    tail.DiscardingLogger,
			})
			if err != nil {
				fmt.Println(err.Error())
				return
			}
100
			defer t.Stop()
101

Jeromy's avatar
Jeromy committed
102
			done := req.Context().Done()
103

104
			for line := range t.Lines {
105 106 107 108 109 110 111
				// return when context closes
				select {
				case <-done:
					return
				default:
				}

112 113 114 115 116 117 118 119 120
				if line.Err != nil {
					fmt.Println(err.Error())
					return
				}
				// TODO: unpack the line text into a struct and output that
				outChan <- &MessageOutput{line.Text}
			}
		}()

121
		res.SetOutput((<-chan interface{})(outChan))
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
	},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
			outChan, ok := res.Output().(<-chan interface{})
			if !ok {
				return nil, u.ErrCast()
			}

			return &cmds.ChannelMarshaler{
				Channel: outChan,
				Marshaler: func(v interface{}) (io.Reader, error) {
					output := v.(*MessageOutput)
					return strings.NewReader(output.Message + "\n"), nil
				},
			}, nil
		},
	},
	Type: MessageOutput{},
}