func afterTask(ctx *app.Context, task *Task, started time.Time, terr *error) { name := task.Name() if err := recover(); err != nil { skip, stackSkip, _, _ := runtimeutil.GetPanic() var buf bytes.Buffer fmt.Fprintf(&buf, "Panic executing task %s: %v\n", name, err) stack := runtimeutil.FormatStack(stackSkip) location, code := runtimeutil.FormatCaller(skip, 5, true, true) if location != "" { buf.WriteString("\n At ") buf.WriteString(location) if code != "" { buf.WriteByte('\n') buf.WriteString(code) buf.WriteByte('\n') } } if stack != "" { buf.WriteString("\nStack:\n") buf.WriteString(stack) } *terr = errors.New(buf.String()) } end := time.Now() running.Lock() defer running.Unlock() c := running.tasks[task] - 1 if c > 0 { running.tasks[task] = c } else { delete(running.tasks, task) } ctx.Logger().Infof("Finished task %s (%d instances now running) at %v (took %v)", name, c, end, end.Sub(started)) }
func (app *App) logError(ctx *Context, err interface{}) { skip, stackSkip, _, _ := runtimeutil.GetPanic() var buf bytes.Buffer if ctx.R != nil { buf.WriteString("Panic serving ") buf.WriteString(ctx.R.Method) buf.WriteByte(' ') buf.WriteString(ctx.R.Host) buf.WriteString(ctx.R.URL.Path) if rq := ctx.R.URL.RawQuery; rq != "" { buf.WriteByte('?') buf.WriteString(rq) } if rf := ctx.R.URL.Fragment; rf != "" { buf.WriteByte('#') buf.WriteString(rf) } buf.WriteByte(' ') buf.WriteString(ctx.RemoteAddress()) } else { buf.WriteString("Panic") } elapsed := ctx.Elapsed() fmt.Fprintf(&buf, " (after %s): %v\n", elapsed, err) stack := runtimeutil.FormatStack(stackSkip) location, code := runtimeutil.FormatCaller(skip, 5, true, true) if location != "" { buf.WriteString("\n At ") buf.WriteString(location) if code != "" { buf.WriteByte('\n') buf.WriteString(code) buf.WriteByte('\n') } } if stack != "" { buf.WriteString("\nStack:\n") buf.WriteString(stack) } req := "" if ctx.R != nil { dump, derr := httputil.DumpRequest(ctx.R, true) if derr == nil { // This cleans up empty lines and replaces \r\n with \n req = stringutil.Lines(string(dump), 0, 10000, true) buf.WriteString("\nRequest:\n") buf.WriteString(req) } // Check if there are any attached files that we might // want to send in an email if !app.cfg.Debug && mail.AdminEmail() != "" { ctx.R.ParseMultipartForm(32 << 20) // 32 MiB, as stdlib if form := ctx.R.MultipartForm; form != nil { var count int var attachments []*mail.Attachment var message bytes.Buffer if len(form.File) > 0 { for k, v := range form.File { for _, file := range v { f, err := file.Open() if err != nil { fmt.Fprintf(&message, "%s => error %s", k, err) continue } attachment, err := mail.NewAttachment(file.Filename, f) attachment.ContentType = file.Header.Get("Content-Type") f.Close() if err != nil { fmt.Fprintf(&message, "%s => error %s", k, err) continue } count++ fmt.Fprintf(&message, "%s => %s (%s)", k, attachment.Name, attachment.ContentType) attachments = append(attachments, attachment) } } fmt.Fprintf(&message, "\nError:\n%s", buf.String()) host, _ := os.Hostname() from := mail.DefaultFrom() if from == "" { from = fmt.Sprintf("errors@%s", host) } msg := &mail.Message{ From: from, To: mail.Admin, Subject: fmt.Sprintf("Panic with %d attached files on %s", count, host), TextBody: message.String(), Attachments: attachments, } ctx.SendMail("", nil, msg) } } } } ctx.Logger().Error(buf.String()) if app.cfg.Debug { app.errorPage(ctx, elapsed, skip, stackSkip, req, err) } else { app.handleHTTPError(ctx, "Internal Server Error", http.StatusInternalServerError) } }