main.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "log"
  6. "net"
  7. "net/url"
  8. "os"
  9. "os/signal"
  10. "strconv"
  11. "time"
  12. "git.lattuga.net/boyska/circolog/filtering"
  13. "git.lattuga.net/boyska/circolog/formatter"
  14. "github.com/gorilla/websocket"
  15. isatty "github.com/mattn/go-isatty"
  16. "github.com/mgutz/ansi"
  17. "gopkg.in/mcuadros/go-syslog.v2/format"
  18. "gopkg.in/mgo.v2/bson"
  19. )
  20. func main() {
  21. addr := flag.String("addr", "localhost:9080", "http service address")
  22. querySocket := flag.String("socket", "", "Path to a unix domain socket for the HTTP server")
  23. backlogLimit := flag.Int("n", -1, "Limit the backlog length, defaults to no limit (-1)")
  24. var filter filtering.ExprValue
  25. flag.Var(&filter, "where", "sql-like query to filter logs")
  26. // TODO: change to color-mode=auto/no/always
  27. noColor := flag.Bool("no-color", false, "disable colors")
  28. forceColor := flag.Bool("force-color", false, "force colors even on TTY")
  29. flag.Parse()
  30. if *noColor && *forceColor {
  31. fmt.Fprintln(os.Stderr, "Can't use both -no-color and -force-color")
  32. flag.Usage()
  33. os.Exit(2)
  34. }
  35. if *noColor || (!isatty.IsTerminal(os.Stdout.Fd()) && !*forceColor) {
  36. ansi.DisableColors(true)
  37. }
  38. interrupt := make(chan os.Signal, 1)
  39. signal.Notify(interrupt, os.Interrupt)
  40. var d *websocket.Dialer
  41. u := url.URL{Scheme: "ws",
  42. Host: *addr, // ignored in case of -socket; see the Dialer below
  43. Path: "/ws",
  44. }
  45. q := u.Query()
  46. q.Set("fmt", "bson")
  47. if *backlogLimit >= 0 {
  48. q.Set("l", strconv.Itoa(*backlogLimit))
  49. }
  50. u.RawQuery = q.Encode()
  51. if *querySocket != "" {
  52. d = &websocket.Dialer{
  53. NetDial: func(network, addr string) (net.Conn, error) {
  54. return net.Dial("unix", *querySocket)
  55. },
  56. HandshakeTimeout: 45 * time.Second, // same as DefaultDialer
  57. }
  58. log.Printf("connecting to %s", *querySocket)
  59. } else {
  60. d = websocket.DefaultDialer
  61. log.Printf("connecting to %s", *addr)
  62. }
  63. c, _, err := d.Dial(u.String(), nil)
  64. if err != nil {
  65. log.Fatal("dial:", err)
  66. }
  67. defer c.Close()
  68. log.Println("connected!", u.String())
  69. done := make(chan struct{})
  70. go func() {
  71. defer close(done)
  72. for {
  73. _, serialized, err := c.ReadMessage()
  74. if err != nil {
  75. log.Println("close:", err)
  76. return
  77. }
  78. var parsed format.LogParts
  79. if err := bson.Unmarshal(serialized, &parsed); err != nil {
  80. log.Println("invalid BSON", err)
  81. continue
  82. }
  83. if !filter.Validate(parsed) {
  84. continue
  85. }
  86. if err := formatter.WriteFormatted(os.Stdout, formatter.FormatSyslog, parsed); err != nil {
  87. log.Println("error printing", err)
  88. }
  89. fmt.Println()
  90. }
  91. }()
  92. for {
  93. select {
  94. case <-done:
  95. return
  96. case <-interrupt:
  97. log.Println("interrupt")
  98. // Cleanly close the connection by sending a close message and then waiting (with timeout) for the
  99. // server to close the connection.
  100. err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
  101. if err != nil {
  102. log.Println("write close:", err)
  103. return
  104. }
  105. select {
  106. case <-done:
  107. log.Println("Successfully close")
  108. case <-time.After(1 * time.Second):
  109. log.Println("Forced close")
  110. }
  111. return
  112. }
  113. }
  114. }