main.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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/data"
  13. "git.lattuga.net/boyska/circolog/filtering"
  14. "git.lattuga.net/boyska/circolog/formatter"
  15. "github.com/gorilla/websocket"
  16. isatty "github.com/mattn/go-isatty"
  17. "github.com/mgutz/ansi"
  18. "gopkg.in/mgo.v2/bson"
  19. )
  20. type BoolAuto uint
  21. const (
  22. BoolAuto_NO BoolAuto = iota
  23. BoolAuto_YES BoolAuto = iota
  24. BoolAuto_AUTO BoolAuto = iota
  25. )
  26. func (b *BoolAuto) String() string {
  27. switch *b {
  28. case BoolAuto_NO:
  29. return "no"
  30. case BoolAuto_YES:
  31. return "always"
  32. case BoolAuto_AUTO:
  33. return "auto"
  34. }
  35. return ""
  36. }
  37. func (b *BoolAuto) Set(s string) error {
  38. switch s {
  39. case "auto":
  40. *b = BoolAuto_AUTO
  41. case "always":
  42. *b = BoolAuto_YES
  43. case "no":
  44. *b = BoolAuto_NO
  45. default:
  46. return fmt.Errorf("Invalid value %s", s)
  47. }
  48. return nil
  49. }
  50. func main() {
  51. queryAddr := flag.String("addr", "", "http service address")
  52. querySocket := flag.String("socket", "/tmp/circologd-query.sock", "Path to a unix domain socket for the HTTP server")
  53. backlogLimit := flag.Int("n", -1, "Limit the backlog length, defaults to no limit (-1)")
  54. var format formatter.Format
  55. format = formatter.FormatSyslog
  56. flag.Var(&format, "fmt", "Output format [syslog|json]")
  57. var filter filtering.ExprValue
  58. flag.Var(&filter, "where", "sql-like query to filter logs")
  59. // TODO: change to color-mode=auto/no/always
  60. hasColor := BoolAuto_AUTO
  61. flag.Var(&hasColor, "color", "dis/enable colors; yes/no/auto")
  62. flag.Parse()
  63. if hasColor == BoolAuto_NO || (!isatty.IsTerminal(os.Stdout.Fd()) && hasColor != BoolAuto_YES) {
  64. ansi.DisableColors(true)
  65. }
  66. interrupt := make(chan os.Signal, 1)
  67. signal.Notify(interrupt, os.Interrupt)
  68. var d *websocket.Dialer
  69. u := url.URL{Scheme: "ws",
  70. Host: *queryAddr, // ignored in case of -socket; see the Dialer below
  71. Path: "/ws",
  72. }
  73. q := u.Query()
  74. q.Set("fmt", "bson")
  75. if *backlogLimit >= 0 {
  76. q.Set("l", strconv.Itoa(*backlogLimit))
  77. }
  78. u.RawQuery = q.Encode()
  79. if *queryAddr == "" {
  80. d = &websocket.Dialer{
  81. NetDial: func(network, addr string) (net.Conn, error) {
  82. return net.Dial("unix", *querySocket)
  83. },
  84. HandshakeTimeout: 45 * time.Second, // same as DefaultDialer
  85. }
  86. log.Printf("connecting to %s", *querySocket)
  87. } else {
  88. d = websocket.DefaultDialer
  89. log.Printf("connecting to %s", *queryAddr)
  90. }
  91. c, _, err := d.Dial(u.String(), nil)
  92. if err != nil {
  93. log.Fatal("dial:", err)
  94. }
  95. defer c.Close()
  96. log.Println("connected!", u.String())
  97. done := make(chan struct{})
  98. go func() {
  99. defer close(done)
  100. for {
  101. _, serialized, err := c.ReadMessage()
  102. if err != nil {
  103. log.Println("close:", err)
  104. return
  105. }
  106. var parsed data.Message
  107. if err := bson.Unmarshal(serialized, &parsed); err != nil {
  108. log.Println("invalid BSON", err)
  109. continue
  110. }
  111. if !filter.Validate(parsed) {
  112. continue
  113. }
  114. if err := formatter.WriteFormatted(os.Stdout, format, parsed); err != nil {
  115. log.Println("error printing", err)
  116. }
  117. if format == formatter.FormatSyslog { // oops
  118. fmt.Println()
  119. }
  120. }
  121. }()
  122. for {
  123. select {
  124. case <-done:
  125. return
  126. case <-interrupt:
  127. log.Println("interrupt")
  128. // Cleanly close the connection by sending a close message and then waiting (with timeout) for the
  129. // server to close the connection.
  130. err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
  131. if err != nil {
  132. log.Println("write close:", err)
  133. return
  134. }
  135. select {
  136. case <-done:
  137. log.Println("Successfully close")
  138. case <-time.After(5 * time.Second):
  139. log.Println("Forced close")
  140. }
  141. return
  142. }
  143. }
  144. }