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 <peterke@gmail.com>
This commit is contained in:
Felix Lange 2021-04-16 08:27:16 +02:00 committed by GitHub
parent 3cfd0fe7a8
commit fda93f643e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 54 additions and 24 deletions

@ -359,11 +359,16 @@ func formatLogfmtValue(value interface{}, term bool) string {
return strconv.FormatFloat(float64(v), floatFormat, 3, 64)
case float64:
return strconv.FormatFloat(v, floatFormat, 3, 64)
case int8, uint8:
return fmt.Sprintf("%d", value)
case int:
return FormatLogfmtInt64(int64(v))
case int8:
return strconv.FormatInt(int64(v), 10)
case uint8:
return strconv.FormatInt(int64(v), 10)
case int16:
return strconv.FormatInt(int64(v), 10)
case uint16:
return strconv.FormatInt(int64(v), 10)
// Larger integers get thousands separators.
case int:
return FormatLogfmtInt64(int64(v))
case int32:
return FormatLogfmtInt64(int64(v))
@ -371,8 +376,6 @@ func formatLogfmtValue(value interface{}, term bool) string {
return FormatLogfmtInt64(v)
case uint:
return FormatLogfmtUint64(uint64(v))
case uint16:
return FormatLogfmtUint64(uint64(v))
case uint32:
return FormatLogfmtUint64(uint64(v))
case uint64:
@ -384,7 +387,7 @@ func formatLogfmtValue(value interface{}, term bool) string {
}
}
// FormatLogfmtInt64 formats a potentially big number in a friendlier split format.
// FormatLogfmtInt64 formats n with thousand separators.
func FormatLogfmtInt64(n int64) string {
if n < 0 {
return formatLogfmtUint64(uint64(-n), true)
@ -392,7 +395,7 @@ func FormatLogfmtInt64(n int64) string {
return formatLogfmtUint64(uint64(n), false)
}
// FormatLogfmtUint64 formats a potentially big number in a friendlier split format.
// FormatLogfmtUint64 formats n with thousand separators.
func FormatLogfmtUint64(n uint64) string {
return formatLogfmtUint64(n, false)
}
@ -431,31 +434,38 @@ func formatLogfmtUint64(n uint64, neg bool) string {
return string(out[i+1:])
}
var big1000 = big.NewInt(1000)
// formatLogfmtBigInt formats a potentially gigantic number in a friendlier split
// format.
// formatLogfmtBigInt formats n with thousand separators.
func formatLogfmtBigInt(n *big.Int) string {
// Most number don't need fancy handling, just downcast
if n.IsUint64() {
return FormatLogfmtUint64(n.Uint64())
}
if n.IsInt64() {
return FormatLogfmtInt64(n.Int64())
}
// Ok, huge number needs huge effort
groups := make([]string, 0, 8) // random initial size to cover most cases
for n.Cmp(big1000) >= 0 {
_, mod := n.DivMod(n, big1000, nil)
groups = append(groups, fmt.Sprintf("%03d", mod))
}
groups = append(groups, n.String())
last := len(groups) - 1
for i := 0; i < len(groups)/2; i++ {
groups[i], groups[last-i] = groups[last-i], groups[i]
var (
text = n.String()
buf = make([]byte, len(text)+len(text)/3)
comma = 0
i = len(buf) - 1
)
for j := len(text) - 1; j >= 0; j, i = j-1, i-1 {
c := text[j]
switch {
case c == '-':
buf[i] = c
case comma == 3:
buf[i] = ','
i--
comma = 0
fallthrough
default:
buf[i] = c
comma++
}
}
return strings.Join(groups, ",")
return string(buf[i+1:])
}
// escapeString checks if the provided string needs escaping/quoting, and

@ -2,6 +2,7 @@ package log
import (
"math"
"math/big"
"math/rand"
"testing"
)
@ -58,6 +59,25 @@ func TestPrettyUint64(t *testing.T) {
}
}
func TestPrettyBigInt(t *testing.T) {
tests := []struct {
int string
s string
}{
{"111222333444555678999", "111,222,333,444,555,678,999"},
{"-111222333444555678999", "-111,222,333,444,555,678,999"},
{"11122233344455567899900", "11,122,233,344,455,567,899,900"},
{"-11122233344455567899900", "-11,122,233,344,455,567,899,900"},
}
for _, tt := range tests {
v, _ := new(big.Int).SetString(tt.int, 10)
if have := formatLogfmtBigInt(v); have != tt.s {
t.Errorf("invalid output %s, want %s", have, tt.s)
}
}
}
var sink string
func BenchmarkPrettyInt64Logfmt(b *testing.B) {