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  }