Skip to content

Commit

Permalink
Generate crash report txt and webpage
Browse files Browse the repository at this point in the history
The last 100 lines of logs are now cached, and when a crash occurs, they
are saved to a file in the temp directory ("/tmp" on *nix), and pretty
HTML version is also created and opened in the browser.
* Currently only handles panics, will be included in more places soon
* Copy button and button to generate a GH issue will be added
  • Loading branch information
hrfee committed Jun 11, 2021
1 parent 2f50169 commit f0f4e81
Show file tree
Hide file tree
Showing 13 changed files with 3,820 additions and 68 deletions.
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,17 @@ bundle-css:
-mkdir -p $(DATA)/web/css
$(info bundling css)
$(ESBUILD) --bundle css/base.css --outfile=$(DATA)/web/css/bundle.css --external:remixicon.css --minify
cp html/crash.html $(DATA)/crash.html
npx uncss $(DATA)/crash.html --csspath web/css --output $(DATA)/bundle.css
bash -c 'cd $(DATA); npx inline-css-cli -i crash.html -o crash.html'
rm $(DATA)/bundle.css

copy:
$(info copying fonts)
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 $(DATA)/web/css/
$(info copying html)
cp -r html $(DATA)/
mv $(DATA)/crash.html $(DATA)/html/
$(info copying static data)
-mkdir -p $(DATA)/web
cp -r static/* $(DATA)/web/
Expand Down
12 changes: 12 additions & 0 deletions css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -521,3 +521,15 @@ img.img-circle {
display: flex !important;
align-items: center;
}

div.card:contains(section.banner.footer) {
padding-bottom: 0px;
}

.card.sectioned {
padding: 0px;
}

.card.sectioned .section {
padding: var(--spacing-4, 1rem);
}
41 changes: 41 additions & 0 deletions exit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main

import (
"html/template"
"log"
"os"
"path/filepath"
"time"

"github.com/pkg/browser"
)

// Exit dumps the last 100 lines of output to a crash file in /tmp (or equivalent), and generates a prettier HTML file containing it that is opened in the browser if possible.
func Exit(err interface{}) {
tmpl, err2 := template.ParseFS(localFS, "html/crash.html", "html/header.html")
if err2 != nil {
log.Fatalf("Failed to load template: %v", err)
}
logCache := lineCache.String()
data := map[string]interface{}{
"Log": logCache,
}
if err != nil {
data["Err"] = err
}
fpath := filepath.Join(temp, "jfa-go-crash-"+time.Now().Local().Format("2006-01-02T15:04:05"))
err2 = os.WriteFile(fpath+".txt", []byte(logCache), 0666)
if err2 != nil {
log.Fatalf("Failed to write crash dump file: %v", err2)
}
f, err2 := os.OpenFile(fpath+".html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err2 != nil {
log.Fatalf("Failed to open crash dump file: %v", err2)
}
defer f.Close()
err2 = tmpl.Execute(f, data)
if err2 != nil {
log.Fatalf("Failed to execute template: %v", err2)
}
browser.OpenFile(fpath + ".html")
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ replace github.com/hrfee/jfa-go/ombi => ./ombi

replace github.com/hrfee/jfa-go/logger => ./logger

replace github.com/hrfee/jfa-go/linecache => ./linecache

require (
github.com/bwmarrin/discordgo v0.23.2
github.com/dgrijalva/jwt-go v3.2.0+incompatible
Expand All @@ -29,6 +31,7 @@ require (
github.com/google/uuid v1.1.2 // indirect
github.com/hrfee/jfa-go/common v0.0.0-20210105184019-fdc97b4e86cc
github.com/hrfee/jfa-go/docs v0.0.0-20201112212552-b6f3cd7c1f71
github.com/hrfee/jfa-go/linecache v0.0.0-00010101000000-000000000000 // indirect
github.com/hrfee/jfa-go/logger v0.0.0-00010101000000-000000000000
github.com/hrfee/jfa-go/ombi v0.0.0-20201112212552-b6f3cd7c1f71
github.com/hrfee/mediabrowser v0.3.4
Expand All @@ -38,6 +41,7 @@ require (
github.com/mailgun/mailgun-go/v4 v4.5.1
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16
github.com/pkg/browser v0.0.0-20210606212950-a7b7a6107d32 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14
Expand Down
39 changes: 39 additions & 0 deletions go.sum

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions html/crash.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" type="text/css" href="bundle.css">
{{ template "header.html" . }}
<title>Crash report</title>
</head>
<body>
<div class="page-container">
<div class="row">
<div class="col">
<div class="card ~critical sectioned">
<section class="section ~critical">
<span class="heading">Crash report for jfa-go</span>
{{ if .Err }}
<div class="monospace pre-line mt-1 mb-1">
Error: {{ .Err }}
</div>
{{ end }}
<a class="button ~critical mb-1" target="_blank" href="https://github.com/hrfee/jfa-go/issues/new/choose">Create an Issue</a>
</section>
<section class="section ~neutral !low">
<span class="subheading">Full Log</span>
<pre class="monospace pre-line">{{ .Log }}</pre>
</section>
</div>
</div>
</div>
</div>
</body>
</html>
3 changes: 3 additions & 0 deletions linecache/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/hrfee/jfa-go/linecache

go 1.16
66 changes: 66 additions & 0 deletions linecache/linecache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Package linecache provides a writer that stores n lines of text at once, overwriting old content as it reaches its capacity. Its contents can be read from with a String() method.
package linecache

import (
"strings"
"sync"
)

// LineCache provides an io.Writer that stores a fixed number of lines of text.
type LineCache struct {
count int
lines [][]byte
current int
lock *sync.Mutex
}

// NewLineCache returns a new line cache of capacity (n) lines.
func NewLineCache(n int) *LineCache {
return &LineCache{
current: 0,
count: n,
lines: make([][]byte, n),
lock: &sync.Mutex{},
}
}

// Write writes a given byte array to the cache.
func (l *LineCache) Write(p []byte) (n int, err error) {
l.lock.Lock()
defer l.lock.Unlock()
lines := strings.Split(string(p), "\n")
for _, line := range lines {
if string(line) == "" {
continue
}
if l.current == l.count {
l.current = 0
}
l.lines[l.current] = []byte(line)
l.current++
}
n = len(p)
return
}

// String returns a string representation of the cache contents.
func (l *LineCache) String() string {
i := 0
if l.lines[l.count-1] != nil && l.current != l.count {
i = l.current
}
out := ""
for {
if l.lines[i] == nil {
return out
}
out += string(l.lines[i]) + "\n"
i++
if i == l.current {
return out
}
if i == l.count {
i = 0
}
}
}
17 changes: 17 additions & 0 deletions linecache/linecache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package linecache

import (
"fmt"
"strings"
"testing"
"time"
)

func Test(t *testing.T) {
wr := NewLineCache(10)
for i := 10; i < 50; i++ {
fmt.Fprintln(wr, i)
fmt.Print(strings.ReplaceAll(wr.String(), "\n", " "), "\n")
time.Sleep(time.Second)
}
}
69 changes: 69 additions & 0 deletions log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package main

import (
"io"
"log"
"os"
"path/filepath"
"regexp"

"github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/linecache"
)

var logPath string = filepath.Join(temp, "jfa-go.log")
var lineCache = linecache.NewLineCache(100)

func logOutput() (closeFunc func()) {
old := os.Stdout
log.Printf("Logging to \"%s\"", logPath)
writers := []io.Writer{old, colorStripper{lineCache}}
wExit := make(chan bool)
r, w, _ := os.Pipe()
if TRAY {
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
closeFunc = func() {}
return
}
writers = append(writers, colorStripper{f})
closeFunc = func() {
w.Close()
<-wExit
f.Close()
}
} else {
closeFunc = func() {
w.Close()
<-wExit
}
}
writer := io.MultiWriter(writers...)
os.Stdout, os.Stderr = w, w
log.SetOutput(writer)
gin.DefaultWriter, gin.DefaultErrorWriter = writer, writer
go func() {
io.Copy(writer, r)
wExit <- true
}()
return
}

// Regex that removes ANSI color escape sequences. Used for outputting to log file and log cache.
var stripColors = func() *regexp.Regexp {
r, err := regexp.Compile("\\x1b\\[[0-9;]*m")
if err != nil {
log.Fatalf("Failed to compile color escape regexp: %v", err)
}
return r
}()

type colorStripper struct {
file io.Writer
}

func (c colorStripper) Write(p []byte) (n int, err error) {
_, err = c.file.Write(stripColors.ReplaceAll(p, []byte("")))
n = len(p)
return
}
37 changes: 6 additions & 31 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/fs"
"log"
"mime"
Expand All @@ -21,7 +20,6 @@ import (
"time"

"github.com/fatih/color"
"github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/common"
_ "github.com/hrfee/jfa-go/docs"
"github.com/hrfee/jfa-go/logger"
Expand Down Expand Up @@ -61,8 +59,6 @@ var temp = func() string {
return temp
}()

var logPath string = filepath.Join(temp, "jfa-go.log")

var serverTypes = map[string]string{
"jellyfin": "Jellyfin",
"emby": "Emby (experimental)",
Expand Down Expand Up @@ -644,34 +640,13 @@ func printVersion() {
fmt.Println(info("jfa-go version: %s (%s)%s\n", hiwhite(version), white(commit), tray))
}

func logOutput() func() {
old := os.Stdout
log.Printf("Logging to \"%s\"", logPath)
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return func() {}
}
writer := io.MultiWriter(old, f)
r, w, _ := os.Pipe()
os.Stdout, os.Stderr = w, w
log.SetOutput(writer)
gin.DefaultWriter, gin.DefaultErrorWriter = writer, writer
wExit := make(chan bool)
go func() {
io.Copy(writer, r)
wExit <- true
}()
return func() {
w.Close()
<-wExit
f.Close()
}
}

func main() {
if TRAY {
defer logOutput()()
}
defer func() {
if r := recover(); r != nil {
Exit(r)
}
}()
defer logOutput()()
printVersion()
SOCK = filepath.Join(temp, SOCK)
fmt.Println("Socket:", SOCK)
Expand Down
Loading

0 comments on commit f0f4e81

Please sign in to comment.