github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/clients/pkg/promtail/server/server.go (about) 1 package server 2 3 import ( 4 "flag" 5 "fmt" 6 "net/http" 7 "net/url" 8 "os" 9 "os/signal" 10 "path" 11 "sort" 12 "strings" 13 "syscall" 14 "text/template" 15 16 "github.com/felixge/fgprof" 17 "github.com/go-kit/log" 18 "github.com/go-kit/log/level" 19 "github.com/pkg/errors" 20 "github.com/prometheus/common/version" 21 serverww "github.com/weaveworks/common/server" 22 23 "github.com/grafana/loki/clients/pkg/promtail/server/ui" 24 "github.com/grafana/loki/clients/pkg/promtail/targets" 25 "github.com/grafana/loki/clients/pkg/promtail/targets/target" 26 ) 27 28 var ( 29 readinessProbeFailure = "Not ready: Unable to find any logs to tail. Please verify permissions, volumes, scrape_config, etc." 30 readinessProbeSuccess = []byte("Ready") 31 ) 32 33 type Server interface { 34 Shutdown() 35 Run() error 36 } 37 38 // Server embed weaveworks server with static file and templating capability 39 type PromtailServer struct { 40 *serverww.Server 41 log log.Logger 42 tms *targets.TargetManagers 43 externalURL *url.URL 44 healthCheckTarget bool 45 promtailCfg string 46 } 47 48 // Config extends weaveworks server config 49 type Config struct { 50 serverww.Config `yaml:",inline"` 51 ExternalURL string `yaml:"external_url"` 52 HealthCheckTarget *bool `yaml:"health_check_target"` 53 Disable bool `yaml:"disable"` 54 } 55 56 // RegisterFlags with prefix registers flags where every name is prefixed by 57 // prefix. If prefix is a non-empty string, prefix should end with a period. 58 func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) { 59 // NOTE: weaveworks server's config can't be registered with a prefix. 60 cfg.Config.RegisterFlags(f) 61 62 f.BoolVar(&cfg.Disable, prefix+"server.disable", false, "Disable the http and grpc server.") 63 } 64 65 // RegisterFlags adds the flags required to config this to the given FlagSet 66 func (cfg *Config) RegisterFlags(f *flag.FlagSet) { 67 cfg.RegisterFlagsWithPrefix("", f) 68 } 69 70 // New makes a new Server 71 func New(cfg Config, log log.Logger, tms *targets.TargetManagers, promtailCfg string) (Server, error) { 72 if cfg.Disable { 73 return newNoopServer(log), nil 74 } 75 wws, err := serverww.New(cfg.Config) 76 if err != nil { 77 return nil, err 78 } 79 80 externalURL, err := computeExternalURL(cfg.ExternalURL, cfg.HTTPListenPort) 81 if err != nil { 82 return nil, errors.Wrapf(err, "parse external URL %q", cfg.ExternalURL) 83 } 84 externalURL.Path += cfg.PathPrefix 85 86 healthCheckTargetFlag := true 87 if cfg.HealthCheckTarget != nil { 88 healthCheckTargetFlag = *cfg.HealthCheckTarget 89 } 90 91 serv := &PromtailServer{ 92 Server: wws, 93 log: log, 94 tms: tms, 95 externalURL: externalURL, 96 healthCheckTarget: healthCheckTargetFlag, 97 promtailCfg: promtailCfg, 98 } 99 100 serv.HTTP.Path("/").Handler(http.RedirectHandler(path.Join(serv.externalURL.Path, "/targets"), 303)) 101 serv.HTTP.Path("/ready").Handler(http.HandlerFunc(serv.ready)) 102 serv.HTTP.PathPrefix("/static/").Handler(http.StripPrefix(externalURL.Path, http.FileServer(ui.Assets))) 103 serv.HTTP.Path("/service-discovery").Handler(http.HandlerFunc(serv.serviceDiscovery)) 104 serv.HTTP.Path("/targets").Handler(http.HandlerFunc(serv.targets)) 105 serv.HTTP.Path("/config").Handler(http.HandlerFunc(serv.config)) 106 serv.HTTP.Path("/debug/fgprof").Handler(fgprof.Handler()) 107 return serv, nil 108 } 109 110 // serviceDiscovery serves the service discovery page. 111 func (s *PromtailServer) serviceDiscovery(rw http.ResponseWriter, req *http.Request) { 112 var index []string 113 allTarget := s.tms.AllTargets() 114 for job := range allTarget { 115 index = append(index, job) 116 } 117 sort.Strings(index) 118 scrapeConfigData := struct { 119 Index []string 120 Targets map[string][]target.Target 121 Active []int 122 Dropped []int 123 Total []int 124 }{ 125 Index: index, 126 Targets: make(map[string][]target.Target), 127 Active: make([]int, len(index)), 128 Dropped: make([]int, len(index)), 129 Total: make([]int, len(index)), 130 } 131 for i, job := range scrapeConfigData.Index { 132 scrapeConfigData.Targets[job] = make([]target.Target, 0, len(allTarget[job])) 133 scrapeConfigData.Total[i] = len(allTarget[job]) 134 for _, t := range allTarget[job] { 135 // Do not display more than 100 dropped targets per job to avoid 136 // returning too much data to the clients. 137 if target.IsDropped(t) { 138 scrapeConfigData.Dropped[i]++ 139 if scrapeConfigData.Dropped[i] > 100 { 140 continue 141 } 142 } else { 143 scrapeConfigData.Active[i]++ 144 } 145 scrapeConfigData.Targets[job] = append(scrapeConfigData.Targets[job], t) 146 } 147 } 148 149 executeTemplate(req.Context(), rw, templateOptions{ 150 Data: scrapeConfigData, 151 BuildVersion: version.Info(), 152 Name: "service-discovery.html", 153 PageTitle: "Service Discovery", 154 ExternalURL: s.externalURL, 155 TemplateFuncs: template.FuncMap{ 156 "fileTargetDetails": func(details interface{}) map[string]int64 { 157 // you can't cast with a text template in go so this is a helper 158 return details.(map[string]int64) 159 }, 160 "dropReason": func(details interface{}) string { 161 if reason, ok := details.(string); ok { 162 return reason 163 } 164 return "" 165 }, 166 "numReady": func(ts []target.Target) (readies int) { 167 for _, t := range ts { 168 if t.Ready() { 169 readies++ 170 } 171 } 172 return 173 }, 174 }, 175 }) 176 } 177 178 func (s *PromtailServer) config(rw http.ResponseWriter, req *http.Request) { 179 executeTemplate(req.Context(), rw, templateOptions{ 180 Data: s.promtailCfg, 181 BuildVersion: version.Info(), 182 Name: "config.html", 183 PageTitle: "Config", 184 ExternalURL: s.externalURL, 185 }) 186 } 187 188 // targets serves the targets page. 189 func (s *PromtailServer) targets(rw http.ResponseWriter, req *http.Request) { 190 executeTemplate(req.Context(), rw, templateOptions{ 191 Data: struct { 192 TargetPools map[string][]target.Target 193 }{ 194 TargetPools: s.tms.ActiveTargets(), 195 }, 196 BuildVersion: version.Info(), 197 Name: "targets.html", 198 PageTitle: "Targets", 199 ExternalURL: s.externalURL, 200 TemplateFuncs: template.FuncMap{ 201 "fileTargetDetails": func(details interface{}) map[string]int64 { 202 // you can't cast with a text template in go so this is a helper 203 return details.(map[string]int64) 204 }, 205 "journalTargetDetails": func(details interface{}) map[string]string { 206 // you can't cast with a text template in go so this is a helper 207 return details.(map[string]string) 208 }, 209 "numReady": func(ts []target.Target) (readies int) { 210 for _, t := range ts { 211 if t.Ready() { 212 readies++ 213 } 214 } 215 return 216 }, 217 }, 218 }) 219 } 220 221 // ready serves the ready endpoint 222 func (s *PromtailServer) ready(rw http.ResponseWriter, _ *http.Request) { 223 if s.healthCheckTarget && !s.tms.Ready() { 224 http.Error(rw, readinessProbeFailure, http.StatusInternalServerError) 225 return 226 } 227 228 rw.WriteHeader(http.StatusOK) 229 if _, err := rw.Write(readinessProbeSuccess); err != nil { 230 level.Error(s.log).Log("msg", "error writing success message", "error", err) 231 } 232 } 233 234 // computeExternalURL computes a sanitized external URL from a raw input. It infers unset 235 // URL parts from the OS and the given listen address. 236 func computeExternalURL(u string, port int) (*url.URL, error) { 237 if u == "" { 238 hostname, err := os.Hostname() 239 if err != nil { 240 return nil, err 241 } 242 243 u = fmt.Sprintf("http://%s:%d/", hostname, port) 244 } 245 246 eu, err := url.Parse(u) 247 if err != nil { 248 return nil, err 249 } 250 251 ppref := strings.TrimRight(eu.Path, "/") 252 if ppref != "" && !strings.HasPrefix(ppref, "/") { 253 ppref = "/" + ppref 254 } 255 eu.Path = ppref 256 257 return eu, nil 258 } 259 260 type noopServer struct { 261 log log.Logger 262 sigs chan os.Signal 263 } 264 265 func newNoopServer(log log.Logger) *noopServer { 266 return &noopServer{ 267 log: log, 268 sigs: make(chan os.Signal, 1), 269 } 270 } 271 272 func (s *noopServer) Run() error { 273 signal.Notify(s.sigs, syscall.SIGINT, syscall.SIGTERM) 274 sig := <-s.sigs 275 level.Info(s.log).Log("msg", "received shutdown signal", "sig", sig) 276 return nil 277 } 278 279 func (s *noopServer) Shutdown() { 280 s.sigs <- syscall.SIGTERM 281 }