github.com/Cloud-Foundations/Dominator@v0.3.4/lib/logbuf/http.go (about)

     1  package logbuf
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  	"path"
    10  	"runtime"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/Cloud-Foundations/Dominator/lib/html"
    17  	"github.com/Cloud-Foundations/Dominator/lib/url"
    18  	_ "github.com/Cloud-Foundations/tricorder/go/healthserver"
    19  )
    20  
    21  type countingWriter struct {
    22  	count      uint64
    23  	writer     io.Writer
    24  	prefixLine string
    25  }
    26  
    27  func showRecentLinks(w io.Writer, recentFirstString string) {
    28  	var recentFirstStringOnly string
    29  	if recentFirstString != "" && recentFirstString[0] == '&' {
    30  		recentFirstStringOnly = "?" + recentFirstString[1:]
    31  	}
    32  	fmt.Fprintf(w, "Show last: <a href=\"logs/showLast?1m%s\">minute</a>\n",
    33  		recentFirstString)
    34  	fmt.Fprintf(w, "           <a href=\"logs/showLast?10m%s\">10 min</a>\n",
    35  		recentFirstString)
    36  	fmt.Fprintf(w, "           <a href=\"logs/showLast?1h%s\">hour</a>\n",
    37  		recentFirstString)
    38  	fmt.Fprintf(w, "           <a href=\"logs/showLast?1d%s\">day</a>\n",
    39  		recentFirstString)
    40  	fmt.Fprintf(w, "           <a href=\"logs/showLast?1w%s\">week</a>\n",
    41  		recentFirstString)
    42  	fmt.Fprintf(w, "           <a href=\"logs/showLast?1M%s\">month</a>\n",
    43  		recentFirstString)
    44  	fmt.Fprintf(w, "           <a href=\"logs/showLast?1y%s\">year</a>\n",
    45  		recentFirstString)
    46  	fmt.Fprintf(w, "           <a href=\"logs/showSinceStartup%s\">since startup</a><br>\n",
    47  		recentFirstStringOnly)
    48  	fmt.Fprintln(w, `Show <a href="logs/showStackTrace">stack trace</a>`)
    49  }
    50  
    51  func (w *countingWriter) Write(p []byte) (n int, err error) {
    52  	if w.prefixLine != "" {
    53  		w.writer.Write([]byte(w.prefixLine))
    54  		w.prefixLine = ""
    55  	}
    56  	n, err = w.writer.Write(p)
    57  	if n > 0 {
    58  		w.count += uint64(n)
    59  	}
    60  	return
    61  }
    62  
    63  func (lb *LogBuffer) addHttpHandlers() {
    64  	if lb.options.HttpServeMux == nil {
    65  		return
    66  	}
    67  	html.ServeMuxHandleFunc(lb.options.HttpServeMux, "/logs",
    68  		lb.httpListHandler)
    69  	html.ServeMuxHandleFunc(lb.options.HttpServeMux, "/logs/dump",
    70  		lb.httpDumpHandler)
    71  	html.ServeMuxHandleFunc(lb.options.HttpServeMux, "/logs/showLast",
    72  		lb.httpShowLastHandler)
    73  	html.ServeMuxHandleFunc(lb.options.HttpServeMux, "/logs/showPreviousPanic",
    74  		lb.httpShowPreviousPanicHandler)
    75  	html.ServeMuxHandleFunc(lb.options.HttpServeMux, "/logs/showSinceStartup",
    76  		lb.httpShowSinceStartupHandler)
    77  	html.ServeMuxHandleFunc(lb.options.HttpServeMux, "/logs/showStackTrace",
    78  		lb.httpShowStackTraceHandler)
    79  }
    80  
    81  func (lb *LogBuffer) dumpFile(writer io.Writer, filename string,
    82  	recentFirst bool) error {
    83  	file, err := os.Open(path.Join(lb.options.Directory, filename))
    84  	if err != nil {
    85  		return err
    86  	}
    87  	defer file.Close()
    88  	if recentFirst {
    89  		scanner := bufio.NewScanner(file)
    90  		lines := make([]string, 0)
    91  		for scanner.Scan() {
    92  			line := scanner.Text()
    93  			if len(line) < 1 {
    94  				continue
    95  			}
    96  			lines = append(lines, line)
    97  		}
    98  		if err = scanner.Err(); err == nil {
    99  			reverseStrings(lines)
   100  			for _, line := range lines {
   101  				fmt.Fprintln(writer, line)
   102  			}
   103  		}
   104  	} else {
   105  		_, err = io.Copy(writer, bufio.NewReader(file))
   106  	}
   107  	return err
   108  }
   109  
   110  func (lb *LogBuffer) httpDumpHandler(w http.ResponseWriter, req *http.Request) {
   111  	parsedQuery := url.ParseQuery(req.URL)
   112  	name, ok := parsedQuery.Table["name"]
   113  	if !ok {
   114  		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
   115  		w.WriteHeader(http.StatusBadRequest)
   116  		return
   117  	}
   118  	_, recentFirst := parsedQuery.Flags["recentFirst"]
   119  	if name == "latest" {
   120  		lbFilename := ""
   121  		lb.rwMutex.RLock()
   122  		if lb.file != nil {
   123  			lbFilename = lb.file.Name()
   124  		}
   125  		lb.rwMutex.RUnlock()
   126  		if lbFilename == "" {
   127  			writer := bufio.NewWriter(w)
   128  			defer writer.Flush()
   129  			lb.Dump(writer, "", "", recentFirst)
   130  			return
   131  		}
   132  		name = path.Base(lbFilename)
   133  	}
   134  	writer := bufio.NewWriter(w)
   135  	defer writer.Flush()
   136  	err := lb.dumpFile(writer, path.Base(path.Clean(name)), recentFirst)
   137  	if err != nil && os.IsNotExist(err) {
   138  		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
   139  		w.WriteHeader(http.StatusNotFound)
   140  		return
   141  	}
   142  	if err != nil {
   143  		fmt.Fprintln(writer, err)
   144  	}
   145  }
   146  
   147  func (lb *LogBuffer) httpListHandler(w http.ResponseWriter, req *http.Request) {
   148  	if lb.options.Directory == "" {
   149  		return
   150  	}
   151  	writer := bufio.NewWriter(w)
   152  	defer writer.Flush()
   153  	parsedQuery := url.ParseQuery(req.URL)
   154  	_, recentFirst := parsedQuery.Flags["recentFirst"]
   155  	names, panicMap, err := lb.list(recentFirst)
   156  	if err != nil {
   157  		fmt.Fprintln(writer, err)
   158  		return
   159  	}
   160  	recentFirstString := ""
   161  	if recentFirst {
   162  		recentFirstString = "&recentFirst"
   163  	}
   164  	if parsedQuery.OutputType() == url.OutputTypeText {
   165  		for _, name := range names {
   166  			fmt.Fprintln(writer, name)
   167  		}
   168  		return
   169  	}
   170  	fmt.Fprintln(writer, "<body>")
   171  	fmt.Fprint(writer, "Logs: ")
   172  	if recentFirst {
   173  		fmt.Fprintf(writer, "showing recent first ")
   174  		fmt.Fprintln(writer, `<a href="logs">show recent last</a><br>`)
   175  	} else {
   176  		fmt.Fprintf(writer, "showing recent last ")
   177  		fmt.Fprintln(writer,
   178  			`<a href="logs?recentFirst">show recent first</a><br>`)
   179  	}
   180  	showRecentLinks(writer, recentFirstString)
   181  	fmt.Fprintln(writer, "<p>")
   182  	currentName := ""
   183  	lb.rwMutex.RLock()
   184  	if lb.file != nil {
   185  		currentName = path.Base(lb.file.Name())
   186  	}
   187  	lb.rwMutex.RUnlock()
   188  	if recentFirst {
   189  		fmt.Fprintf(writer,
   190  			"<a href=\"logs/dump?name=latest%s\">current</a><br>\n",
   191  			recentFirstString)
   192  	}
   193  	for _, name := range names {
   194  		if !recentFirst {
   195  			if name == lb.firstFile {
   196  				fmt.Fprintln(writer, "<hr>")
   197  			}
   198  		}
   199  		if name == currentName {
   200  			fmt.Fprintf(writer,
   201  				"<a href=\"logs/dump?name=%s%s\">%s</a> (current)<br>\n",
   202  				name, recentFirstString, name)
   203  		} else {
   204  			hasPanic := ""
   205  			if _, ok := panicMap[name]; ok {
   206  				hasPanic = " (has panic log)"
   207  			}
   208  			fmt.Fprintf(writer,
   209  				"<a href=\"logs/dump?name=%s%s\">%s</a>%s<br>\n",
   210  				name, recentFirstString, name, hasPanic)
   211  		}
   212  		if recentFirst {
   213  			if name == lb.firstFile {
   214  				fmt.Fprintln(writer, "<hr>")
   215  			}
   216  		}
   217  	}
   218  	if !recentFirst {
   219  		fmt.Fprintf(writer,
   220  			"<a href=\"logs/dump?name=latest%s\">current</a><br>\n",
   221  			recentFirstString)
   222  	}
   223  	fmt.Fprintln(writer, "</body>")
   224  }
   225  
   226  func (lb *LogBuffer) httpShowLastHandler(w http.ResponseWriter,
   227  	req *http.Request) {
   228  	parsedQuery := url.ParseQuery(req.URL)
   229  	_, recentFirst := parsedQuery.Flags["recentFirst"]
   230  	for flag := range parsedQuery.Flags {
   231  		length := len(flag)
   232  		if length < 2 {
   233  			continue
   234  		}
   235  		unitChar := flag[length-1]
   236  		var unit time.Duration
   237  		switch unitChar {
   238  		case 's':
   239  			unit = time.Second
   240  		case 'm':
   241  			unit = time.Minute
   242  		case 'h':
   243  			unit = time.Hour
   244  		case 'd':
   245  			unit = time.Hour * 24
   246  		case 'w':
   247  			unit = time.Hour * 24 * 7
   248  		case 'M':
   249  			// TODO(rgooch): Fix this crude approximation.
   250  			unit = time.Hour * 24 * 31
   251  		case 'y':
   252  			unit = time.Hour * 24 * 365
   253  		default:
   254  			continue
   255  		}
   256  		if val, err := strconv.ParseUint(flag[:length-1], 10, 64); err != nil {
   257  			w.Header().Set("Content-Type", "text/plain; charset=utf-8")
   258  			w.WriteHeader(http.StatusBadRequest)
   259  			return
   260  		} else {
   261  			lb.showRecent(w, time.Duration(val)*unit, recentFirst)
   262  			return
   263  		}
   264  	}
   265  	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
   266  	w.WriteHeader(http.StatusBadRequest)
   267  }
   268  
   269  func (lb *LogBuffer) httpShowPreviousPanicHandler(w http.ResponseWriter,
   270  	req *http.Request) {
   271  	writer := bufio.NewWriter(w)
   272  	defer writer.Flush()
   273  	panicLogfile := lb.panicLogfile
   274  	if panicLogfile == nil {
   275  		fmt.Fprintln(writer, "Last invocation did not panic!")
   276  		return
   277  	}
   278  	if *panicLogfile == "" {
   279  		fmt.Fprintln(writer, "Logfile for previous invocation has expired")
   280  		return
   281  	}
   282  	file, err := os.Open(path.Join(lb.options.Directory, *panicLogfile))
   283  	if err != nil {
   284  		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
   285  		w.WriteHeader(http.StatusNotFound)
   286  		return
   287  	}
   288  	defer file.Close()
   289  	_, err = io.Copy(writer, bufio.NewReader(file))
   290  	if err != nil {
   291  		fmt.Fprintln(writer, err)
   292  	}
   293  }
   294  
   295  func (lb *LogBuffer) httpShowSinceStartupHandler(w http.ResponseWriter,
   296  	req *http.Request) {
   297  	parsedQuery := url.ParseQuery(req.URL)
   298  	_, recentFirst := parsedQuery.Flags["recentFirst"]
   299  	writer := bufio.NewWriter(w)
   300  	defer writer.Flush()
   301  	names, _, err := lb.list(recentFirst)
   302  	if err != nil {
   303  		fmt.Fprintln(writer, err)
   304  		return
   305  	}
   306  	lb.flush()
   307  	dumpFiles := recentFirst
   308  	for _, name := range names {
   309  		if !recentFirst && name == lb.firstFile {
   310  			dumpFiles = true
   311  		}
   312  		if dumpFiles {
   313  			lb.dumpFile(writer, name, recentFirst)
   314  		}
   315  		if recentFirst && name == lb.firstFile {
   316  			dumpFiles = false
   317  		}
   318  	}
   319  }
   320  
   321  func (lb *LogBuffer) httpShowStackTraceHandler(w http.ResponseWriter,
   322  	req *http.Request) {
   323  	writer := bufio.NewWriter(w)
   324  	defer writer.Flush()
   325  	lb.rwMutex.Lock()
   326  	if time.Since(lb.lastStackTrace) < 2*time.Second {
   327  		lb.rwMutex.Unlock()
   328  		writer.Write([]byte("Too soon\n"))
   329  		return
   330  	}
   331  	lb.lastStackTrace = time.Now()
   332  	lb.rwMutex.Unlock()
   333  	buffer := make([]byte, 1<<20)
   334  	nBytes := runtime.Stack(buffer, true)
   335  	writer.Write(buffer[:nBytes])
   336  }
   337  
   338  func (lb *LogBuffer) list(recentFirst bool) (
   339  	[]string, map[string]struct{}, error) {
   340  	file, err := os.Open(lb.options.Directory)
   341  	if err != nil {
   342  		return nil, nil, err
   343  	}
   344  	fileInfos, err := file.Readdir(-1)
   345  	file.Close()
   346  	if err != nil {
   347  		return nil, nil, err
   348  	}
   349  	panicMap := make(map[string]struct{})
   350  	names := make([]string, 0, len(fileInfos))
   351  	for _, fi := range fileInfos {
   352  		if strings.Count(fi.Name(), ":") == 3 {
   353  			names = append(names, fi.Name())
   354  			if fi.Mode()&os.ModeSticky != 0 {
   355  				panicMap[fi.Name()] = struct{}{}
   356  			}
   357  		}
   358  	}
   359  	sort.Strings(names)
   360  	if recentFirst {
   361  		reverseStrings(names)
   362  	}
   363  	return names, panicMap, nil
   364  }
   365  
   366  func (lb *LogBuffer) showRecent(w io.Writer, duration time.Duration,
   367  	recentFirst bool) {
   368  	writer := bufio.NewWriter(w)
   369  	defer writer.Flush()
   370  	names, _, err := lb.list(true)
   371  	if err != nil {
   372  		fmt.Fprintln(writer, err)
   373  		return
   374  	}
   375  	earliestTime := time.Now().Add(-duration)
   376  	// Get a list of names which may be recent enough.
   377  	tmpNames := make([]string, 0, len(names))
   378  	for _, name := range names {
   379  		startTime, err := time.ParseInLocation(timeLayout, name, time.Local)
   380  		if err != nil {
   381  			continue
   382  		}
   383  		tmpNames = append(tmpNames, name)
   384  		if startTime.Before(earliestTime) {
   385  			break
   386  		}
   387  	}
   388  	names = tmpNames
   389  	if !recentFirst {
   390  		reverseStrings(names)
   391  	}
   392  	fmt.Fprintln(writer, "<body>")
   393  	cWriter := &countingWriter{writer: writer, prefixLine: "<pre>\n"}
   394  	lb.flush()
   395  	for _, name := range names {
   396  		cWriter.count = 0
   397  		foundReopenMessage, _ := lb.dumpSince(cWriter, name, earliestTime, "",
   398  			"\n", recentFirst)
   399  		if cWriter.count > 0 && !foundReopenMessage {
   400  			cWriter.prefixLine = "</pre>\n<hr>\n<pre>\n"
   401  		}
   402  	}
   403  	fmt.Fprintln(writer, "</pre>")
   404  	fmt.Fprintln(writer, "</body>")
   405  }
   406  
   407  func (lb *LogBuffer) writeHtml(writer io.Writer) {
   408  	if lb.options.Directory != "" {
   409  		fmt.Fprintln(writer, `<a href="logs">Logs:</a><br>`)
   410  		panicLogfile := lb.panicLogfile
   411  		if panicLogfile != nil {
   412  			fmt.Fprint(writer,
   413  				"<font color=\"red\">Last invocation paniced</font>, ")
   414  			if *panicLogfile == "" {
   415  				fmt.Fprintln(writer, "logfile no longer available<br>")
   416  			} else {
   417  				fmt.Fprintln(writer,
   418  					"<a href=\"logs/showPreviousPanic\">logfile</a><br>")
   419  			}
   420  		}
   421  	} else {
   422  		fmt.Fprintln(writer, "Logs:<br>")
   423  	}
   424  	fmt.Fprintln(writer, "<pre>")
   425  	lb.dump(writer, "", "", false, true)
   426  	fmt.Fprintln(writer, "</pre>")
   427  }