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:
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user