198 lines
5.8 KiB
Go
198 lines
5.8 KiB
Go
package report
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type ClickLog struct {
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Element string `json:"element"`
|
|
PageURL string `json:"pageUrl"`
|
|
Text *string `json:"text"`
|
|
ClassName *string `json:"className"`
|
|
}
|
|
|
|
type NetworkLog struct {
|
|
Type string `json:"type"`
|
|
Method string `json:"method"`
|
|
URL string `json:"url"`
|
|
PageURL string `json:"pageUrl"`
|
|
Status int `json:"status"`
|
|
Duration int `json:"duration"`
|
|
DataPreview string `json:"dataPreview"`
|
|
Body string `json:"body"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
type ReactQueryLog struct {
|
|
Type string `json:"type"`
|
|
PageURL string `json:"pageUrl"`
|
|
Status string `json:"status"`
|
|
Hash string `json:"hash"`
|
|
Error interface{} `json:"error"`
|
|
DataPreview string `json:"dataPreview"`
|
|
DataType string `json:"dataType"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
type ConsoleLog struct {
|
|
Type string `json:"type"`
|
|
Content string `json:"content"`
|
|
PageURL string `json:"pageUrl"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
type UnlockedLocalFile struct {
|
|
Path string `json:"path"`
|
|
MediaId int `json:"mediaId"`
|
|
}
|
|
|
|
type IssueReport struct {
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UserAgent string `json:"userAgent"`
|
|
AppVersion string `json:"appVersion"`
|
|
OS string `json:"os"`
|
|
Arch string `json:"arch"`
|
|
ClickLogs []*ClickLog `json:"clickLogs,omitempty"`
|
|
NetworkLogs []*NetworkLog `json:"networkLogs,omitempty"`
|
|
ReactQueryLogs []*ReactQueryLog `json:"reactQueryLogs,omitempty"`
|
|
ConsoleLogs []*ConsoleLog `json:"consoleLogs,omitempty"`
|
|
UnlockedLocalFiles []*UnlockedLocalFile `json:"unlockedLocalFiles,omitempty"`
|
|
ScanLogs []string `json:"scanLogs,omitempty"`
|
|
ServerLogs string `json:"serverLogs,omitempty"`
|
|
ServerStatus string `json:"status,omitempty"`
|
|
}
|
|
|
|
func NewIssueReport(userAgent, appVersion, _os, arch string, logsDir string, isAnimeLibraryIssue bool, serverStatus interface{}, toRedact []string) (ret *IssueReport, err error) {
|
|
ret = &IssueReport{
|
|
CreatedAt: time.Now(),
|
|
UserAgent: userAgent,
|
|
AppVersion: appVersion,
|
|
OS: _os,
|
|
Arch: arch,
|
|
ClickLogs: make([]*ClickLog, 0),
|
|
NetworkLogs: make([]*NetworkLog, 0),
|
|
ReactQueryLogs: make([]*ReactQueryLog, 0),
|
|
ConsoleLogs: make([]*ConsoleLog, 0),
|
|
UnlockedLocalFiles: make([]*UnlockedLocalFile, 0),
|
|
ScanLogs: make([]string, 0),
|
|
ServerLogs: "",
|
|
ServerStatus: "",
|
|
}
|
|
|
|
// Get all log files in the directory
|
|
entries, err := os.ReadDir(logsDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read log directory: %w", err)
|
|
}
|
|
var serverLogFiles []os.FileInfo
|
|
var scanLogFiles []os.FileInfo
|
|
|
|
for _, file := range entries {
|
|
if strings.HasPrefix(file.Name(), "seanime-") {
|
|
info, err := file.Info()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
serverLogFiles = append(serverLogFiles, info)
|
|
}
|
|
if strings.Contains(file.Name(), "-scan") {
|
|
info, err := file.Info()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
// Check if file is newer than 1 day
|
|
if time.Since(info.ModTime()).Hours() < 24 {
|
|
scanLogFiles = append(scanLogFiles, info)
|
|
}
|
|
}
|
|
}
|
|
|
|
userPathPattern := regexp.MustCompile(`(?i)(/home/|/Users/|C:\\Users\\)([^/\\]+)`)
|
|
|
|
if serverStatus != nil {
|
|
serverStatusMarshaled, err := json.Marshal(serverStatus)
|
|
if err == nil {
|
|
// pretty print the json
|
|
var prettyJSON bytes.Buffer
|
|
err = json.Indent(&prettyJSON, serverStatusMarshaled, "", " ")
|
|
if err == nil {
|
|
ret.ServerStatus = prettyJSON.String()
|
|
|
|
for _, redact := range toRedact {
|
|
ret.ServerStatus = strings.ReplaceAll(ret.ServerStatus, redact, "[REDACTED]")
|
|
}
|
|
|
|
ret.ServerStatus = userPathPattern.ReplaceAllString(ret.ServerStatus, "${1}[REDACTED]")
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(serverLogFiles) > 0 {
|
|
sort.Slice(serverLogFiles, func(i, j int) bool {
|
|
return serverLogFiles[i].ModTime().After(serverLogFiles[j].ModTime())
|
|
})
|
|
// Get the most recent log file
|
|
latestLog := serverLogFiles[0]
|
|
latestLogPath := filepath.Join(logsDir, latestLog.Name())
|
|
latestLogContent, err := os.ReadFile(latestLogPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read log file: %w", err)
|
|
}
|
|
|
|
latestLogContent = userPathPattern.ReplaceAll(latestLogContent, []byte("${1}[REDACTED]"))
|
|
|
|
for _, redact := range toRedact {
|
|
latestLogContent = bytes.ReplaceAll(latestLogContent, []byte(redact), []byte("[REDACTED]"))
|
|
}
|
|
ret.ServerLogs = string(latestLogContent)
|
|
}
|
|
|
|
if isAnimeLibraryIssue {
|
|
if len(scanLogFiles) > 0 {
|
|
for _, file := range scanLogFiles {
|
|
scanLogPath := filepath.Join(logsDir, file.Name())
|
|
scanLogContent, err := os.ReadFile(scanLogPath)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
scanLogContent = userPathPattern.ReplaceAll(scanLogContent, []byte("${1}[REDACTED]"))
|
|
|
|
if len(scanLogContent) == 0 {
|
|
ret.ScanLogs = append(ret.ScanLogs, "EMPTY")
|
|
} else {
|
|
ret.ScanLogs = append(ret.ScanLogs, string(scanLogContent))
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (ir *IssueReport) AddClickLogs(clickLogs []*ClickLog) {
|
|
ir.ClickLogs = append(ir.ClickLogs, clickLogs...)
|
|
}
|
|
|
|
func (ir *IssueReport) AddNetworkLogs(networkLogs []*NetworkLog) {
|
|
ir.NetworkLogs = append(ir.NetworkLogs, networkLogs...)
|
|
}
|
|
|
|
func (ir *IssueReport) AddReactQueryLogs(reactQueryLogs []*ReactQueryLog) {
|
|
ir.ReactQueryLogs = append(ir.ReactQueryLogs, reactQueryLogs...)
|
|
}
|
|
|
|
func (ir *IssueReport) AddConsoleLogs(consoleLogs []*ConsoleLog) {
|
|
ir.ConsoleLogs = append(ir.ConsoleLogs, consoleLogs...)
|
|
}
|