Skip to content

Commit 10ebe83

Browse files
committed
Remove null byte from log output
Strip the [v\x00JSON] prefix used by logtail for structured logging before passing log messages to user-defined Logf. This prevents null bytes from appearing in log files when the logging bypasses logtail processing. Fixes: SagerNet/sing-box#3481
1 parent e284958 commit 10ebe83

File tree

1 file changed

+27
-1
lines changed

1 file changed

+27
-1
lines changed

tsnet/tsnet.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,8 +573,14 @@ func (s *Server) start() (reterr error) {
573573
if s.Logf == nil {
574574
return
575575
}
576-
s.Logf(format, a...)
576+
// Format the log message and remove the [v\x00JSON] prefix
577+
// that is used internally by logtail for structured logging.
578+
// See: https://github.com/SagerNet/sing-box/issues/3481
579+
msg := fmt.Sprintf(format, a...)
580+
msg = removeJSONLogPrefix(msg)
581+
s.Logf("%s", msg)
577582
}
583+
578584

579585
sys := tsd.NewSystem()
580586
s.sys = sys
@@ -1371,3 +1377,23 @@ type addr struct{ ln *listener }
13711377

13721378
func (a addr) Network() string { return a.ln.keys[0].network }
13731379
func (a addr) String() string { return a.ln.addr }
1380+
1381+
// vJSONPrefix is the magic prefix used by logtail for structured JSON logging.
1382+
// The null byte (\x00) is used as a marker that logtail recognizes and strips,
1383+
// but if the log bypasses logtail processing, it appears in the output.
1384+
const vJSONPrefix = "[v\x00JSON]"
1385+
1386+
// removeJSONLogPrefix removes the [v\x00JSON] prefix and log level digit
1387+
// from a log message. This prefix is used internally by tailscale's logger
1388+
// for structured logging, but should not appear in user-visible logs.
1389+
func removeJSONLogPrefix(msg string) string {
1390+
if idx := strings.Index(msg, vJSONPrefix); idx != -1 {
1391+
rest := msg[idx+len(vJSONPrefix):]
1392+
if len(rest) >= 1 && rest[0] >= '0' && rest[0] <= '9' {
1393+
// Skip the log level digit
1394+
return msg[:idx] + rest[1:]
1395+
}
1396+
return msg[:idx] + rest
1397+
}
1398+
return msg
1399+
}

0 commit comments

Comments
 (0)