repo_description: Delve is a debugger for the Go programming language.
repo_topics: debugger go golang
code: package logflags
  
  import (
  	"bytes"
  	"context"
  	"errors"
  	"fmt"
  	"io"
  	"log"
  	"log/slog"
  	"net"
  	"os"
  	"sort"
  	"strconv"
  	"strings"
  	"time"
  
  	"golang.org/x/telemetry/counter"
  )
  
  var any = false
  var debugger = false
  var gdbWire = false
  var lldbServerOutput = false
  var debugLineErrors = false
  var rpc = false
  var dap = false
  var fnCall = false
  var minidump = false
  var stack = false
  
  var logOut io.WriteCloser
  
  var Bug = counter.NewStack("delve/bug", 16)
  
  func makeLogger(flag bool, attrs ...interface{}) Logger {
  	if lf := loggerFactory; lf != nil {
  		fields := make(Fields)
  		for i := 0; i < len(attrs); i += 2 {
  			fields[attrs[i].(string)] = attrs[i+1]
  		}
  		return lf(flag, fields, logOut)
  	}
  
  	var out io.WriteCloser = os.Stderr
  	if logOut != nil {
  		out = logOut
  	}
  	level := slog.LevelError
  	if flag {
  		level = slog.LevelDebug
  	}
  	logger := slog.New(newTextHandler(out, &slog.HandlerOptions{Level: level})).With(attrs...)
  	return slogLogger{logger}
  }
  
  // Any returns true if any logging is enabled.
  func Any() bool {
  	return any
  }
  
  // GdbWire returns true if the gdbserial package should log all the packets
  // exchanged with the stub.
  func GdbWire() bool {
  	return gdbWire
  }
  
  // GdbWireLogger returns a configured logger for the gdbserial wire protocol.
  func GdbWireLogger() Logger {
  	return makeLogger(gdbWire, "layer", "gdbconn")
  }
  
  // Debugger returns true if the debugger package should log.
  func Debugger() bool {
  	return debugger
  }
  
  // DebuggerLogger returns a logger for the debugger package.
  func DebuggerLogger() Logger {
  	return makeLogger(debugger, "layer", "debugger")
  }
  
  // LLDBServerOutput returns true if the output of the LLDB server should be
  // redirected to standard output instead of suppressed.
  func LLDBServerOutput() bool {
  	return lldbServerOutput
  }
  
  // DebugLineErrors returns true if pkg/dwarf/line should log its recoverable
  // errors.
  func DebugLineErrors() bool {
  	return debugLineErrors
  }
  
  // DebugLineLogger returns a logger for the dwarf/line package.
  func DebugLineLogger() Logger {
  	return makeLogger(debugLineErrors, "layer", "dwarf-line")
  }
  
  // RPC returns true if RPC messages should be logged.
  func RPC() bool {
  	return rpc
  }
  
  // RPCLogger returns a logger for RPC messages.
  func RPCLogger() Logger {
  	return rpcLogger(rpc)
  }
  
  // rpcLogger returns a logger for RPC messages set to a specific minimal log level.
  func rpcLogger(flag bool) Logger {
  	return makeLogger(flag, "layer", "rpc")
  }
  
  // DAP returns true if dap package should log.
  func DAP() bool {
  	return dap
  }
  
  // DAPLogger returns a logger for dap package.
  func DAPLogger() Logger {
  	return makeLogger(dap, "layer", "dap")
  }
  
  // FnCall returns true if the function call protocol should be logged.
  func FnCall() bool {
  	return fnCall
  }
  
  func FnCallLogger() Logger {
  	return makeLogger(fnCall, "layer", "proc", "kind", "fncall")
  }
  
  // Minidump returns true if the minidump loader should be logged.
  func Minidump() bool {
  	return minidump
  }
  
  func MinidumpLogger() Logger {
  	return makeLogger(minidump, "layer", "core", "kind", "minidump")
  }
  
  // Stack returns true if the stacktracer should be logged.
  func Stack() bool {
  	return stack
  }
  
  func StackLogger() Logger {
  	return makeLogger(stack, "layer", "core", "kind", "stack")
  }
  
  // WriteDAPListeningMessage writes the "DAP server listening" message in dap mode.
  func WriteDAPListeningMessage(addr net.Addr) {
  	writeListeningMessage("DAP", addr)
  }
  
  // WriteAPIListeningMessage writes the "API server listening" message in headless mode.
  func WriteAPIListeningMessage(addr net.Addr) {
  	writeListeningMessage("API", addr)
  }
  
  func writeListeningMessage(server string, addr net.Addr) {
  	msg := fmt.Sprintf("%s server listening at: %s", server, addr)
  	if logOut != nil {
  		fmt.Fprintln(logOut, msg)
  	} else {
  		fmt.Println(msg)
  	}
  	tcpAddr, _ := addr.(*net.TCPAddr)
  	if tcpAddr == nil || tcpAddr.IP.IsLoopback() {
  		return
  	}
  	logger := rpcLogger(true)
  	logger.Warnf("Listening for remote connections (connections are not authenticated nor encrypted)")
  }
  
  func WriteError(msg string) {
  	if logOut != nil {
  		fmt.Fprintln(logOut, msg)
  	} else {
  		fmt.Fprintln(os.Stderr, msg)
  	}
  }
  
  func WriteCgoFlagsWarning() {
  	makeLogger(true, "layer", "dlv").Warn("CGO_CFLAGS already set, Cgo code could be optimized.")
  }
  
  var errLogstrWithoutLog = errors.New("--log-output specified without --log")
  
  // Setup sets debugger flags based on the contents of logstr.
  // If logDest is not empty logs will be redirected to the file descriptor or
  // file path specified by logDest.
  func Setup(logFlag bool, logstr, logDest string) error {
  	if logDest != "" {
  		n, err := strconv.Atoi(logDest)
  		if err == nil {
  			logOut = os.NewFile(uintptr(n), "delve-logs")
  		} else {
  			fh, err := os.Create(logDest)
  			if err != nil {
  				return fmt.Errorf("could not create log file: %v", err)
  			}
  			logOut = fh
  		}
  	}
  	log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
  	if !logFlag {
  		log.SetOutput(io.Discard)
  		if logstr != "" {
  			return errLogstrWithoutLog
  		}
  		return nil
  	}
  	if logstr == "" {
  		logstr = "debugger"
  	}
  	any = true
  	v := strings.SplitSeq(logstr, ",")
  	for logcmd := range v {
  		// If adding another value, do make sure to
  		// update "Help about logging flags" in commands.go.
  		switch logcmd {
  		case "debugger":
  			debugger = true
  		case "gdbwire":
  			gdbWire = true
  		case "lldbout":
  			lldbServerOutput = true
  		case "debuglineerr":
  			debugLineErrors = true
  		case "rpc":
  			rpc = true
  		case "dap":
  			dap = true
  		case "fncall":
  			fnCall = true
  		case "minidump":
  			minidump = true
  		case "stack":
  			stack = true
  		default:
  			fmt.Fprintf(os.Stderr, "Warning: unknown log output value %q, run 'dlv help log' for usage.\n", logcmd)
  		}
  	}
  	return nil
  }
  
  // Close closes the logger output.
  func Close() {
  	if logOut != nil {
  		logOut.Close()
  	}
  }
  
  type textHandler struct {
  	out               io.WriteCloser
  	opts              slog.HandlerOptions
  	attrs             []slog.Attr
  	preformattedAttrs string // preformatted version of attrs for optimization purposes
  }
  
  func newTextHandler(out io.WriteCloser, opts *slog.HandlerOptions) *textHandler {
  	return &textHandler{
  		out:  out,
  		opts: *opts,
  	}
  }
  
  func (h *textHandler) Enabled(_ context.Context, l slog.Level) bool {
  	return l >= h.opts.Level.Level()
  }
  
  func (h *textHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
  	h2 := *h
  	h2.attrs = append(h2.attrs, attrs...)
  	m := map[string]slog.Value{}
  	keys := []string{}
  	for i := range attrs {
  		m[attrs[i].Key] = attrs[i].Value
  		keys = append(keys, attrs[i].Key)
  	}
  	sort.Strings(keys)
  	b := new(bytes.Buffer)
  	for _, key := range keys {
  		appendAttr(b, key, m[key])
  	}
  	b.Truncate(b.Len() - 1)
  	h2.preformattedAttrs = b.String()
  	return &h2
  }
  
  func appendAttr(b *bytes.Buffer, key string, val slog.Value) {
  	b.WriteString(key)
  	b.WriteByte('=')
  	stringVal := val.String()
  	if needsQuoting(stringVal) {
  		fmt.Fprintf(b, "%q", stringVal)
  	} else {
  		b.WriteString(stringVal)
  	}
  	b.WriteByte(',')
  }
  
  func (h *textHandler) WithGroup(group string) slog.Handler {
  	// group not handled
  	return h
  }
  
  func (h *textHandler) Handle(_ context.Context, entry slog.Record) error {
  	b := &bytes.Buffer{}
  
  	b.WriteString(entry.Time.Format(time.RFC3339))
  	b.WriteByte(' ')
  	b.WriteString(strings.ToLower(entry.Level.String()))
  	b.WriteByte(' ')
  	b.WriteString(h.preformattedAttrs)
  
  	if entry.NumAttrs() > 0 {
  		if len(h.preformattedAttrs) > 0 {
  			b.WriteByte(' ')
  		}
  		entry.Attrs(func(attr slog.Attr) bool {
  			appendAttr(b, attr.Key, attr.Value)
  			return true
  		})
  		b.Truncate(b.Len() - 1)
  	}
  
  	if len(h.preformattedAttrs) > 0 || entry.NumAttrs() > 0 {
  		b.WriteByte(' ')
  	}
  
  	b.WriteString(entry.Message)
  	b.WriteByte('\n')
  	_, err := h.out.Write(b.Bytes())
  	return err
  }
  
  func needsQuoting(text string) bool {
  	for _, ch := range text {
  		if !((ch >= 'a' && ch <= 'z') ||
  			(ch >= 'A' && ch <= 'Z') ||
  			(ch >= '0' && ch <= '9') ||
  			ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
  			return true
  		}
  	}
  	return false
  }
  