Skip to content

Commit 2649f26

Browse files
fjlkaralabe
authored andcommitted
log: fix formatting of big.Int (#22679)
* log: fix formatting of big.Int The implementation of formatLogfmtBigInt had two issues: it crashed when the number was actually large enough to hit the big integer case, and modified the big.Int while formatting it. * log: don't call FormatLogfmtInt64 for int16 * log: separate from decimals back, not front Co-authored-by: Péter Szilágyi <[email protected]>
1 parent 509552e commit 2649f26

File tree

2 files changed

+198
-2
lines changed

2 files changed

+198
-2
lines changed

log/format.go

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"encoding/json"
66
"fmt"
7+
"math/big"
78
"reflect"
89
"strconv"
910
"strings"
@@ -350,15 +351,115 @@ func formatLogfmtValue(value interface{}, term bool) string {
350351
return strconv.FormatFloat(float64(v), floatFormat, 3, 64)
351352
case float64:
352353
return strconv.FormatFloat(v, floatFormat, 3, 64)
353-
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
354-
return fmt.Sprintf("%d", value)
354+
case int8:
355+
return strconv.FormatInt(int64(v), 10)
356+
case uint8:
357+
return strconv.FormatInt(int64(v), 10)
358+
case int16:
359+
return strconv.FormatInt(int64(v), 10)
360+
case uint16:
361+
return strconv.FormatInt(int64(v), 10)
362+
// Larger integers get thousands separators.
363+
case int:
364+
return FormatLogfmtInt64(int64(v))
365+
case int32:
366+
return FormatLogfmtInt64(int64(v))
367+
case int64:
368+
return FormatLogfmtInt64(v)
369+
case uint:
370+
return FormatLogfmtUint64(uint64(v))
371+
case uint32:
372+
return FormatLogfmtUint64(uint64(v))
373+
case uint64:
374+
return FormatLogfmtUint64(v)
355375
case string:
356376
return escapeString(v)
357377
default:
358378
return escapeString(fmt.Sprintf("%+v", value))
359379
}
360380
}
361381

