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  }