golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/quic/qlog/json_writer.go (about)

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build go1.21
     6  
     7  package qlog
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"io"
    13  	"log/slog"
    14  	"strconv"
    15  	"sync"
    16  	"time"
    17  )
    18  
    19  // A jsonWriter writes JSON-SEQ (RFC 7464).
    20  //
    21  // A JSON-SEQ file consists of a series of JSON text records,
    22  // each beginning with an RS (0x1e) character and ending with LF (0x0a).
    23  type jsonWriter struct {
    24  	mu  sync.Mutex
    25  	w   io.WriteCloser
    26  	buf bytes.Buffer
    27  }
    28  
    29  // writeRecordStart writes the start of a JSON-SEQ record.
    30  func (w *jsonWriter) writeRecordStart() {
    31  	w.mu.Lock()
    32  	w.buf.WriteByte(0x1e)
    33  	w.buf.WriteByte('{')
    34  }
    35  
    36  // writeRecordEnd finishes writing a JSON-SEQ record.
    37  func (w *jsonWriter) writeRecordEnd() {
    38  	w.buf.WriteByte('}')
    39  	w.buf.WriteByte('\n')
    40  	w.w.Write(w.buf.Bytes())
    41  	w.buf.Reset()
    42  	w.mu.Unlock()
    43  }
    44  
    45  func (w *jsonWriter) writeAttrs(attrs []slog.Attr) {
    46  	w.buf.WriteByte('{')
    47  	for _, a := range attrs {
    48  		w.writeAttr(a)
    49  	}
    50  	w.buf.WriteByte('}')
    51  }
    52  
    53  func (w *jsonWriter) writeAttr(a slog.Attr) {
    54  	if a.Key == "" {
    55  		return
    56  	}
    57  	w.writeName(a.Key)
    58  	w.writeValue(a.Value)
    59  }
    60  
    61  // writeAttr writes a []slog.Attr as an object field.
    62  func (w *jsonWriter) writeAttrsField(name string, attrs []slog.Attr) {
    63  	w.writeName(name)
    64  	w.writeAttrs(attrs)
    65  }
    66  
    67  func (w *jsonWriter) writeValue(v slog.Value) {
    68  	v = v.Resolve()
    69  	switch v.Kind() {
    70  	case slog.KindAny:
    71  		switch v := v.Any().(type) {
    72  		case []slog.Value:
    73  			w.writeArray(v)
    74  		case interface{ AppendJSON([]byte) []byte }:
    75  			w.buf.Write(v.AppendJSON(w.buf.AvailableBuffer()))
    76  		default:
    77  			w.writeString(fmt.Sprint(v))
    78  		}
    79  	case slog.KindBool:
    80  		w.writeBool(v.Bool())
    81  	case slog.KindDuration:
    82  		w.writeDuration(v.Duration())
    83  	case slog.KindFloat64:
    84  		w.writeFloat64(v.Float64())
    85  	case slog.KindInt64:
    86  		w.writeInt64(v.Int64())
    87  	case slog.KindString:
    88  		w.writeString(v.String())
    89  	case slog.KindTime:
    90  		w.writeTime(v.Time())
    91  	case slog.KindUint64:
    92  		w.writeUint64(v.Uint64())
    93  	case slog.KindGroup:
    94  		w.writeAttrs(v.Group())
    95  	default:
    96  		w.writeString("unhandled kind")
    97  	}
    98  }
    99  
   100  // writeName writes an object field name followed by a colon.
   101  func (w *jsonWriter) writeName(name string) {
   102  	if b := w.buf.Bytes(); len(b) > 0 && b[len(b)-1] != '{' {
   103  		// Add the comma separating this from the previous field.
   104  		w.buf.WriteByte(',')
   105  	}
   106  	w.writeString(name)
   107  	w.buf.WriteByte(':')
   108  }
   109  
   110  func (w *jsonWriter) writeObject(f func()) {
   111  	w.buf.WriteByte('{')
   112  	f()
   113  	w.buf.WriteByte('}')
   114  }
   115  
   116  // writeObject writes an object-valued object field.
   117  // The function f is called to write the contents.
   118  func (w *jsonWriter) writeObjectField(name string, f func()) {
   119  	w.writeName(name)
   120  	w.writeObject(f)
   121  }
   122  
   123  func (w *jsonWriter) writeArray(vals []slog.Value) {
   124  	w.buf.WriteByte('[')
   125  	for i, v := range vals {
   126  		if i != 0 {
   127  			w.buf.WriteByte(',')
   128  		}
   129  		w.writeValue(v)
   130  	}
   131  	w.buf.WriteByte(']')
   132  }
   133  
   134  func (w *jsonWriter) writeRaw(v string) {
   135  	w.buf.WriteString(v)
   136  }
   137  
   138  // writeRawField writes a field with a raw JSON value.
   139  func (w *jsonWriter) writeRawField(name, v string) {
   140  	w.writeName(name)
   141  	w.writeRaw(v)
   142  }
   143  
   144  func (w *jsonWriter) writeBool(v bool) {
   145  	if v {
   146  		w.buf.WriteString("true")
   147  	} else {
   148  		w.buf.WriteString("false")
   149  	}
   150  }
   151  
   152  // writeBoolField writes a bool-valued object field.
   153  func (w *jsonWriter) writeBoolField(name string, v bool) {
   154  	w.writeName(name)
   155  	w.writeBool(v)
   156  }
   157  
   158  // writeDuration writes a duration as milliseconds.
   159  func (w *jsonWriter) writeDuration(v time.Duration) {
   160  	if v < 0 {
   161  		w.buf.WriteByte('-')
   162  		v = -v
   163  	}
   164  	fmt.Fprintf(&w.buf, "%d.%06d", v.Milliseconds(), v%time.Millisecond)
   165  }
   166  
   167  // writeDurationField writes a millisecond duration-valued object field.
   168  func (w *jsonWriter) writeDurationField(name string, v time.Duration) {
   169  	w.writeName(name)
   170  	w.writeDuration(v)
   171  }
   172  
   173  func (w *jsonWriter) writeFloat64(v float64) {
   174  	w.buf.Write(strconv.AppendFloat(w.buf.AvailableBuffer(), v, 'f', -1, 64))
   175  }
   176  
   177  // writeFloat64Field writes an float64-valued object field.
   178  func (w *jsonWriter) writeFloat64Field(name string, v float64) {
   179  	w.writeName(name)
   180  	w.writeFloat64(v)
   181  }
   182  
   183  func (w *jsonWriter) writeInt64(v int64) {
   184  	w.buf.Write(strconv.AppendInt(w.buf.AvailableBuffer(), v, 10))
   185  }
   186  
   187  // writeInt64Field writes an int64-valued object field.
   188  func (w *jsonWriter) writeInt64Field(name string, v int64) {
   189  	w.writeName(name)
   190  	w.writeInt64(v)
   191  }
   192  
   193  func (w *jsonWriter) writeUint64(v uint64) {
   194  	w.buf.Write(strconv.AppendUint(w.buf.AvailableBuffer(), v, 10))
   195  }
   196  
   197  // writeUint64Field writes a uint64-valued object field.
   198  func (w *jsonWriter) writeUint64Field(name string, v uint64) {
   199  	w.writeName(name)
   200  	w.writeUint64(v)
   201  }
   202  
   203  // writeTime writes a time as seconds since the Unix epoch.
   204  func (w *jsonWriter) writeTime(v time.Time) {
   205  	fmt.Fprintf(&w.buf, "%d.%06d", v.UnixMilli(), v.Nanosecond()%int(time.Millisecond))
   206  }
   207  
   208  // writeTimeField writes a time-valued object field.
   209  func (w *jsonWriter) writeTimeField(name string, v time.Time) {
   210  	w.writeName(name)
   211  	w.writeTime(v)
   212  }
   213  
   214  func jsonSafeSet(c byte) bool {
   215  	// mask is a 128-bit bitmap with 1s for allowed bytes,
   216  	// so that the byte c can be tested with a shift and an and.
   217  	// If c > 128, then 1<<c and 1<<(c-64) will both be zero,
   218  	// and this function will return false.
   219  	const mask = 0 |
   220  		(1<<(0x22-0x20)-1)<<0x20 |
   221  		(1<<(0x5c-0x23)-1)<<0x23 |
   222  		(1<<(0x7f-0x5d)-1)<<0x5d
   223  	return ((uint64(1)<<c)&(mask&(1<<64-1)) |
   224  		(uint64(1)<<(c-64))&(mask>>64)) != 0
   225  }
   226  
   227  func jsonNeedsEscape(s string) bool {
   228  	for i := range s {
   229  		if !jsonSafeSet(s[i]) {
   230  			return true
   231  		}
   232  	}
   233  	return false
   234  }
   235  
   236  // writeString writes an ASCII string.
   237  //
   238  // qlog fields should never contain anything that isn't ASCII,
   239  // so we do the bare minimum to avoid producing invalid output if we
   240  // do write something unexpected.
   241  func (w *jsonWriter) writeString(v string) {
   242  	w.buf.WriteByte('"')
   243  	if !jsonNeedsEscape(v) {
   244  		w.buf.WriteString(v)
   245  	} else {
   246  		for i := range v {
   247  			if jsonSafeSet(v[i]) {
   248  				w.buf.WriteByte(v[i])
   249  			} else {
   250  				fmt.Fprintf(&w.buf, `\u%04x`, v[i])
   251  			}
   252  		}
   253  	}
   254  	w.buf.WriteByte('"')
   255  }
   256  
   257  // writeStringField writes a string-valued object field.
   258  func (w *jsonWriter) writeStringField(name, v string) {
   259  	w.writeName(name)
   260  	w.writeString(v)
   261  }