main.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  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. addr := flag.String("addr", "localhost:9080", "http service address")
  52. querySocket := flag.String("socket", "", "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 filter filtering.ExprValue
  55. flag.Var(&filter, "where", "sql-like query to filter logs")
  56. // TODO: change to color-mode=auto/no/always
  57. hasColor := BoolAuto_AUTO
  58. flag.Var(&hasColor, "color", "dis/enable colors")
  59. flag.Parse()
  60. if hasColor == BoolAuto_NO || (!isatty.IsTerminal(os.Stdout.Fd()) && hasColor != BoolAuto_YES) {
  61. ansi.DisableColors(true)
  62. }
  63. interrupt := make(chan os.Signal, 1)
  64. signal.Notify(interrupt, os.Interrupt)
  65. var d *websocket.Dialer
  66. u := url.URL{Scheme: "ws",
  67. Host: *addr, // ignored in case of -socket; see the Dialer below
  68. Path: "/ws",
  69. }
  70. q := u.Query()
  71. q.Set("fmt", "bson")
  72. if *backlogLimit >= 0 {
  73. q.Set("l", strconv.Itoa(*backlogLimit))
  74. }
  75. u.RawQuery = q.Encode()
  76. if *querySocket != "" {
  77. d = &websocket.Dialer{
  78. NetDial: func(network, addr string) (net.Conn, error) {
  79. return net.Dial("unix", *querySocket)
  80. },
  81. HandshakeTimeout: 45 * time.Second, // same as DefaultDialer
  82. }
  83. log.Printf("connecting to %s", *querySocket)
  84. } else {
  85. d = websocket.DefaultDialer
  86. log.Printf("connecting to %s", *addr)
  87. }
  88. c, _, err := d.Dial(u.String(), nil)
  89. if err != nil {
  90. log.Fatal("dial:", err)
  91. }
  92. defer c.Close()
  93. log.Println("connected!", u.String())
  94. done := make(chan struct{})
  95. go func() {
  96. defer close(done)
  97. for {
  98. _, serialized, err := c.ReadMessage()
  99. if err != nil {
  100. log.Println("close:", err)
  101. return
  102. }
  103. var parsed data.Message
  104. if err := bson.Unmarshal(serialized, &parsed); err != nil {
  105. log.Println("invalid BSON", err)
  106. continue
  107. }
  108. if !filter.Validate(parsed) {
  109. continue
  110. }
  111. if err := formatter.WriteFormatted(os.Stdout, formatter.FormatSyslog, parsed); err != nil {
  112. log.Println("error printing", err)
  113. }
  114. fmt.Println()
  115. }
  116. }()
  117. for {
  118. select {
  119. case <-done:
  120. return
  121. case <-interrupt:
  122. log.Println("interrupt")
  123. // Cleanly close the connection by sending a close message and then waiting (with timeout) for the
  124. // server to close the connection.
  125. err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
  126. if err != nil {
  127. log.Println("write close:", err)
  128. return
  129. }
  130. select {
  131. case <-done:
  132. log.Println("Successfully close")
  133. case <-time.After(1 * time.Second):
  134. log.Println("Forced close")
  135. }
  136. return
  137. }
  138. }
  139. }