diff --git a/cmd/logtool/main.go b/cmd/logtool/main.go index 51bf6117..1e53de47 100644 --- a/cmd/logtool/main.go +++ b/cmd/logtool/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "net" "os" "github.com/Symantec/Dominator/lib/flags/loadflags" @@ -13,6 +14,10 @@ import ( ) var ( + excludeRegex = flag.String("excludeRegex", "", + "The exclude regular expression to filter out when watching (after include)") + includeRegex = flag.String("includeRegex", "", + "The include regular expression to filter for when watching") loggerHostname = flag.String("loggerHostname", "localhost", "Hostname of log server") loggerName = flag.String("loggerName", "", "Name of logger") @@ -28,21 +33,38 @@ func printUsage() { fmt.Fprintln(os.Stderr, " debug level args...") fmt.Fprintln(os.Stderr, " print args...") fmt.Fprintln(os.Stderr, " set-debug-level level") + fmt.Fprintln(os.Stderr, " watch level") } -type commandFunc func(*srpc.Client, []string, log.Logger) +type commandFunc func(clients []*srpc.Client, addrs, args []string, + logger log.Logger) type subcommand struct { - command string - minArgs int - maxArgs int - cmdFunc commandFunc + command string + minArgs int + maxArgs int + allowMultiClient bool + cmdFunc commandFunc } var subcommands = []subcommand{ - {"debug", 2, -1, debugSubcommand}, - {"print", 1, -1, printSubcommand}, - {"set-debug-level", 1, 1, setDebugLevelSubcommand}, + {"debug", 2, -1, false, debugSubcommand}, + {"print", 1, -1, false, printSubcommand}, + {"set-debug-level", 1, 1, false, setDebugLevelSubcommand}, + {"watch", 1, 1, true, watchSubcommand}, +} + +func dialAll(addrs []string) ([]*srpc.Client, error) { + clients := make([]*srpc.Client, 0, len(addrs)) + for _, addr := range addrs { + clientName := fmt.Sprintf("%s:%d", addr, *loggerPortNum) + if client, err := srpc.DialHTTP("tcp", clientName, 0); err != nil { + return nil, err + } else { + clients = append(clients, client) + } + } + return clients, nil } func main() { @@ -60,10 +82,12 @@ func main() { if err := setupclient.SetupTls(true); err != nil { logger.Fatalln(err) } - clientName := fmt.Sprintf("%s:%d", *loggerHostname, *loggerPortNum) - client, err := srpc.DialHTTP("tcp", clientName, 0) + addrs, err := net.LookupHost(*loggerHostname) if err != nil { - logger.Fatalf("Error dialing: %s\n", err) + logger.Fatalln(err) + } + if len(addrs) < 1 { + logger.Fatalf("no addresses for: %s\n", *loggerHostname) } numSubcommandArgs := flag.NArg() - 1 for _, subcommand := range subcommands { @@ -74,7 +98,15 @@ func main() { printUsage() os.Exit(2) } - subcommand.cmdFunc(client, flag.Args()[1:], logger) + if len(addrs) > 1 && !subcommand.allowMultiClient { + logger.Fatalf("%s does not support multiple endpoints\n", + flag.Arg(0)) + } + clients, err := dialAll(addrs) + if err != nil { + logger.Fatalln(err) + } + subcommand.cmdFunc(clients, addrs, flag.Args()[1:], logger) os.Exit(3) } } diff --git a/cmd/logtool/remoteLog.go b/cmd/logtool/remoteLog.go index a17a4190..b9d3f0f5 100644 --- a/cmd/logtool/remoteLog.go +++ b/cmd/logtool/remoteLog.go @@ -9,12 +9,13 @@ import ( proto "github.com/Symantec/Dominator/proto/logger" ) -func debugSubcommand(client *srpc.Client, args []string, logger log.Logger) { +func debugSubcommand(clients []*srpc.Client, addrs, args []string, + logger log.Logger) { level, err := strconv.ParseUint(args[0], 10, 8) if err != nil { logger.Fatalf("Error parsing level: %s\n", err) } - if err := debug(client, uint8(level), args[1:]); err != nil { + if err := debug(clients[0], uint8(level), args[1:]); err != nil { logger.Fatalf("Error sending debug log: %s\n", err) } os.Exit(0) @@ -30,8 +31,9 @@ func debug(client *srpc.Client, level uint8, args []string) error { return client.RequestReply("Logger.Debug", request, &reply) } -func printSubcommand(client *srpc.Client, args []string, logger log.Logger) { - if err := print(client, args); err != nil { +func printSubcommand(clients []*srpc.Client, addrs, args []string, + logger log.Logger) { + if err := print(clients[0], args); err != nil { logger.Fatalf("Error sending log: %s\n", err) } os.Exit(0) diff --git a/cmd/logtool/setDebugLevel.go b/cmd/logtool/setDebugLevel.go index 29fa727a..b6d22665 100644 --- a/cmd/logtool/setDebugLevel.go +++ b/cmd/logtool/setDebugLevel.go @@ -9,13 +9,13 @@ import ( proto "github.com/Symantec/Dominator/proto/logger" ) -func setDebugLevelSubcommand(client *srpc.Client, args []string, +func setDebugLevelSubcommand(clients []*srpc.Client, addrs, args []string, logger log.Logger) { level, err := strconv.ParseInt(args[0], 10, 16) if err != nil { logger.Fatalf("Error parsing level: %s\n", err) } - if err := setDebugLevel(client, int16(level)); err != nil { + if err := setDebugLevel(clients[0], int16(level)); err != nil { logger.Fatalf("Error setting debug level: %s\n", err) } os.Exit(0) diff --git a/cmd/logtool/watch.go b/cmd/logtool/watch.go new file mode 100644 index 00000000..932aaac7 --- /dev/null +++ b/cmd/logtool/watch.go @@ -0,0 +1,108 @@ +package main + +import ( + "encoding/gob" + "fmt" + "io" + "os" + "strconv" + "strings" + + "github.com/Symantec/Dominator/lib/errors" + "github.com/Symantec/Dominator/lib/log" + "github.com/Symantec/Dominator/lib/srpc" + proto "github.com/Symantec/Dominator/proto/logger" +) + +func watchSubcommand(clients []*srpc.Client, addrs, args []string, + logger log.Logger) { + level, err := strconv.ParseInt(args[0], 10, 16) + if err != nil { + logger.Fatalf("Error parsing level: %s\n", err) + } + if err != nil { + logger.Fatalf("Error dialing: %s\n", err) + } + if err := watchAll(clients, addrs, int16(level)); err != nil { + logger.Fatalf("Error watching: %s\n", err) + } + os.Exit(0) +} + +func watchAll(clients []*srpc.Client, addrs []string, level int16) error { + if len(clients) == 1 { + return watchOne(clients[0], level, "") + } + maxWidth := 0 + for _, addr := range addrs { + if len(addr) > maxWidth { + maxWidth = len(addr) + } + } + errors := make(chan error, 1) + for index, client := range clients { + prefix := addrs[index] + if len(prefix) < maxWidth { + prefix += strings.Repeat(" ", maxWidth-len(prefix)) + } + go func(client *srpc.Client, level int16, prefix string) { + errors <- watchOne(client, level, prefix) + }(client, level, prefix) + } + for range clients { + if err := <-errors; err != nil { + return err + } + } + return nil +} + +func watchOne(client *srpc.Client, level int16, prefix string) error { + request := proto.WatchRequest{ + ExcludeRegex: *excludeRegex, + IncludeRegex: *includeRegex, + Name: *loggerName, + DebugLevel: level, + } + if conn, err := client.Call("Logger.Watch"); err != nil { + return err + } else { + defer conn.Close() + encoder := gob.NewEncoder(conn) + if err := encoder.Encode(request); err != nil { + return err + } + if err := conn.Flush(); err != nil { + return err + } + decoder := gob.NewDecoder(conn) + var response proto.WatchResponse + if err := decoder.Decode(&response); err != nil { + return fmt.Errorf("error decoding: %s", err) + } + if response.Error != "" { + return errors.New(response.Error) + } + if prefix == "" { + _, err := io.Copy(os.Stdout, conn) + return err + } + for { + line, err := conn.ReadString('\n') + if len(line) > 0 { + if prefix != "" { + line = prefix + " " + line + } + if _, err := os.Stdout.Write([]byte(line)); err != nil { + return err + } + } + if err != nil { + if err == io.EOF { + return nil + } + return err + } + } + } +} diff --git a/lib/log/serverlogger/api.go b/lib/log/serverlogger/api.go index 3b2b0d5e..030a41fa 100644 --- a/lib/log/serverlogger/api.go +++ b/lib/log/serverlogger/api.go @@ -20,7 +20,7 @@ var ( ) type Logger struct { - accessChecker func(authInfo *srpc.AuthInformation) bool + accessChecker func(method string, authInfo *srpc.AuthInformation) bool circularBuffer *logbuf.LogBuffer flags int level int16 @@ -154,7 +154,7 @@ func (l *Logger) Println(v ...interface{}) { // called for the Logger. This allows the application to control which users or // groups are permitted to remotely control the Logger. func (l *Logger) SetAccessChecker( - accessChecker func(authInfo *srpc.AuthInformation) bool) { + accessChecker func(method string, authInfo *srpc.AuthInformation) bool) { l.accessChecker = accessChecker } diff --git a/lib/log/serverlogger/impl.go b/lib/log/serverlogger/impl.go index 186743c4..74c2bd45 100644 --- a/lib/log/serverlogger/impl.go +++ b/lib/log/serverlogger/impl.go @@ -6,6 +6,7 @@ import ( "log" "os" "regexp" + "runtime" "strings" "sync" "time" @@ -42,6 +43,18 @@ func init() { srpc.RegisterName("Logger", loggerMap) } +func getCallerName(depth int) string { + if pc, _, _, ok := runtime.Caller(depth); !ok { + return "UNKNOWN" + } else if fi := runtime.FuncForPC(pc); fi == nil { + return "UNKNOWN" + } else if splitName := strings.Split(fi.Name(), "."); len(splitName) < 1 { + return "UNKNOWN" + } else { + return splitName[len(splitName)-1] + } +} + func (w *grabWriter) Write(p []byte) (int, error) { w.data = p return len(p), nil @@ -78,7 +91,7 @@ func (l *Logger) checkAuth(authInfo *srpc.AuthInformation) error { } if accessChecker := l.accessChecker; accessChecker == nil { return errors.New("no access to resource") - } else if accessChecker(authInfo) { + } else if accessChecker(getCallerName(3), authInfo) { return nil } else { return errors.New("no access to resource")