func main() { runtime.GOMAXPROCS(1) debugMode = getopt("DEBUG", "") != "" port := getopt("PORT", "8000") endpoint := getopt("DOCKER_HOST", "unix:///var/run/docker.sock") routespath := getopt("ROUTESPATH", "/var/lib/logspout") client, err := docker.NewClient(endpoint) assert(err, "docker") attacher := NewAttachManager(client) router := NewRouteManager(attacher) // HACK: if we are connecting to etcd, get the logger's connection // details from there if etcdHost := os.Getenv("ETCD_HOST"); etcdHost != "" { connectionString := []string{"http://" + etcdHost + ":4001"} debug("etcd:", connectionString[0]) etcd := etcd.NewClient(connectionString) etcd.SetDialTimeout(3 * time.Second) hostResp, err := etcd.Get("/deis/logs/host", false, false) assert(err, "url") portResp, err := etcd.Get("/deis/logs/port", false, false) assert(err, "url") protocol := getEtcdValueOrDefault(etcd, "/deis/logs/protocol", "udp") host := fmt.Sprintf("%s:%s", hostResp.Node.Value, portResp.Node.Value) log.Printf("routing all to %s://%s", protocol, host) router.Add(&Route{Target: Target{Type: "syslog", Addr: host, Protocol: protocol}}) } if len(os.Args) > 1 { u, err := url.Parse(os.Args[1]) assert(err, "url") log.Println("routing all to " + os.Args[1]) router.Add(&Route{Target: Target{Type: u.Scheme, Addr: u.Host}}) } if _, err := os.Stat(routespath); err == nil { log.Println("loading and persisting routes in " + routespath) assert(router.Load(RouteFileStore(routespath)), "persistor") } m := martini.Classic() m.Get("/logs(?:/(?P<predicate>[a-zA-Z]+):(?P<value>.+))?", func(w http.ResponseWriter, req *http.Request, params martini.Params) { source := new(Source) switch { case params["predicate"] == "id" && params["value"] != "": source.ID = params["value"][:12] case params["predicate"] == "name" && params["value"] != "": source.Name = params["value"] case params["predicate"] == "filter" && params["value"] != "": source.Filter = params["value"] } if source.ID != "" && attacher.Get(source.ID) == nil { http.NotFound(w, req) return } logstream := make(chan *Log) defer close(logstream) var closer <-chan bool if req.Header.Get("Upgrade") == "websocket" { closerBi := make(chan bool) go websocketStreamer(w, req, logstream, closerBi) closer = closerBi } else { go httpStreamer(w, req, logstream, source.All() || source.Filter != "") closer = w.(http.CloseNotifier).CloseNotify() } attacher.Listen(source, logstream, closer) }) m.Get("/routes", func(w http.ResponseWriter, req *http.Request) { w.Header().Add("Content-Type", "application/json") routes, _ := router.GetAll() w.Write(append(marshal(routes), '\n')) }) m.Post("/routes", func(w http.ResponseWriter, req *http.Request) (int, string) { route := new(Route) if err := unmarshal(req.Body, route); err != nil { return http.StatusBadRequest, "Bad request: " + err.Error() } // TODO: validate? router.Add(route) w.Header().Add("Content-Type", "application/json") return http.StatusCreated, string(append(marshal(route), '\n')) }) m.Get("/routes/:id", func(w http.ResponseWriter, req *http.Request, params martini.Params) { route, _ := router.Get(params["id"]) if route == nil { http.NotFound(w, req) return } w.Write(append(marshal(route), '\n')) }) m.Delete("/routes/:id", func(w http.ResponseWriter, req *http.Request, params martini.Params) { if ok := router.Remove(params["id"]); !ok { http.NotFound(w, req) } }) log.Println("logspout serving http on :" + port) log.Fatal(http.ListenAndServe(":"+port, m)) }
func main() { runtime.GOMAXPROCS(1) debugMode = getopt("DEBUG", "") != "" port := getopt("PORT", "8000") endpoint := getopt("DOCKER_HOST", "unix:///var/run/docker.sock") routespath := getopt("ROUTESPATH", "/var/lib/logspout") client, err := docker.NewClient(endpoint) assert(err, "docker") attacher := NewAttachManager(client) router := NewRouteManager(attacher) // HACK: if we are connecting to etcd, get the logger's connection // details from there if etcdHost := os.Getenv("ETCD_HOST"); etcdHost != "" { connectionString := []string{"http://" + etcdHost + ":4001"} debug("etcd:", connectionString[0]) etcd := etcd.NewClient(connectionString) etcd.SetDialTimeout(3 * time.Second) router.Add(getEtcdRoute(etcd)) go func() { for { // NOTE(bacongobbler): sleep for a bit before doing the discovery loop again time.Sleep(10 * time.Second) newRoute := getEtcdRoute(etcd) oldRoute, err := router.Get(newRoute.ID) // router.Get only returns an error if the route doesn't exist. If it does, // then we can skip this check and just add the new route to the routing table if err == nil && newRoute.Target.Protocol == oldRoute.Target.Protocol && newRoute.Target.Addr == oldRoute.Target.Addr { // NOTE(bacongobbler): the two targets are the same; perform a no-op continue } // NOTE(bacongobbler): this operation is a no-op if the route doesn't exist router.Remove(oldRoute.ID) router.Add(newRoute) } }() } if len(os.Args) > 1 { u, err := url.Parse(os.Args[1]) assert(err, "url") log.Println("routing all to " + os.Args[1]) router.Add(&Route{Target: Target{Type: u.Scheme, Addr: u.Host}}) } if _, err := os.Stat(routespath); err == nil { log.Println("loading and persisting routes in " + routespath) assert(router.Load(RouteFileStore(routespath)), "persistor") } m := martini.Classic() m.Get("/logs(?:/(?P<predicate>[a-zA-Z]+):(?P<value>.+))?", func(w http.ResponseWriter, req *http.Request, params martini.Params) { source := new(Source) switch { case params["predicate"] == "id" && params["value"] != "": source.ID = params["value"][:12] case params["predicate"] == "name" && params["value"] != "": source.Name = params["value"] case params["predicate"] == "filter" && params["value"] != "": source.Filter = params["value"] } if source.ID != "" && attacher.Get(source.ID) == nil { http.NotFound(w, req) return } logstream := make(chan *Log) defer close(logstream) var closer <-chan bool if req.Header.Get("Upgrade") == "websocket" { closerBi := make(chan bool) go websocketStreamer(w, req, logstream, closerBi) closer = closerBi } else { go httpStreamer(w, req, logstream, source.All() || source.Filter != "") closer = w.(http.CloseNotifier).CloseNotify() } attacher.Listen(source, logstream, closer) }) m.Get("/routes", func(w http.ResponseWriter, req *http.Request) { w.Header().Add("Content-Type", "application/json") routes, _ := router.GetAll() w.Write(append(marshal(routes), '\n')) }) m.Post("/routes", func(w http.ResponseWriter, req *http.Request) (int, string) { route := new(Route) if err := unmarshal(req.Body, route); err != nil { return http.StatusBadRequest, "Bad request: " + err.Error() } // TODO: validate? router.Add(route) w.Header().Add("Content-Type", "application/json") return http.StatusCreated, string(append(marshal(route), '\n')) }) m.Get("/routes/:id", func(w http.ResponseWriter, req *http.Request, params martini.Params) { route, _ := router.Get(params["id"]) if route == nil { http.NotFound(w, req) return } w.Write(append(marshal(route), '\n')) }) m.Delete("/routes/:id", func(w http.ResponseWriter, req *http.Request, params martini.Params) { if ok := router.Remove(params["id"]); !ok { http.NotFound(w, req) } }) log.Println("logspout serving http on :" + port) log.Fatal(http.ListenAndServe(":"+port, m)) }