package debug import ( "bytes" "fmt" "runtime/pprof" "strings" "time" "github.com/safing/portbase/info" "github.com/safing/portbase/log" "github.com/safing/portbase/modules" ) // Info gathers debugging information and stores everything in a buffer in // order to write it to somewhere later. It directly inherits a bytes.Buffer, // so you can also use all these functions too. type Info struct { bytes.Buffer Style string } // InfoFlag defines possible options for adding sections to a Info. type InfoFlag int const ( // NoFlags does nothing. NoFlags InfoFlag = 0 // UseCodeSection wraps the section content in a markdown code section. UseCodeSection InfoFlag = 1 // AddContentLineBreaks adds a line breaks after each line of content, // except for the last. AddContentLineBreaks InfoFlag = 2 ) func useCodeSection(flags InfoFlag) bool { return flags&UseCodeSection > 0 } func addContentLineBreaks(flags InfoFlag) bool { return flags&AddContentLineBreaks > 0 } // AddSection adds a debug section to the Info. The result is directly // written into the buffer. func (di *Info) AddSection(name string, flags InfoFlag, content ...string) { // Check if we need a spacer. if di.Len() > 0 { _, _ = di.WriteString("\n\n") } // Write section to buffer. // Write section header. if di.Style == "github" { _, _ = di.WriteString(fmt.Sprintf("
\n%s\n\n", name)) } else { _, _ = di.WriteString(fmt.Sprintf("**%s**:\n\n", name)) } // Write section content. if useCodeSection(flags) { // Write code header: Needs one empty line between previous data. _, _ = di.WriteString("```\n") } for i, part := range content { _, _ = di.WriteString(part) if addContentLineBreaks(flags) && i < len(content)-1 { _, _ = di.WriteString("\n") } } if useCodeSection(flags) { // Write code footer: Needs one empty line between next data. _, _ = di.WriteString("\n```\n") } // Write section header. if di.Style == "github" { _, _ = di.WriteString("\n
") } } // AddVersionInfo adds version information from the info pkg. func (di *Info) AddVersionInfo() { di.AddSection( "Version "+info.Version(), UseCodeSection, info.FullVersion(), ) } // AddGoroutineStack adds the current goroutine stack. func (di *Info) AddGoroutineStack() { buf := new(bytes.Buffer) err := pprof.Lookup("goroutine").WriteTo(buf, 1) if err != nil { di.AddSection( "Goroutine Stack", NoFlags, fmt.Sprintf("Failed to get: %s", err), ) return } // Add section. di.AddSection( "Goroutine Stack", UseCodeSection, buf.String(), ) } // AddLastReportedModuleError adds the last reported module error, if one exists. func (di *Info) AddLastReportedModuleError() { me := modules.GetLastReportedError() if me == nil { di.AddSection("No Module Error", NoFlags) return } di.AddSection( fmt.Sprintf("%s Module Error", strings.Title(me.ModuleName)), //nolint:staticcheck UseCodeSection, me.Format(), ) } // AddLastUnexpectedLogs adds the last 10 unexpected log lines, if any. func (di *Info) AddLastUnexpectedLogs() { lines := log.GetLastUnexpectedLogs() // Check if there is anything at all. if len(lines) == 0 { di.AddSection("No Unexpected Logs", NoFlags) return } di.AddSection( "Unexpected Logs", UseCodeSection|AddContentLineBreaks, append( lines, fmt.Sprintf("%s CURRENT TIME", time.Now().Format("060102 15:04:05.000")), )..., ) }