/* A generic error function Utility functions pass errors up to the caller Higher level "request functions" handle errors directly often we want to abort further request processing and issue an message into the http response AND into the logs Sometimes we only want to write the error into the logs and continue operation => continueExecution true In addition to the generic error messages we may add specific error explanations or values via parameter vs - for display and logging We also show the source file+location. A "global panic catcher" in util_err.Adapter() ...defer(){} cooperates - suppressing the stacktrace and healing the panic */ func E(w http.ResponseWriter, r *http.Request, bool_or_err interface{}, continueExecution bool, vs ...interface{}) { var err error switch bool_or_err.(type) { default: type_unknown := fmt.Sprintf("%T", bool_or_err) err = errors.New("only bool or error - instead: -" + type_unknown + "-") panic(err) case nil: return case bool: if bool_or_err.(bool) { return } err = errors.New("Not OK (type conv?)") case error: err = bool_or_err.(error) } if err != nil { line, file := runtimepb.LineFileXUp(1) // we cannot determine, whether html is already sent // we cannot determine, whether we are in plaintext or html context // thus we need the <br> s := fmt.Sprintf("ERR: %v <br>\n\t /%s:%d \n", err, file, line) if len(vs) > 0 { s = s + "\t" + fmt.Sprint(vs...) + "\n" } if continueExecution { c, _ := util_appengine.SafelyExtractGaeCtxError(r) if c == nil { log.Printf(s) } else { aelog.Infof(c, s) } } else { c, _ := util_appengine.SafelyExtractGaeCtxError(r) if c == nil { log.Printf(s) } else { aelog.Errorf(c, s) } w.Header().Set("Content-Type", "text/plain") http.Error(w, s, http.StatusInternalServerError) panic("abort_handler_processing") } } }
func Pf(w io.Writer, r *http.Request, f string, vs ...interface{}) { for idx, v := range vs { switch v := v.(type) { case []byte: if len(v) > 1024*5 { appdx := append([]byte(" ...omitted... "), v[len(v)-100:]...) vs[idx] = append(v[:1024*5], appdx...) } case string: if len(v) > 1024*5 { appdx := " ...omitted... " + v[len(v)-100:] vs[idx] = v[:1024*5] + appdx } } } // Prepare the string var s string if len(vs) > 0 { s = fmt.Sprintf(f, vs...) } else { s = f } if s == "" { return } // Write it to http response or bytes.Buffer // unless prefixed with 'lo ' - log only. // Thread-safety could be introduced by syncing/locking w. if w != nil && !strings.HasPrefix(s, "lo ") { w.Write([]byte(s)) w.Write([]byte{'\n'}) } // Write to log/gae-log // Adding src code info line, file := runtimepb.LineFileXUp(1) // if strings.HasSuffix(file, "log.go") if strings.HasSuffix(file, runtimepb.ThisFile()) { // change line, file = runtimepb.LineFileXUp(2) } if len(s) < 60 { s = stringspb.ToLen(s, 60) } s = fmt.Sprintf("%v - %v:%v", s, file, line) // Log it c, _ := util_appengine.SafelyExtractGaeCtxError(r) if c == nil { lnp.Printf(s) } else { aelog.Infof(c, s) } }
// Adapter() checks the path, takes the time, precomputes values into a map // provides a global panic catcher // The typed signature is cleaner than the long version: // func Adapter(given func(http.ResponseWriter, *http.Request, map[string]interface{})) http.HandlerFunc { func Adapter(given ExtendedHandler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { start := time.Now() c, _ := util_appengine.SafelyExtractGaeCtxError(r) lgi := log.Printf lge := log.Fatalf if c != nil { defer logServerTime(c, start) // lgi = c.Infof lgi = func(format string, v ...interface{}) { aelog.Infof(c, format, v...) } // lge = c.Errorf lge = func(format string, v ...interface{}) { aelog.Errorf(c, format, v...) } C = c } if !authenticate(w, r) { return } //check_against := r.URL.String() check_against := r.URL.Path matches := validRequestPath.FindStringSubmatch(check_against) if matches == nil { s := "illegal chars in path: " + check_against lgi(s) http.Error(w, s, http.StatusInternalServerError) return } s, err := url.Parse(r.URL.String()) if err != nil { panic("Could not url.Parse current url") } mp := map[string]interface{}{ "dir": path.Dir(s.Path), "base": path.Base(s.Path), } defer func() { // note: Println works even in panic panicSignal := recover() if panicSignal != nil { miniStacktrace := "" for i := 1; i < 11; i++ { _, file, line, _ := runtime.Caller(i) if strings.Index(file, `/src/pkg/runtime/`) > -1 { miniStacktrace += fmt.Sprintf("<br>\n\t\t %s:%d ", file[len(file)-20:], line) } else { dir := filepath.Dir(file) dirLast := filepath.Base(dir) file = filepath.Join(dirLast, filepath.Base(file)) // we cannot determine, whether html is already sent // we cannot determine, whether we are in plaintext or html context // thus we need the <br> miniStacktrace += fmt.Sprintf("<br>\n\t\t /%s:%d ", file, line) } } // headers := w.Header() // for k, v := range headers { // miniStacktrace += fmt.Sprintf("%#v %#v<br>\n", k, v) // } if panicSignal == "abort_handler_processing" { s := fmt.Sprint("\thttp processing aborted\n", miniStacktrace) lge(s) w.Write([]byte(s)) } else if panicSignal != nil { s := fmt.Sprintf("\tPANIC caught by util_err.Adapter: %v %s\n", panicSignal, miniStacktrace) lge(s) w.Write([]byte(s)) } } }() r.Header.Set("adapter_01", "a string set by adapter") if c == nil { given(w, r, mp) } else { var given1 AppengineHandler given1 = func(c context.Context, w http.ResponseWriter, r *http.Request) { given(w, r, mp) // automatically set on appengine live, but not on appengine dev if r.Header.Get("Content-Type") == "" { w.Header().Set("Content-Type", "text/html; charset=utf-8") } if r.Header.Get("X-Custom-Header-Counter") != "nocounter" { cntr := 0 if true { // This seems to cause problems with new applications // possible because of missing indize distributed_unancestored.Increment(c, mp["dir"].(string)+mp["base"].(string)) cntr, _ = distributed_unancestored.Count(c, mp["dir"].(string)+mp["base"].(string)) } fmt.Fprintf(w, "<br>\n%v Views<br>\n", cntr) } } if true || appengine.IsDevAppServer() { given1(c, w, r) } else { // wrapped := appstats.NewHandler(given1) // mjibson // wrapped.ServeHTTP(w, r) } } } }