glog: add JSON structured logging mode (#8708)

* glog: add JSON structured logging mode

Add opt-in JSON output format for glog, enabling integration with
log aggregation systems like ELK, Loki, and Datadog.

- Add --log_json flag to enable JSON output at startup
- Add SetJSONMode()/IsJSONMode() for runtime toggle
- Add JSON branches in println, printDepth, printf, printWithFileLine
- Use manual JSON construction (no encoding/json) for performance
- Add jsonEscapeString() for safe string escaping
- Include 8 unit tests and 1 benchmark

Enabled via --log_json flag. Default behavior unchanged.

* glog: prevent lazy flag init from overriding SetJSONMode

If --log_json=true and SetJSONMode(false) was called at runtime,
a subsequent IsJSONMode() call would re-enable JSON mode via the
sync.Once lazy initialization. Mark jsonFlagOnce as done inside
SetJSONMode so the runtime API always takes precedence.

* glog: fix RuneError check to not misclassify valid U+FFFD

The condition r == utf8.RuneError matches both invalid UTF-8
sequences (size=1) and a valid U+FFFD replacement character
(size=3). Without checking size == 1, a valid U+FFFD input
would be incorrectly escaped and only advance by 1 byte,
corrupting the output.

---------

Co-authored-by: Chris Lu <chris.lu@gmail.com>
This commit is contained in:
JARDEL ALVES
2026-03-20 02:01:09 -03:00
committed by GitHub
parent 5f2244d25d
commit 1413822424
4 changed files with 406 additions and 0 deletions

View File

@@ -630,7 +630,21 @@ func (buf *buffer) someDigits(i, d int) int {
return copy(buf.tmp[i:], buf.tmp[j:])
}
// logJSON is a helper that formats and outputs a JSON log line.
// Used by println, printDepth, and printf to avoid code duplication.
// depth is incremented by 1 to account for this extra call frame.
func (l *loggingT) logJSON(s severity, depth int, msg string) {
buf, file, line := l.formatJSON(s, depth+1)
buf.WriteString(jsonEscapeString(strings.TrimRight(msg, "\n")))
finishJSON(buf)
l.output(s, buf, file, line, false)
}
func (l *loggingT) println(s severity, args ...interface{}) {
if IsJSONMode() {
l.logJSON(s, 0, fmt.Sprintln(args...))
return
}
buf, file, line := l.header(s, 0)
fmt.Fprintln(buf, args...)
l.output(s, buf, file, line, false)
@@ -641,6 +655,10 @@ func (l *loggingT) print(s severity, args ...interface{}) {
}
func (l *loggingT) printDepth(s severity, depth int, args ...interface{}) {
if IsJSONMode() {
l.logJSON(s, depth, fmt.Sprint(args...))
return
}
buf, file, line := l.header(s, depth)
fmt.Fprint(buf, args...)
if buf.Bytes()[buf.Len()-1] != '\n' {
@@ -650,6 +668,10 @@ func (l *loggingT) printDepth(s severity, depth int, args ...interface{}) {
}
func (l *loggingT) printf(s severity, format string, args ...interface{}) {
if IsJSONMode() {
l.logJSON(s, 0, fmt.Sprintf(format, args...))
return
}
buf, file, line := l.header(s, 0)
fmt.Fprintf(buf, format, args...)
if buf.Bytes()[buf.Len()-1] != '\n' {
@@ -662,6 +684,36 @@ func (l *loggingT) printf(s severity, format string, args ...interface{}) {
// alsoLogToStderr is true, the log message always appears on standard error; it
// will also appear in the log file unless --logtostderr is set.
func (l *loggingT) printWithFileLine(s severity, file string, line int, alsoToStderr bool, args ...interface{}) {
if IsJSONMode() {
buf := l.getBuffer()
now := timeNow()
buf.WriteString(`{"ts":"`)
buf.WriteString(now.UTC().Format(time.RFC3339Nano))
buf.WriteString(`","level":"`)
switch {
case s == infoLog:
buf.WriteString("INFO")
case s == warningLog:
buf.WriteString("WARNING")
case s == errorLog:
buf.WriteString("ERROR")
case s >= fatalLog:
buf.WriteString("FATAL")
}
buf.WriteString(`","file":"`)
buf.WriteString(jsonEscapeString(file))
buf.WriteString(`","line":`)
if line < 0 {
line = 0
}
buf.WriteString(itoa(line))
buf.WriteString(`,"msg":"`)
msg := fmt.Sprint(args...)
buf.WriteString(jsonEscapeString(strings.TrimRight(msg, "\n")))
finishJSON(buf)
l.output(s, buf, file, line, alsoToStderr)
return
}
buf := l.formatHeader(s, file, line)
fmt.Fprint(buf, args...)
if buf.Bytes()[buf.Len()-1] != '\n' {
@@ -873,6 +925,11 @@ func (sb *syncBuffer) rotateFile(now time.Time) error {
sb.Writer = bufio.NewWriterSize(sb.file, bufferSize)
// Skip text header in JSON mode to keep files as valid NDJSON.
if IsJSONMode() {
return nil
}
// Write header.
var buf bytes.Buffer
fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05"))