github.com/nikandfor/tlog@v0.21.5-0.20231108111739-3ef89426a96d/convert/json.go (about) 1 package convert 2 3 import ( 4 "encoding/base64" 5 "errors" 6 "io" 7 "path/filepath" 8 "strconv" 9 "time" 10 11 "github.com/nikandfor/hacked/hfmt" 12 "github.com/nikandfor/loc" 13 14 "github.com/nikandfor/tlog" 15 "github.com/nikandfor/tlog/low" 16 "github.com/nikandfor/tlog/tlwire" 17 ) 18 19 type ( 20 JSON struct { 21 io.Writer 22 23 AppendNewLine bool 24 AppendKeySafe bool 25 TimeFormat string 26 TimeZone *time.Location 27 28 Rename RenameFunc 29 30 d tlwire.Decoder 31 32 b low.Buf 33 } 34 35 RenameFunc func(b, p, k []byte, st int) ([]byte, bool) 36 37 TagSub struct { 38 Tag byte 39 Sub int64 40 } 41 42 SimpleRenameRule struct { 43 Tags []TagSub 44 Key string 45 } 46 47 SimpleRenamer struct { 48 tlwire.Decoder 49 50 Rules map[string]SimpleRenameRule 51 52 Fallback RenameFunc 53 } 54 ) 55 56 func NewJSON(w io.Writer) *JSON { 57 return &JSON{ 58 Writer: w, 59 AppendNewLine: true, 60 AppendKeySafe: true, 61 TimeFormat: time.RFC3339Nano, 62 TimeZone: time.Local, 63 } 64 } 65 66 func (w *JSON) Write(p []byte) (i int, err error) { 67 b := w.b[:0] 68 69 more: 70 tag, els, i := w.d.Tag(p, i) 71 if tag != tlwire.Map { 72 return i, errors.New("map expected") 73 } 74 75 b = append(b, '{') 76 77 var k []byte 78 for el := 0; els == -1 || el < int(els); el++ { 79 if els == -1 && w.d.Break(p, &i) { 80 break 81 } 82 83 if el != 0 { 84 b = append(b, ',') 85 } 86 87 b = append(b, '"') 88 89 k, i = w.d.Bytes(p, i) 90 91 var renamed bool 92 93 if w.Rename != nil { 94 b, renamed = w.Rename(b, p, k, i) 95 } 96 97 if !renamed { 98 if w.AppendKeySafe { 99 b = low.AppendSafe(b, k) 100 } else { 101 b = append(b, k...) 102 } 103 } 104 105 b = append(b, '"', ':') 106 107 b, i = w.ConvertValue(b, p, i) 108 } 109 110 b = append(b, '}') 111 if w.AppendNewLine { 112 b = append(b, '\n') 113 } 114 115 if i < len(p) { 116 goto more 117 } 118 119 w.b = b[:0] 120 121 _, err = w.Writer.Write(b) 122 if err != nil { 123 return 0, err 124 } 125 126 return len(p), nil 127 } 128 129 func (w *JSON) ConvertValue(b, p []byte, st int) (_ []byte, i int) { 130 tag, sub, i := w.d.Tag(p, st) 131 132 switch tag { 133 case tlwire.Int: 134 b = strconv.AppendUint(b, uint64(sub), 10) 135 case tlwire.Neg: 136 b = strconv.AppendInt(b, sub, 10) 137 case tlwire.Bytes: 138 b = append(b, '"') 139 140 m := base64.StdEncoding.EncodedLen(int(sub)) 141 st := len(b) 142 143 for st+m < cap(b) { 144 b = append(b[:cap(b)], 0, 0, 0, 0) 145 } 146 147 b = b[:st+m] 148 149 base64.StdEncoding.Encode(b[st:], p[i:]) 150 151 b = append(b, '"') 152 153 i += int(sub) 154 case tlwire.String: 155 b = append(b, '"') 156 157 b = low.AppendSafe(b, p[i:i+int(sub)]) 158 159 b = append(b, '"') 160 161 i += int(sub) 162 case tlwire.Array: 163 b = append(b, '[') 164 165 for el := 0; sub == -1 || el < int(sub); el++ { 166 if sub == -1 && w.d.Break(p, &i) { 167 break 168 } 169 170 if el != 0 { 171 b = append(b, ',') 172 } 173 174 b, i = w.ConvertValue(b, p, i) 175 } 176 177 b = append(b, ']') 178 case tlwire.Map: 179 var k []byte 180 181 b = append(b, '{') 182 183 for el := 0; sub == -1 || el < int(sub); el++ { 184 if sub == -1 && w.d.Break(p, &i) { 185 break 186 } 187 188 if el != 0 { 189 b = append(b, ',') 190 } 191 192 k, i = w.d.Bytes(p, i) 193 194 b = append(b, '"') 195 196 if w.AppendKeySafe { 197 b = low.AppendSafe(b, k) 198 } else { 199 b = append(b, k...) 200 } 201 202 b = append(b, '"', ':') 203 204 b, i = w.ConvertValue(b, p, i) 205 } 206 207 b = append(b, '}') 208 case tlwire.Semantic: 209 switch sub { 210 case tlwire.Time: 211 var t time.Time 212 t, i = w.d.Time(p, st) 213 214 if w.TimeZone != nil { 215 t = t.In(w.TimeZone) 216 } 217 218 if w.TimeFormat != "" { 219 b = append(b, '"') 220 b = t.AppendFormat(b, w.TimeFormat) 221 b = append(b, '"') 222 } else { 223 b = strconv.AppendInt(b, t.UnixNano(), 10) 224 } 225 case tlog.WireID: 226 var id tlog.ID 227 i = id.TlogParse(p, st) 228 229 bst := len(b) + 1 230 b = append(b, `"123456789_123456789_123456789_12"`...) 231 232 id.FormatTo(b[bst:], 'x') 233 case tlwire.Caller: 234 var pc loc.PC 235 var pcs loc.PCs 236 pc, pcs, i = w.d.Callers(p, st) 237 238 if pcs != nil { 239 b = append(b, '[') 240 for i, pc := range pcs { 241 if i != 0 { 242 b = append(b, ',') 243 } 244 245 _, file, line := pc.NameFileLine() 246 b = hfmt.Appendf(b, `"%v:%d"`, filepath.Base(file), line) 247 } 248 b = append(b, ']') 249 } else { 250 _, file, line := pc.NameFileLine() 251 252 b = hfmt.Appendf(b, `"%v:%d"`, filepath.Base(file), line) 253 } 254 default: 255 b, i = w.ConvertValue(b, p, i) 256 } 257 case tlwire.Special: 258 switch sub { 259 case tlwire.False: 260 b = append(b, "false"...) 261 case tlwire.True: 262 b = append(b, "true"...) 263 case tlwire.Nil, tlwire.Undefined, tlwire.None, tlwire.Hidden, tlwire.SelfRef: 264 b = append(b, "null"...) 265 case tlwire.Float64, tlwire.Float32, tlwire.Float16, tlwire.Float8: 266 var f float64 267 f, i = w.d.Float(p, st) 268 269 b = strconv.AppendFloat(b, f, 'f', -1, 64) 270 default: 271 panic(sub) 272 } 273 } 274 275 return b, i 276 } 277 278 func (r SimpleRenamer) Rename(b, p, k []byte, i int) ([]byte, bool) { 279 rule, ok := r.Rules[string(k)] 280 if !ok { 281 return r.fallback(b, p, k, i) 282 } 283 284 for _, ts := range rule.Tags { 285 tag, sub, j := r.Tag(p, i) 286 287 if tag != tlwire.Semantic && tag != tlwire.Special { 288 sub = 0 289 } 290 291 if ts != (TagSub{tag, sub}) { 292 return r.fallback(b, p, k, i) 293 } 294 295 i = j 296 } 297 298 return append(b, rule.Key...), true 299 } 300 301 func (r SimpleRenamer) fallback(b, p, k []byte, i int) ([]byte, bool) { 302 if r.Fallback == nil { 303 return b, false 304 } 305 306 return r.Fallback(b, p, k, i) 307 }