-
-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Generate crash report txt and webpage
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
Showing
13 changed files
with
3,820 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module github.com/hrfee/jfa-go/linecache | ||
|
||
go 1.16 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.