Crashes are now saved in the database

This commit is contained in:
Eduard Urbach 2019-11-04 16:34:00 +09:00
parent 604dc3c755
commit 2e9ad4bf6f
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
11 changed files with 125 additions and 5 deletions

View File

@ -14,7 +14,7 @@ type ClientErrorReport struct {
hasCreator
}
// StreamClientErrorReports returns a stream of all characters.
// StreamClientErrorReports returns a stream of all client error reports.
func StreamClientErrorReports() <-chan *ClientErrorReport {
channel := make(chan *ClientErrorReport, nano.ChannelBufferSize)
@ -29,10 +29,9 @@ func StreamClientErrorReports() <-chan *ClientErrorReport {
return channel
}
// AllClientErrorReports returns a slice of all characters.
// AllClientErrorReports returns a slice of all client error reports.
func AllClientErrorReports() []*ClientErrorReport {
all := make([]*ClientErrorReport, 0, DB.Collection("ClientErrorReport").Count())
stream := StreamClientErrorReports()
for obj := range stream {

View File

@ -7,6 +7,7 @@ import (
"github.com/aerogo/api"
)
// Force interface implementations
var (
_ api.Newable = (*ClientErrorReport)(nil)
)

39
arn/Crash.go Normal file
View File

@ -0,0 +1,39 @@
package arn
import "github.com/aerogo/nano"
// Crash contains data about server crashes.
type Crash struct {
Error string `json:"error"`
Stack string `json:"stack"`
hasID
hasCreator
}
// StreamCrashes returns a stream of all crashes.
func StreamCrashes() <-chan *Crash {
channel := make(chan *Crash, nano.ChannelBufferSize)
go func() {
for obj := range DB.All("Crash") {
channel <- obj.(*Crash)
}
close(channel)
}()
return channel
}
// AllCrashes returns a slice of all crashes.
func AllCrashes() []*Crash {
all := make([]*Crash, 0, DB.Collection("Crash").Count())
stream := StreamCrashes()
for obj := range stream {
all = append(all, obj)
}
return all
}

13
arn/CrashAPI.go Normal file
View File

@ -0,0 +1,13 @@
package arn
import "github.com/aerogo/api"
// Force interface implementations
var (
_ api.Savable = (*Crash)(nil)
)
// Save saves the crash in the database.
func (crash *Crash) Save() {
DB.Set("Crash", crash.ID, crash)
}

View File

@ -25,6 +25,7 @@ var DB = Node.Namespace("arn").RegisterTypes(
(*AnimeCharacters)(nil),
(*AnimeRelations)(nil),
(*AnimeList)(nil),
(*Crash)(nil),
(*Character)(nil),
(*ClientErrorReport)(nil),
(*Company)(nil),

View File

@ -8,6 +8,7 @@ import (
"strings"
"github.com/aerogo/aero"
"github.com/animenotifier/notify.moe/arn"
)
// Recover recovers from panics and shows them as the response body.
@ -27,11 +28,28 @@ func Recover(next aero.Handler) aero.Handler {
}
stack := make([]byte, 4096)
length := runtime.Stack(stack, true)
length := runtime.Stack(stack, false)
stackString := string(stack[:length])
fmt.Fprint(os.Stderr, stackString)
message := err.Error() + "<br><br>" + strings.ReplaceAll(stackString, "\n", "<br>")
// Save crash in database
crash := &arn.Crash{
Error: err.Error(),
Stack: stackString,
}
crash.ID = arn.GenerateID("Crash")
crash.Created = arn.DateTimeUTC()
user := arn.GetUserFromContext(ctx)
if user != nil {
crash.CreatedBy = user.ID
}
crash.Save()
// Send HTML
message := "<div class='crash'>" + err.Error() + "<br><br>" + strings.ReplaceAll(stackString, "\n", "<br>") + "</div>"
_ = ctx.Error(http.StatusInternalServerError, message)
}()

View File

@ -2,6 +2,7 @@ component AdminTabs
.tabs
Tab("Server", "server", "/admin")
Tab("WebDev", "html5", "/admin/webdev")
Tab("Crashes", "exclamation", "/admin/crashes")
Tab("Client errors", "exclamation", "/admin/errors/client")
Tab("Registrations", "user-plus", "/admin/registrations")
Tab("Purchases", "shopping-cart", "/admin/purchases")

26
pages/admin/crashes.go Normal file
View File

@ -0,0 +1,26 @@
package admin
import (
"sort"
"github.com/aerogo/aero"
"github.com/animenotifier/notify.moe/arn"
"github.com/animenotifier/notify.moe/components"
)
const maxCrashes = 80
// Crashes shows client-side errors.
func Crashes(ctx aero.Context) error {
crashes := arn.AllCrashes()
sort.Slice(crashes, func(i, j int) bool {
return crashes[i].Created > crashes[j].Created
})
if len(crashes) > maxCrashes {
crashes = crashes[:maxCrashes]
}
return ctx.HTML(components.Crashes(crashes))
}

19
pages/admin/crashes.pixy Normal file
View File

@ -0,0 +1,19 @@
component Crashes(crashes []*arn.Crash)
AdminTabs
h1.mountable Server-side crashes
table
tbody
each crash in crashes
tr.mountable
td
a(href="/api/crash/" + crash.ID, target="_blank")= crash.Error
td
each line in strings.Split(crash.Stack, "\n")
p= line
td.utc-date(data-date=crash.Created)
td.edit-log-user
if crash.CreatedBy != ""
Avatar(crash.Creator())
else
span anonymous

View File

@ -16,6 +16,8 @@ import (
// privateTypes are types that are not available for download.
var privateTypes = []string{
"Analytics",
"Crash",
"ClientErrorReport",
"EditLogEntry",
"EmailToUser",
"FacebookToUser",

View File

@ -77,6 +77,7 @@ func Register(app *aero.Application) {
page.Get(app, "/admin", admin.Get)
page.Get(app, "/admin/webdev", admin.WebDev)
page.Get(app, "/admin/registrations", admin.UserRegistrations)
page.Get(app, "/admin/crashes", admin.Crashes)
page.Get(app, "/admin/errors/client", admin.ClientErrors)
page.Get(app, "/admin/purchases", admin.PurchaseHistory)
page.Get(app, "/admin/payments", admin.PaymentHistory)