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

View File

@ -359,11 +359,16 @@ func formatLogfmtValue(value interface{}, term bool) string {
return strconv.FormatFloat(float64(v), floatFormat, 3, 64) return strconv.FormatFloat(float64(v), floatFormat, 3, 64)
case float64: case float64:
return strconv.FormatFloat(v, floatFormat, 3, 64) return strconv.FormatFloat(v, floatFormat, 3, 64)
case int8, uint8: case int8:
return fmt.Sprintf("%d", value) return strconv.FormatInt(int64(v), 10)
case int: case uint8:
return FormatLogfmtInt64(int64(v)) return strconv.FormatInt(int64(v), 10)
case int16: 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)) return FormatLogfmtInt64(int64(v))
case int32: case int32:
return FormatLogfmtInt64(int64(v)) return FormatLogfmtInt64(int64(v))
@ -371,8 +376,6 @@ func formatLogfmtValue(value interface{}, term bool) string {
return FormatLogfmtInt64(v) return FormatLogfmtInt64(v)
case uint: case uint:
return FormatLogfmtUint64(uint64(v)) return FormatLogfmtUint64(uint64(v))
case uint16:
return FormatLogfmtUint64(uint64(v))
case uint32: case uint32:
return FormatLogfmtUint64(uint64(v)) return FormatLogfmtUint64(uint64(v))
case uint64: 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 { func FormatLogfmtInt64(n int64) string {
if n < 0 { if n < 0 {
return formatLogfmtUint64(uint64(-n), true) return formatLogfmtUint64(uint64(-n), true)
@ -392,7 +395,7 @@ func FormatLogfmtInt64(n int64) string {
return formatLogfmtUint64(uint64(n), false) 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 { func FormatLogfmtUint64(n uint64) string {
return formatLogfmtUint64(n, false) return formatLogfmtUint64(n, false)
} }
@ -431,31 +434,38 @@ func formatLogfmtUint64(n uint64, neg bool) string {
return string(out[i+1:]) return string(out[i+1:])
} }
var big1000 = big.NewInt(1000) // formatLogfmtBigInt formats n with thousand separators.
// formatLogfmtBigInt formats a potentially gigantic number in a friendlier split
// format.
func formatLogfmtBigInt(n *big.Int) string { func formatLogfmtBigInt(n *big.Int) string {
// Most number don't need fancy handling, just downcast
if n.IsUint64() { if n.IsUint64() {
return FormatLogfmtUint64(n.Uint64()) return FormatLogfmtUint64(n.Uint64())
} }
if n.IsInt64() { if n.IsInt64() {
return FormatLogfmtInt64(n.Int64()) 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 var (
for i := 0; i < len(groups)/2; i++ { text = n.String()
groups[i], groups[last-i] = groups[last-i], groups[i] 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 // escapeString checks if the provided string needs escaping/quoting, and

View File

@ -2,6 +2,7 @@ package log
import ( import (
"math" "math"
"math/big"
"math/rand" "math/rand"
"testing" "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 var sink string
func BenchmarkPrettyInt64Logfmt(b *testing.B) { func BenchmarkPrettyInt64Logfmt(b *testing.B) {