github.com/safing/portbase@v0.19.5/utils/debug/debug.go (about) 1 package debug 2 3 import ( 4 "bytes" 5 "fmt" 6 "runtime/pprof" 7 "strings" 8 "time" 9 10 "github.com/safing/portbase/info" 11 "github.com/safing/portbase/log" 12 "github.com/safing/portbase/modules" 13 ) 14 15 // Info gathers debugging information and stores everything in a buffer in 16 // order to write it to somewhere later. It directly inherits a bytes.Buffer, 17 // so you can also use all these functions too. 18 type Info struct { 19 bytes.Buffer 20 Style string 21 } 22 23 // InfoFlag defines possible options for adding sections to a Info. 24 type InfoFlag int 25 26 const ( 27 // NoFlags does nothing. 28 NoFlags InfoFlag = 0 29 30 // UseCodeSection wraps the section content in a markdown code section. 31 UseCodeSection InfoFlag = 1 32 33 // AddContentLineBreaks adds a line breaks after each line of content, 34 // except for the last. 35 AddContentLineBreaks InfoFlag = 2 36 ) 37 38 func useCodeSection(flags InfoFlag) bool { 39 return flags&UseCodeSection > 0 40 } 41 42 func addContentLineBreaks(flags InfoFlag) bool { 43 return flags&AddContentLineBreaks > 0 44 } 45 46 // AddSection adds a debug section to the Info. The result is directly 47 // written into the buffer. 48 func (di *Info) AddSection(name string, flags InfoFlag, content ...string) { 49 // Check if we need a spacer. 50 if di.Len() > 0 { 51 _, _ = di.WriteString("\n\n") 52 } 53 54 // Write section to buffer. 55 56 // Write section header. 57 if di.Style == "github" { 58 _, _ = di.WriteString(fmt.Sprintf("<details>\n<summary>%s</summary>\n\n", name)) 59 } else { 60 _, _ = di.WriteString(fmt.Sprintf("**%s**:\n\n", name)) 61 } 62 63 // Write section content. 64 if useCodeSection(flags) { 65 // Write code header: Needs one empty line between previous data. 66 _, _ = di.WriteString("```\n") 67 } 68 for i, part := range content { 69 _, _ = di.WriteString(part) 70 if addContentLineBreaks(flags) && i < len(content)-1 { 71 _, _ = di.WriteString("\n") 72 } 73 } 74 if useCodeSection(flags) { 75 // Write code footer: Needs one empty line between next data. 76 _, _ = di.WriteString("\n```\n") 77 } 78 79 // Write section header. 80 if di.Style == "github" { 81 _, _ = di.WriteString("\n</details>") 82 } 83 } 84 85 // AddVersionInfo adds version information from the info pkg. 86 func (di *Info) AddVersionInfo() { 87 di.AddSection( 88 "Version "+info.Version(), 89 UseCodeSection, 90 info.FullVersion(), 91 ) 92 } 93 94 // AddGoroutineStack adds the current goroutine stack. 95 func (di *Info) AddGoroutineStack() { 96 buf := new(bytes.Buffer) 97 err := pprof.Lookup("goroutine").WriteTo(buf, 1) 98 if err != nil { 99 di.AddSection( 100 "Goroutine Stack", 101 NoFlags, 102 fmt.Sprintf("Failed to get: %s", err), 103 ) 104 return 105 } 106 107 // Add section. 108 di.AddSection( 109 "Goroutine Stack", 110 UseCodeSection, 111 buf.String(), 112 ) 113 } 114 115 // AddLastReportedModuleError adds the last reported module error, if one exists. 116 func (di *Info) AddLastReportedModuleError() { 117 me := modules.GetLastReportedError() 118 if me == nil { 119 di.AddSection("No Module Error", NoFlags) 120 return 121 } 122 123 di.AddSection( 124 fmt.Sprintf("%s Module Error", strings.Title(me.ModuleName)), //nolint:staticcheck 125 UseCodeSection, 126 me.Format(), 127 ) 128 } 129 130 // AddLastUnexpectedLogs adds the last 10 unexpected log lines, if any. 131 func (di *Info) AddLastUnexpectedLogs() { 132 lines := log.GetLastUnexpectedLogs() 133 134 // Check if there is anything at all. 135 if len(lines) == 0 { 136 di.AddSection("No Unexpected Logs", NoFlags) 137 return 138 } 139 140 di.AddSection( 141 "Unexpected Logs", 142 UseCodeSection|AddContentLineBreaks, 143 append( 144 lines, 145 fmt.Sprintf("%s CURRENT TIME", time.Now().Format("060102 15:04:05.000")), 146 )..., 147 ) 148 }