382+
// FormatLogfmtInt64 formats n with thousand separators.
383+
func FormatLogfmtInt64(n int64) string {
384+
if n < 0 {
385+
return formatLogfmtUint64(uint64(-n), true)
386+
}
387+
return formatLogfmtUint64(uint64(n), false)
388+
}
389+
390+
// FormatLogfmtUint64 formats n with thousand separators.
391+
func FormatLogfmtUint64(n uint64) string {
392+
return formatLogfmtUint64(n, false)
393+
}
394+
395+
func formatLogfmtUint64(n uint64, neg bool) string {
396+
// Small numbers are fine as is
397+
if n < 100000 {
398+
if neg {
399+
return strconv.Itoa(-int(n))
400+
} else {
401+
return strconv.Itoa(int(n))
402+
}
403+
}
404+
// Large numbers should be split
405+
const maxLength = 26
406+
407+
var (
408+
out = make([]byte, maxLength)
409+
i = maxLength - 1
410+
comma = 0
411+
)
412+
for ; n > 0; i-- {
413+
if comma == 3 {
414+
comma = 0
415+
out[i] = ','
416+
} else {
417+
comma++
418+
out[i] = '0' + byte(n%10)
419+
n /= 10
420+
}
421+
}
422+
if neg {
423+
out[i] = '-'
424+
i--
425+
}
426+
return string(out[i+1:])
427+
}
428+
429+
// formatLogfmtBigInt formats n with thousand separators.
430+
func formatLogfmtBigInt(n *big.Int) string {
431+
if n.IsUint64() {
432+
return FormatLogfmtUint64(n.Uint64())
433+
}
434+
if n.IsInt64() {
435+
return FormatLogfmtInt64(n.Int64())
436+
}
437+
438+
var (
439+
text = n.String()
440+
buf = make([]byte, len(text)+len(text)/3)
441+
comma = 0
442+
i = len(buf) - 1
443+
)
444+
for j := len(text) - 1; j >= 0; j, i = j-1, i-1 {
445+
c := text[j]
446+
447+
switch {
448+
case c == '-':
449+
buf[i] = c
450+
case comma == 3:
451+
buf[i] = ','
452+
i--
453+
comma = 0
454+
fallthrough
455+
default:
456+
buf[i] = c
457+
comma++
458+
}
459+
}
460+
return string(buf[i+1:])
461+
}
462+
362463
// escapeString checks if the provided string needs escaping/quoting, and
363464
// calls strconv.Quote if needed
364465
func escapeString(s string) string {

log/format_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package log
2+
3+
import (
4+
"math"
5+
"math/big"
6+
"math/rand"
7+
"testing"
8+
)
9+
10+
func TestPrettyInt64(t *testing.T) {
11+
tests := []struct {
12+
n int64
13+
s string
14+
}{
15+
{0, "0"},
16+
{10, "10"},
17+
{-10, "-10"},
18+
{100, "100"},
19+
{-100, "-100"},
20+
{1000, "1000"},
21+
{-1000, "-1000"},
22+
{10000, "10000"},
23+
{-10000, "-10000"},
24+
{99999, "99999"},
25+
{-99999, "-99999"},
26+
{100000, "100,000"},
27+
{-100000, "-100,000"},
28+
{1000000, "1,000,000"},
29+
{-1000000, "-1,000,000"},
30+
{math.MaxInt64, "9,223,372,036,854,775,807"},
31+
{math.MinInt64, "-9,223,372,036,854,775,808"},
32+
}
33+
for i, tt := range tests {
34+
if have := FormatLogfmtInt64(tt.n); have != tt.s {
35+
t.Errorf("test %d: format mismatch: have %s, want %s", i, have, tt.s)
36+
}
37+
}
38+
}
39+
40+
func TestPrettyUint64(t *testing.T) {
41+
tests := []struct {
42+
n uint64
43+
s string
44+
}{
45+
{0, "0"},
46+
{10, "10"},
47+
{100, "100"},
48+
{1000, "1000"},
49+
{10000, "10000"},
50+
{99999, "99999"},
51+
{100000, "100,000"},
52+
{1000000, "1,000,000"},
53+
{math.MaxUint64, "18,446,744,073,709,551,615"},
54+
}
55+
for i, tt := range tests {
56+
if have := FormatLogfmtUint64(tt.n); have != tt.s {
57+
t.Errorf("test %d: format mismatch: have %s, want %s", i, have, tt.s)
58+
}
59+
}
60+
}
61+
62+
func TestPrettyBigInt(t *testing.T) {
63+
tests := []struct {
64+
int string
65+
s string
66+
}{
67+
{"111222333444555678999", "111,222,333,444,555,678,999"},
68+
{"-111222333444555678999", "-111,222,333,444,555,678,999"},
69+
{"11122233344455567899900", "11,122,233,344,455,567,899,900"},
70+
{"-11122233344455567899900", "-11,122,233,344,455,567,899,900"},
71+
}
72+
73+
for _, tt := range tests {
74+
v, _ := new(big.Int).SetString(tt.int, 10)
75+
if have := formatLogfmtBigInt(v); have != tt.s {
76+
t.Errorf("invalid output %s, want %s", have, tt.s)
77+
}
78+
}
79+
}
80+
81+
var sink string
82+
83+
func BenchmarkPrettyInt64Logfmt(b *testing.B) {
84+
b.ReportAllocs()
85+
for i := 0; i < b.N; i++ {
86+
sink = FormatLogfmtInt64(rand.Int63())
87+
}
88+
}
89+
90+
func BenchmarkPrettyUint64Logfmt(b *testing.B) {
91+
b.ReportAllocs()
92+
for i := 0; i < b.N; i++ {
93+
sink = FormatLogfmtUint64(rand.Uint64())
94+
}
95+
}

0 commit comments

Comments
 (0)