package main import ( "flag" "fmt" "log" "net" "net/url" "os" "os/signal" "strconv" "time" "git.lattuga.net/boyska/circolog/formatter" "github.com/araddon/qlbridge/datasource" "github.com/araddon/qlbridge/expr" "github.com/araddon/qlbridge/value" "github.com/araddon/qlbridge/vm" "github.com/gorilla/websocket" "gopkg.in/mcuadros/go-syslog.v2/format" "gopkg.in/mgo.v2/bson" ) type ExprValue struct { Node expr.Node } func (e *ExprValue) String() string { if e.Node != nil { return e.Node.String() } else { return "" } } func (e *ExprValue) Set(value string) error { ast, err := expr.ParseExpression(value) if err != nil { return err } e.Node = ast return nil } func main() { addr := flag.String("addr", "localhost:9080", "http service address") querySocket := flag.String("socket", "", "Path to a unix domain socket for the HTTP server") backlogLimit := flag.Int("n", -1, "Limit the backlog length, defaults to no limit (-1)") var filter ExprValue flag.Var(&filter, "where", "sql-like query to filter logs") flag.Parse() interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) var d *websocket.Dialer u := url.URL{Scheme: "ws", Host: *addr, // ignored in case of -socket; see the Dialer below Path: "/ws", } q := u.Query() q.Set("fmt", "bson") if *backlogLimit >= 0 { q.Set("l", strconv.Itoa(*backlogLimit)) } u.RawQuery = q.Encode() if *querySocket != "" { d = &websocket.Dialer{ NetDial: func(network, addr string) (net.Conn, error) { return net.Dial("unix", *querySocket) }, HandshakeTimeout: 45 * time.Second, // same as DefaultDialer } log.Printf("connecting to %s", *querySocket) } else { d = websocket.DefaultDialer log.Printf("connecting to %s", *addr) } c, _, err := d.Dial(u.String(), nil) if err != nil { log.Fatal("dial:", err) } defer c.Close() log.Println("connected!", u.String()) done := make(chan struct{}) go func() { defer close(done) for { _, serialized, err := c.ReadMessage() if err != nil { log.Println("close:", err) return } var parsed format.LogParts if err := bson.Unmarshal(serialized, &parsed); err != nil { log.Println("invalid BSON", err) continue } if filter.Node != nil { context := datasource.NewContextSimpleNative(parsed) val, ok := vm.Eval(context, filter.Node) if !ok || val == nil { // errors when evaluating continue } if val.Type() != value.BoolType { fmt.Fprintln(os.Stderr, "WARNING: The 'where' expression doesn't return a boolean") continue } if val.Value().(bool) != true { continue } } if err := formatter.WriteFormatted(os.Stdout, formatter.FormatSyslog, parsed); err != nil { log.Println("error printing", err) } fmt.Println() } }() for { select { case <-done: return case <-interrupt: log.Println("interrupt") // Cleanly close the connection by sending a close message and then waiting (with timeout) for the // server to close the connection. err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) if err != nil { log.Println("write close:", err) return } select { case <-done: log.Println("Successfully close") case <-time.After(1 * time.Second): log.Println("Forced close") } return } } }