update dependencies

This commit is contained in:
tsmethurst
2022-01-16 18:52:30 +01:00
parent 18047de666
commit 6f5ccf4355
55 changed files with 5674 additions and 1905 deletions

View File

@@ -4,17 +4,9 @@ import (
"fmt"
"sync"
"codeberg.org/gruf/go-bytes"
"codeberg.org/gruf/go-logger"
"codeberg.org/gruf/go-format"
)
// global logfmt data formatter.
var logfmt = logger.TextFormat{
Strict: false,
Verbose: true,
MaxDepth: 5,
}
// KV is a structure for setting key-value pairs in ErrorData.
type KV struct {
Key string
@@ -31,7 +23,7 @@ type ErrorData interface {
Append(...KV)
// Implement byte slice representation formatter.
logger.Formattable
format.Formattable
// Implement string representation formatter.
fmt.Stringer
@@ -89,13 +81,22 @@ func (d *errorData) Append(kvs ...KV) {
}
func (d *errorData) AppendFormat(b []byte) []byte {
buf := bytes.Buffer{B: b}
buf := format.Buffer{B: b}
d.mu.Lock()
buf.B = append(buf.B, '{')
// Append data as kv pairs
for i := range d.data {
logfmt.AppendKey(&buf, d.data[i].Key)
logfmt.AppendValue(&buf, d.data[i].Value)
key := d.data[i].Key
val := d.data[i].Value
format.Appendf(&buf, "{:k}={:v} ", key, val)
}
// Drop trailing space
if len(d.data) > 0 {
buf.Truncate(1)
}
buf.B = append(buf.B, '}')
d.mu.Unlock()
return buf.B

16
vendor/codeberg.org/gruf/go-format/README.md generated vendored Normal file
View File

@@ -0,0 +1,16 @@
# go-format
String formatting package using Rust-style formatting directives.
Output is generally more visually-friendly than `"fmt"`, while performance is neck-and-neck.
README is WIP.
## todos
- improved verbose printing of number types
- more test cases
- improved verbose printing of string ptr types

81
vendor/codeberg.org/gruf/go-format/buffer.go generated vendored Normal file
View File

@@ -0,0 +1,81 @@
package format
import (
"io"
"unicode/utf8"
"unsafe"
)
// ensure we conform to io.Writer.
var _ io.Writer = (*Buffer)(nil)
// Buffer is a simple wrapper around a byte slice.
type Buffer struct {
B []byte
}
// Write will append given byte slice to buffer, fulfilling io.Writer.
func (buf *Buffer) Write(b []byte) (int, error) {
buf.B = append(buf.B, b...)
return len(b), nil
}
// AppendByte appends given byte to the buffer.
func (buf *Buffer) AppendByte(b byte) {
buf.B = append(buf.B, b)
}
// AppendRune appends given rune to the buffer.
func (buf *Buffer) AppendRune(r rune) {
if r < utf8.RuneSelf {
buf.B = append(buf.B, byte(r))
return
}
l := buf.Len()
for i := 0; i < utf8.UTFMax; i++ {
buf.B = append(buf.B, 0)
}
n := utf8.EncodeRune(buf.B[l:buf.Len()], r)
buf.B = buf.B[:l+n]
}
// Append will append given byte slice to the buffer.
func (buf *Buffer) Append(b []byte) {
buf.B = append(buf.B, b...)
}
// AppendString appends given string to the buffer.
func (buf *Buffer) AppendString(s string) {
buf.B = append(buf.B, s...)
}
// Len returns the length of the buffer's underlying byte slice.
func (buf *Buffer) Len() int {
return len(buf.B)
}
// Cap returns the capacity of the buffer's underlying byte slice.
func (buf *Buffer) Cap() int {
return cap(buf.B)
}
// Truncate will reduce the length of the buffer by 'n'.
func (buf *Buffer) Truncate(n int) {
if n > len(buf.B) {
n = len(buf.B)
}
buf.B = buf.B[:buf.Len()-n]
}
// Reset will reset the buffer length to 0 (retains capacity).
func (buf *Buffer) Reset() {
buf.B = buf.B[:0]
}
// String returns the underlying byte slice as a string. Please note
// this value is tied directly to the underlying byte slice, if you
// write to the buffer then returned string values will also change.
func (buf *Buffer) String() string {
return *(*string)(unsafe.Pointer(&buf.B))
}

565
vendor/codeberg.org/gruf/go-format/format.go generated vendored Normal file
View File

@@ -0,0 +1,565 @@
package format
import (
"reflect"
"strconv"
"unsafe"
)
// Formattable defines a type capable of being formatted and appended to a byte buffer.
type Formattable interface {
AppendFormat([]byte) []byte
}
// format is the object passed among the append___ formatting functions.
type format struct {
flags uint8 // 'isKey' and 'verbose' flags
drefs uint8 // current value deref count
curd uint8 // current depth
maxd uint8 // maximum depth
buf *Buffer // out buffer
}
const (
// flag bit constants.
isKeyBit = uint8(1) << 0
isValBit = uint8(1) << 1
vboseBit = uint8(1) << 2
panicBit = uint8(1) << 3
)
// AtMaxDepth returns whether format is currently at max depth.
func (f format) AtMaxDepth() bool {
return f.curd > f.maxd
}
// Derefs returns no. times current value has been dereferenced.
func (f format) Derefs() uint8 {
return f.drefs
}
// IsKey returns whether the isKey flag is set.
func (f format) IsKey() bool {
return (f.flags & isKeyBit) != 0
}
// IsValue returns whether the isVal flag is set.
func (f format) IsValue() bool {
return (f.flags & isValBit) != 0
}
// Verbose returns whether the verbose flag is set.
func (f format) Verbose() bool {
return (f.flags & vboseBit) != 0
}
// Panic returns whether the panic flag is set.
func (f format) Panic() bool {
return (f.flags & panicBit) != 0
}
// SetIsKey returns format instance with the isKey bit set to value.
func (f format) SetIsKey() format {
return format{
flags: f.flags & ^isValBit | isKeyBit,
curd: f.curd,
maxd: f.maxd,
buf: f.buf,
}
}
// SetIsValue returns format instance with the isVal bit set to value.
func (f format) SetIsValue() format {
return format{
flags: f.flags & ^isKeyBit | isValBit,
curd: f.curd,
maxd: f.maxd,
buf: f.buf,
}
}
// SetPanic returns format instance with the panic bit set to value.
func (f format) SetPanic() format {
return format{
flags: f.flags | panicBit /* handle panic as value */ | isValBit & ^isKeyBit,
curd: f.curd,
maxd: f.maxd,
buf: f.buf,
}
}
// IncrDepth returns format instance with depth incremented.
func (f format) IncrDepth() format {
return format{
flags: f.flags,
curd: f.curd + 1,
maxd: f.maxd,
buf: f.buf,
}
}
// IncrDerefs returns format instance with dereference count incremented.
func (f format) IncrDerefs() format {
return format{
flags: f.flags,
drefs: f.drefs + 1,
curd: f.curd,
maxd: f.maxd,
buf: f.buf,
}
}
// appendType appends a type using supplied type str.
func appendType(fmt format, t string) {
for i := uint8(0); i < fmt.Derefs(); i++ {
fmt.buf.AppendByte('*')
}
fmt.buf.AppendString(t)
}
// appendNilType Appends nil to buf, type included if verbose.
func appendNilType(fmt format, t string) {
if fmt.Verbose() {
fmt.buf.AppendByte('(')
appendType(fmt, t)
fmt.buf.AppendString(`)(nil)`)
} else {
fmt.buf.AppendString(`nil`)
}
}
// appendByte Appends a single byte to buf.
func appendByte(fmt format, b byte) {
if fmt.IsValue() || fmt.Verbose() {
fmt.buf.AppendString(`'` + string(b) + `'`)
} else {
fmt.buf.AppendByte(b)
}
}
// appendBytes Appends a quoted byte slice to buf.
func appendBytes(fmt format, b []byte) {
if b == nil {
// Bytes CAN be nil formatted
appendNilType(fmt, `[]byte`)
} else {
// Append bytes as slice
fmt.buf.AppendByte('[')
for _, b := range b {
fmt.buf.AppendByte(b)
fmt.buf.AppendByte(',')
}
if len(b) > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.AppendByte(']')
}
}
// appendString Appends an escaped, double-quoted string to buf.
func appendString(fmt format, s string) {
switch {
// Key in a key-value pair
case fmt.IsKey():
if !strconv.CanBackquote(s) {
// Requires quoting AND escaping
fmt.buf.B = strconv.AppendQuote(fmt.buf.B, s)
} else if containsSpaceOrTab(s) {
// Contains space, needs quotes
fmt.buf.AppendString(`"` + s + `"`)
} else {
// All else write as-is
fmt.buf.AppendString(s)
}
// Value in a key-value pair (always escape+quote)
case fmt.IsValue():
fmt.buf.B = strconv.AppendQuote(fmt.buf.B, s)
// Verbose but neither key nor value (always quote)
case fmt.Verbose():
fmt.buf.AppendString(`"` + s + `"`)
// All else
default:
fmt.buf.AppendString(s)
}
}
// appendBool Appends a formatted bool to buf.
func appendBool(fmt format, b bool) {
fmt.buf.B = strconv.AppendBool(fmt.buf.B, b)
}
// appendInt Appends a formatted int to buf.
func appendInt(fmt format, i int64) {
fmt.buf.B = strconv.AppendInt(fmt.buf.B, i, 10)
}
// appendUint Appends a formatted uint to buf.
func appendUint(fmt format, u uint64) {
fmt.buf.B = strconv.AppendUint(fmt.buf.B, u, 10)
}
// appendFloat Appends a formatted float to buf.
func appendFloat(fmt format, f float64) {
fmt.buf.B = strconv.AppendFloat(fmt.buf.B, f, 'G', -1, 64)
}
// appendComplex Appends a formatted complex128 to buf.
func appendComplex(fmt format, c complex128) {
appendFloat(fmt, real(c))
fmt.buf.AppendByte('+')
appendFloat(fmt, imag(c))
fmt.buf.AppendByte('i')
}
// isNil will safely check if 'v' is nil without dealing with weird Go interface nil bullshit.
func isNil(i interface{}) bool {
e := *(*struct {
_ unsafe.Pointer // type
v unsafe.Pointer // value
})(unsafe.Pointer(&i))
return (e.v == nil)
}
// appendIfaceOrReflectValue will attempt to append as interface, falling back to reflection.
func appendIfaceOrRValue(fmt format, i interface{}) {
if !appendIface(fmt, i) {
appendRValue(fmt, reflect.ValueOf(i))
}
}
// appendValueNext checks for interface methods before performing appendRValue, checking + incr depth.
func appendRValueOrIfaceNext(fmt format, v reflect.Value) {
// Check we haven't hit max
if fmt.AtMaxDepth() {
fmt.buf.AppendString("...")
return
}
// Incr the depth
fmt = fmt.IncrDepth()
// Make actual call
if !v.CanInterface() || !appendIface(fmt, v.Interface()) {
appendRValue(fmt, v)
}
}
// appendIface parses and Appends a formatted interface value to buf.
func appendIface(fmt format, i interface{}) (ok bool) {
ok = true // default
catchPanic := func() {
if r := recover(); r != nil {
// DON'T recurse catchPanic()
if fmt.Panic() {
panic(r)
}
// Attempt to decode panic into buf
fmt.buf.AppendString(`!{PANIC=`)
appendIfaceOrRValue(fmt.SetPanic(), r)
fmt.buf.AppendByte('}')
// Ensure return
ok = true
}
}
switch i := i.(type) {
// Nil type
case nil:
fmt.buf.AppendString(`nil`)
// Reflect types
case reflect.Type:
if isNil(i) /* safer nil check */ {
appendNilType(fmt, `reflect.Type`)
} else {
appendType(fmt, `reflect.Type`)
fmt.buf.AppendString(`(` + i.String() + `)`)
}
case reflect.Value:
appendType(fmt, `reflect.Value`)
fmt.buf.AppendByte('(')
fmt.flags |= vboseBit
appendRValue(fmt, i)
fmt.buf.AppendByte(')')
// Bytes and string types
case byte:
appendByte(fmt, i)
case []byte:
appendBytes(fmt, i)
case string:
appendString(fmt, i)
// Int types
case int:
appendInt(fmt, int64(i))
case int8:
appendInt(fmt, int64(i))
case int16:
appendInt(fmt, int64(i))
case int32:
appendInt(fmt, int64(i))
case int64:
appendInt(fmt, i)
// Uint types
case uint:
appendUint(fmt, uint64(i))
// case uint8 :: this is 'byte'
case uint16:
appendUint(fmt, uint64(i))
case uint32:
appendUint(fmt, uint64(i))
case uint64:
appendUint(fmt, i)
// Float types
case float32:
appendFloat(fmt, float64(i))
case float64:
appendFloat(fmt, i)
// Bool type
case bool:
appendBool(fmt, i)
// Complex types
case complex64:
appendComplex(fmt, complex128(i))
case complex128:
appendComplex(fmt, i)
// Method types
case error:
switch {
case fmt.Verbose():
ok = false
case isNil(i) /* use safer nil check */ :
appendNilType(fmt, reflect.TypeOf(i).String())
default:
defer catchPanic()
appendString(fmt, i.Error())
}
case Formattable:
switch {
case fmt.Verbose():
ok = false
case isNil(i) /* use safer nil check */ :
appendNilType(fmt, reflect.TypeOf(i).String())
default:
defer catchPanic()
fmt.buf.B = i.AppendFormat(fmt.buf.B)
}
case interface{ String() string }:
switch {
case fmt.Verbose():
ok = false
case isNil(i) /* use safer nil check */ :
appendNilType(fmt, reflect.TypeOf(i).String())
default:
defer catchPanic()
appendString(fmt, i.String())
}
// No quick handler
default:
ok = false
}
return ok
}
// appendReflectValue will safely append a reflected value.
func appendRValue(fmt format, v reflect.Value) {
switch v.Kind() {
// String and byte types
case reflect.Uint8:
appendByte(fmt, byte(v.Uint()))
case reflect.String:
appendString(fmt, v.String())
// Float tpyes
case reflect.Float32, reflect.Float64:
appendFloat(fmt, v.Float())
// Int types
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
appendInt(fmt, v.Int())
// Uint types
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
appendUint(fmt, v.Uint())
// Complex types
case reflect.Complex64, reflect.Complex128:
appendComplex(fmt, v.Complex())
// Bool type
case reflect.Bool:
appendBool(fmt, v.Bool())
// Slice and array types
case reflect.Array:
appendArrayType(fmt, v)
case reflect.Slice:
if v.IsNil() {
appendNilType(fmt, v.Type().String())
} else {
appendArrayType(fmt, v)
}
// Map types
case reflect.Map:
if v.IsNil() {
appendNilType(fmt, v.Type().String())
} else {
appendMapType(fmt, v)
}
// Struct types
case reflect.Struct:
appendStructType(fmt, v)
// Deref'able ptr types
case reflect.Ptr, reflect.Interface:
if v.IsNil() {
appendNilType(fmt, v.Type().String())
} else {
appendRValue(fmt.IncrDerefs(), v.Elem())
}
// 'raw' pointer types
case reflect.UnsafePointer:
appendType(fmt, `unsafe.Pointer`)
fmt.buf.AppendByte('(')
if u := v.Pointer(); u != 0 {
fmt.buf.AppendString("0x")
fmt.buf.B = strconv.AppendUint(fmt.buf.B, uint64(u), 16)
} else {
fmt.buf.AppendString(`nil`)
}
fmt.buf.AppendByte(')')
case reflect.Uintptr:
appendType(fmt, `uintptr`)
fmt.buf.AppendByte('(')
if u := v.Uint(); u != 0 {
fmt.buf.AppendString("0x")
fmt.buf.B = strconv.AppendUint(fmt.buf.B, u, 16)
} else {
fmt.buf.AppendString(`nil`)
}
fmt.buf.AppendByte(')')
// Generic types we don't *exactly* handle
case reflect.Func, reflect.Chan:
if v.IsNil() {
appendNilType(fmt, v.Type().String())
} else {
fmt.buf.AppendString(v.String())
}
// Unhandled kind
default:
fmt.buf.AppendString(v.String())
}
}
// appendArrayType Appends an array of unknown type (parsed by reflection) to buf, unlike appendSliceType does NOT catch nil slice.
func appendArrayType(fmt format, v reflect.Value) {
// get no. elements
n := v.Len()
fmt.buf.AppendByte('[')
// Append values
for i := 0; i < n; i++ {
appendRValueOrIfaceNext(fmt.SetIsValue(), v.Index(i))
fmt.buf.AppendByte(',')
}
// Drop last comma
if n > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.AppendByte(']')
}
// appendMapType Appends a map of unknown types (parsed by reflection) to buf.
func appendMapType(fmt format, v reflect.Value) {
// Prepend type if verbose
if fmt.Verbose() {
appendType(fmt, v.Type().String())
}
// Get a map iterator
r := v.MapRange()
n := v.Len()
fmt.buf.AppendByte('{')
// Iterate pairs
for r.Next() {
appendRValueOrIfaceNext(fmt.SetIsKey(), r.Key())
fmt.buf.AppendByte('=')
appendRValueOrIfaceNext(fmt.SetIsValue(), r.Value())
fmt.buf.AppendByte(' ')
}
// Drop last space
if n > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.AppendByte('}')
}
// appendStructType Appends a struct (as a set of key-value fields) to buf.
func appendStructType(fmt format, v reflect.Value) {
// Get value type & no. fields
t := v.Type()
n := v.NumField()
// Prepend type if verbose
if fmt.Verbose() {
appendType(fmt, v.Type().String())
}
fmt.buf.AppendByte('{')
// Iterate fields
for i := 0; i < n; i++ {
vfield := v.Field(i)
tfield := t.Field(i)
// Append field name
fmt.buf.AppendString(tfield.Name)
fmt.buf.AppendByte('=')
appendRValueOrIfaceNext(fmt.SetIsValue(), vfield)
// Iter written count
fmt.buf.AppendByte(' ')
}
// Drop last space
if n > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.AppendByte('}')
}
// containsSpaceOrTab checks if "s" contains space or tabs.
func containsSpaceOrTab(s string) bool {
for _, r := range s {
if r == ' ' || r == '\t' {
return true
}
}
return false
}

352
vendor/codeberg.org/gruf/go-format/formatter.go generated vendored Normal file
View File

@@ -0,0 +1,352 @@
package format
import (
"strings"
)
// Formatter allows configuring value and string formatting.
type Formatter struct {
// MaxDepth specifies the max depth of fields the formatter will iterate.
// Once max depth is reached, value will simply be formatted as "...".
// e.g.
//
// MaxDepth=1
// type A struct{
// Nested B
// }
// type B struct{
// Nested C
// }
// type C struct{
// Field string
// }
//
// Append(&buf, A{}) => {Nested={Nested={Field=...}}}
MaxDepth uint8
}
// Append will append formatted form of supplied values into 'buf'.
func (f Formatter) Append(buf *Buffer, v ...interface{}) {
for _, v := range v {
appendIfaceOrRValue(format{maxd: f.MaxDepth, buf: buf}, v)
buf.AppendByte(' ')
}
if len(v) > 0 {
buf.Truncate(1)
}
}
// Appendf will append the formatted string with supplied values into 'buf'.
// Supported format directives:
// - '{}' => format supplied arg, in place
// - '{0}' => format arg at index 0 of supplied, in place
// - '{:?}' => format supplied arg verbosely, in place
// - '{:k}' => format supplied arg as key, in place
// - '{:v}' => format supplied arg as value, in place
//
// To escape either of '{}' simply append an additional brace e.g.
// - '{{' => '{'
// - '}}' => '}'
// - '{{}}' => '{}'
// - '{{:?}}' => '{:?}'
//
// More formatting directives might be included in the future.
func (f Formatter) Appendf(buf *Buffer, s string, a ...interface{}) {
const (
// ground state
modeNone = uint8(0)
// prev reached '{'
modeOpen = uint8(1)
// prev reached '}'
modeClose = uint8(2)
// parsing directive index
modeIdx = uint8(3)
// parsing directive operands
modeOp = uint8(4)
)
var (
// mode is current parsing mode
mode uint8
// arg is the current arg index
arg int
// carg is current directive-set arg index
carg int
// last is the trailing cursor to see slice windows
last int
// idx is the current index in 's'
idx int
// fmt is the base argument formatter
fmt = format{
maxd: f.MaxDepth,
buf: buf,
}
// NOTE: these functions are defined here as function
// locals as it turned out to be better for performance
// doing it this way, than encapsulating their logic in
// some kind of parsing structure. Maybe if the parser
// was pooled along with the buffers it might work out
// better, but then it makes more internal functions i.e.
// .Append() .Appendf() less accessible outside package.
//
// Currently, passing '-gcflags "-l=4"' causes a not
// insignificant decrease in ns/op, which is likely due
// to more aggressive function inlining, which this
// function can obviously stand to benefit from :)
// Str returns current string window slice, and updates
// the trailing cursor 'last' to current 'idx'
Str = func() string {
str := s[last:idx]
last = idx
return str
}
// MoveUp moves the trailing cursor 'last' just past 'idx'
MoveUp = func() {
last = idx + 1
}
// MoveUpTo moves the trailing cursor 'last' either up to
// closest '}', or current 'idx', whichever is furthest
MoveUpTo = func() {
if i := strings.IndexByte(s[idx:], '}'); i >= 0 {
idx += i
}
MoveUp()
}
// ParseIndex parses an integer from the current string
// window, updating 'last' to 'idx'. The string window
// is ASSUMED to contain only valid ASCII numbers. This
// only returns false if number exceeds platform int size
ParseIndex = func() bool {
// Get current window
str := Str()
if len(str) < 1 {
return true
}
// Index HAS to fit within platform int
if !can32bitInt(str) && !can64bitInt(str) {
return false
}
// Build integer from string
carg = 0
for _, c := range []byte(str) {
carg = carg*10 + int(c-'0')
}
return true
}
// ParseOp parses operands from the current string
// window, updating 'last' to 'idx'. The string window
// is ASSUMED to contain only valid operand ASCII. This
// returns success on parsing of operand logic
ParseOp = func() bool {
// Get current window
str := Str()
if len(str) < 1 {
return true
}
// (for now) only
// accept length = 1
if len(str) > 1 {
return false
}
switch str[0] {
case 'k':
fmt.flags |= isKeyBit
case 'v':
fmt.flags |= isValBit
case '?':
fmt.flags |= vboseBit
}
return true
}
// AppendArg will take either the directive-set, or
// iterated arg index, check within bounds of 'a' and
// append the that argument formatted to the buffer.
// On failure, it will append an error string
AppendArg = func() {
// Look for idx
if carg < 0 {
carg = arg
}
// Incr idx
arg++
if carg < len(a) {
// Append formatted argument value
appendIfaceOrRValue(fmt, a[carg])
} else {
// No argument found for index
buf.AppendString(`!{MISSING_ARG}`)
}
}
// Reset will reset the mode to ground, the flags
// to empty and parsed 'carg' to empty
Reset = func() {
mode = modeNone
fmt.flags = 0
carg = -1
}
)
for idx = 0; idx < len(s); idx++ {
// Get next char
c := s[idx]
switch mode {
// Ground mode
case modeNone:
switch c {
case '{':
// Enter open mode
buf.AppendString(Str())
mode = modeOpen
MoveUp()
case '}':
// Enter close mode
buf.AppendString(Str())
mode = modeClose
MoveUp()
}
// Encountered open '{'
case modeOpen:
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
// Starting index
mode = modeIdx
MoveUp()
case '{':
// Escaped bracket
buf.AppendByte('{')
mode = modeNone
MoveUp()
case '}':
// Format arg
AppendArg()
Reset()
MoveUp()
case ':':
// Starting operands
mode = modeOp
MoveUp()
default:
// Bad char, missing a close
buf.AppendString(`!{MISSING_CLOSE}`)
mode = modeNone
MoveUpTo()
}
// Encountered close '}'
case modeClose:
switch c {
case '}':
// Escaped close bracket
buf.AppendByte('}')
mode = modeNone
MoveUp()
default:
// Missing an open bracket
buf.AppendString(`!{MISSING_OPEN}`)
mode = modeNone
MoveUp()
}
// Preparing index
case modeIdx:
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
case ':':
if !ParseIndex() {
// Unable to parse an integer
buf.AppendString(`!{BAD_INDEX}`)
mode = modeNone
MoveUpTo()
} else {
// Starting operands
mode = modeOp
MoveUp()
}
case '}':
if !ParseIndex() {
// Unable to parse an integer
buf.AppendString(`!{BAD_INDEX}`)
} else {
// Format arg
AppendArg()
}
Reset()
MoveUp()
default:
// Not a valid index character
buf.AppendString(`!{BAD_INDEX}`)
mode = modeNone
MoveUpTo()
}
// Preparing operands
case modeOp:
switch c {
case 'k', 'v', '?':
// TODO: set flags as received
case '}':
if !ParseOp() {
// Unable to parse operands
buf.AppendString(`!{BAD_OPERAND}`)
} else {
// Format arg
AppendArg()
}
Reset()
MoveUp()
default:
// Not a valid operand char
buf.AppendString(`!{BAD_OPERAND}`)
Reset()
MoveUpTo()
}
}
}
// Append any remaining
buf.AppendString(s[last:])
}
// formatter is the default formatter instance.
var formatter = Formatter{
MaxDepth: 10,
}
// Append will append formatted form of supplied values into 'buf' using default formatter.
// See Formatter.Append() for more documentation.
func Append(buf *Buffer, v ...interface{}) {
formatter.Append(buf, v...)
}
// Appendf will append the formatted string with supplied values into 'buf' using default formatter.
// See Formatter.Appendf() for more documentation.
func Appendf(buf *Buffer, s string, a ...interface{}) {
formatter.Appendf(buf, s, a...)
}

88
vendor/codeberg.org/gruf/go-format/print.go generated vendored Normal file
View File

@@ -0,0 +1,88 @@
package format
import (
"io"
"os"
"sync"
)
// pool is the global printer buffer pool.
var pool = sync.Pool{
New: func() interface{} {
return &Buffer{}
},
}
// getBuf fetches a buffer from pool.
func getBuf() *Buffer {
return pool.Get().(*Buffer)
}
// putBuf places a Buffer back in pool.
func putBuf(buf *Buffer) {
if buf.Cap() > 64<<10 {
return // drop large
}
buf.Reset()
pool.Put(buf)
}
// Sprint will format supplied values, returning this string.
func Sprint(v ...interface{}) string {
buf := Buffer{}
Append(&buf, v...)
return buf.String()
}
// Sprintf will format supplied format string and args, returning this string.
// See Formatter.Appendf() for more documentation.
func Sprintf(s string, a ...interface{}) string {
buf := Buffer{}
Appendf(&buf, s, a...)
return buf.String()
}
// Print will format supplied values, print this to os.Stdout.
func Print(v ...interface{}) {
Fprint(os.Stdout, v...) //nolint
}
// Printf will format supplied format string and args, printing this to os.Stdout.
// See Formatter.Appendf() for more documentation.
func Printf(s string, a ...interface{}) {
Fprintf(os.Stdout, s, a...) //nolint
}
// Println will format supplied values, append a trailing newline and print this to os.Stdout.
func Println(v ...interface{}) {
Fprintln(os.Stdout, v...) //nolint
}
// Fprint will format supplied values, writing this to an io.Writer.
func Fprint(w io.Writer, v ...interface{}) (int, error) {
buf := getBuf()
Append(buf, v...)
n, err := w.Write(buf.B)
putBuf(buf)
return n, err
}
// Fprintf will format supplied format string and args, writing this to an io.Writer.
// See Formatter.Appendf() for more documentation.
func Fprintf(w io.Writer, s string, a ...interface{}) (int, error) {
buf := getBuf()
Appendf(buf, s, a...)
n, err := w.Write(buf.B)
putBuf(buf)
return n, err
}
// Println will format supplied values, append a trailing newline and writer this to an io.Writer.
func Fprintln(w io.Writer, v ...interface{}) (int, error) {
buf := getBuf()
Append(buf, v...)
buf.AppendByte('\n')
n, err := w.Write(buf.B)
putBuf(buf)
return n, err
}

13
vendor/codeberg.org/gruf/go-format/util.go generated vendored Normal file
View File

@@ -0,0 +1,13 @@
package format
import "strconv"
// can32bitInt returns whether it's possible for 's' to contain an int on 32bit platforms.
func can32bitInt(s string) bool {
return strconv.IntSize == 32 && (0 < len(s) && len(s) < 10)
}
// can64bitInt returns whether it's possible for 's' to contain an int on 64bit platforms.
func can64bitInt(s string) bool {
return strconv.IntSize == 64 && (0 < len(s) && len(s) < 19)
}

View File

@@ -1,13 +0,0 @@
Fast levelled logging package with customizable formatting.
Supports logging in 2 modes:
- no locks, fastest possible logging, no guarantees for io.Writer thread safety
- mutex locks during writes, still far faster than standard library logger
Running without locks isn't likely to cause you any issues*, but if it does, you can wrap your `io.Writer` using `AddSafety()` when instantiating your new Logger. Even when running the benchmarks, this library has no printing issues without locks, so in most cases you'll be fine, but the safety is there if you need it.
*most logging libraries advertising high speeds are likely not performing mutex locks, which is why with this library you have the option to opt-in/out of them.
Note there are 2 uses of the unsafe package:
- safer interface nil value checks, uses similar logic to reflect package to check if the value in the internal fat pointer is nil
- casting a byte slice to string to allow sharing of similar byte and string methods, performs same logic as `strings.Builder{}.String()`

View File

@@ -1,21 +0,0 @@
package logger
import (
"sync"
"time"
"codeberg.org/gruf/go-nowish"
)
var (
clock = nowish.Clock{}
clockOnce = sync.Once{}
)
// startClock starts the global nowish clock.
func startClock() {
clockOnce.Do(func() {
clock.Start(time.Millisecond * 100)
clock.SetFormat("2006-01-02 15:04:05")
})
}

View File

@@ -1,107 +0,0 @@
package logger
import (
"os"
"sync"
)
var (
instance *Logger
instanceOnce = sync.Once{}
)
// Default returns the default Logger instance.
func Default() *Logger {
instanceOnce.Do(func() { instance = New(os.Stdout) })
return instance
}
// Debug prints the provided arguments with the debug prefix to the global Logger instance.
func Debug(a ...interface{}) {
Default().Debug(a...)
}
// Debugf prints the provided format string and arguments with the debug prefix to the global Logger instance.
func Debugf(s string, a ...interface{}) {
Default().Debugf(s, a...)
}
// Info prints the provided arguments with the info prefix to the global Logger instance.
func Info(a ...interface{}) {
Default().Info(a...)
}
// Infof prints the provided format string and arguments with the info prefix to the global Logger instance.
func Infof(s string, a ...interface{}) {
Default().Infof(s, a...)
}
// Warn prints the provided arguments with the warn prefix to the global Logger instance.
func Warn(a ...interface{}) {
Default().Warn(a...)
}
// Warnf prints the provided format string and arguments with the warn prefix to the global Logger instance.
func Warnf(s string, a ...interface{}) {
Default().Warnf(s, a...)
}
// Error prints the provided arguments with the error prefix to the global Logger instance.
func Error(a ...interface{}) {
Default().Error(a...)
}
// Errorf prints the provided format string and arguments with the error prefix to the global Logger instance.
func Errorf(s string, a ...interface{}) {
Default().Errorf(s, a...)
}
// Fatal prints the provided arguments with the fatal prefix to the global Logger instance before exiting the program with os.Exit(1).
func Fatal(a ...interface{}) {
Default().Fatal(a...)
}
// Fatalf prints the provided format string and arguments with the fatal prefix to the global Logger instance before exiting the program with os.Exit(1).
func Fatalf(s string, a ...interface{}) {
Default().Fatalf(s, a...)
}
// Log prints the provided arguments with the supplied log level to the global Logger instance.
func Log(lvl LEVEL, a ...interface{}) {
Default().Log(lvl, a...)
}
// Logf prints the provided format string and arguments with the supplied log level to the global Logger instance.
func Logf(lvl LEVEL, s string, a ...interface{}) {
Default().Logf(lvl, s, a...)
}
// LogFields prints the provided fields formatted as key-value pairs at the supplied log level to the global Logger instance.
func LogFields(lvl LEVEL, fields map[string]interface{}) {
Default().LogFields(lvl, fields)
}
// LogValues prints the provided values formatted as-so at the supplied log level to the global Logger instance.
func LogValues(lvl LEVEL, a ...interface{}) {
Default().LogValues(lvl, a...)
}
// Print simply prints provided arguments to the global Logger instance.
func Print(a ...interface{}) {
Default().Print(a...)
}
// Printf simply prints provided the provided format string and arguments to the global Logger instance.
func Printf(s string, a ...interface{}) {
Default().Printf(s, a...)
}
// PrintFields prints the provided fields formatted as key-value pairs to the global Logger instance.
func PrintFields(fields map[string]interface{}) {
Default().PrintFields(fields)
}
// PrintValues prints the provided values formatted as-so to the global Logger instance.
func PrintValues(a ...interface{}) {
Default().PrintValues(a...)
}

View File

@@ -1,385 +0,0 @@
package logger
import (
"context"
"fmt"
"time"
"codeberg.org/gruf/go-bytes"
)
// Entry defines an entry in the log, it is NOT safe for concurrent use
type Entry struct {
ctx context.Context
lvl LEVEL
buf *bytes.Buffer
log *Logger
}
// Context returns the current set Entry context.Context
func (e *Entry) Context() context.Context {
return e.ctx
}
// WithContext updates Entry context value to the supplied
func (e *Entry) WithContext(ctx context.Context) *Entry {
e.ctx = ctx
return e
}
// Level appends the supplied level to the log entry, and sets the entry level.
// Please note this CAN be called and append log levels multiple times
func (e *Entry) Level(lvl LEVEL) *Entry {
e.log.Format.AppendLevel(e.buf, lvl)
e.buf.WriteByte(' ')
e.lvl = lvl
return e
}
// Timestamp appends the current timestamp to the log entry. Please note this
// CAN be called and append the timestamp multiple times
func (e *Entry) Timestamp() *Entry {
e.log.Format.AppendTimestamp(e.buf, clock.NowFormat())
e.buf.WriteByte(' ')
return e
}
// TimestampIf performs Entry.Timestamp() only IF timestamping is enabled for the Logger.
// Please note this CAN be called multiple times
func (e *Entry) TimestampIf() *Entry {
if e.log.Timestamp {
e.Timestamp()
}
return e
}
// Hooks applies currently set Hooks to the Entry. Please note this CAN be
// called and perform the Hooks multiple times
func (e *Entry) Hooks() *Entry {
for _, hook := range e.log.Hooks {
hook.Do(e)
}
return e
}
// Byte appends a byte value to the log entry
func (e *Entry) Byte(value byte) *Entry {
e.log.Format.AppendByte(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// ByteField appends a byte value as key-value pair to the log entry
func (e *Entry) ByteField(key string, value byte) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendByte(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Bytes appends a byte slice value as to the log entry
func (e *Entry) Bytes(value []byte) *Entry {
e.log.Format.AppendBytes(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// BytesField appends a byte slice value as key-value pair to the log entry
func (e *Entry) BytesField(key string, value []byte) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendBytes(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Str appends a string value to the log entry
func (e *Entry) Str(value string) *Entry {
e.log.Format.AppendString(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// StrField appends a string value as key-value pair to the log entry
func (e *Entry) StrField(key string, value string) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendString(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Strs appends a string slice value to the log entry
func (e *Entry) Strs(value []string) *Entry {
e.log.Format.AppendStrings(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// StrsField appends a string slice value as key-value pair to the log entry
func (e *Entry) StrsField(key string, value []string) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendStrings(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Int appends an int value to the log entry
func (e *Entry) Int(value int) *Entry {
e.log.Format.AppendInt(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// IntField appends an int value as key-value pair to the log entry
func (e *Entry) IntField(key string, value int) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendInt(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Ints appends an int slice value to the log entry
func (e *Entry) Ints(value []int) *Entry {
e.log.Format.AppendInts(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// IntsField appends an int slice value as key-value pair to the log entry
func (e *Entry) IntsField(key string, value []int) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendInts(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Uint appends a uint value to the log entry
func (e *Entry) Uint(value uint) *Entry {
e.log.Format.AppendUint(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// UintField appends a uint value as key-value pair to the log entry
func (e *Entry) UintField(key string, value uint) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendUint(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Uints appends a uint slice value to the log entry
func (e *Entry) Uints(value []uint) *Entry {
e.log.Format.AppendUints(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// UintsField appends a uint slice value as key-value pair to the log entry
func (e *Entry) UintsField(key string, value []uint) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendUints(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Float appends a float value to the log entry
func (e *Entry) Float(value float64) *Entry {
e.log.Format.AppendFloat(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// FloatField appends a float value as key-value pair to the log entry
func (e *Entry) FloatField(key string, value float64) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendFloat(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Floats appends a float slice value to the log entry
func (e *Entry) Floats(value []float64) *Entry {
e.log.Format.AppendFloats(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// FloatsField appends a float slice value as key-value pair to the log entry
func (e *Entry) FloatsField(key string, value []float64) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendFloats(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Bool appends a bool value to the log entry
func (e *Entry) Bool(value bool) *Entry {
e.log.Format.AppendBool(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// BoolField appends a bool value as key-value pair to the log entry
func (e *Entry) BoolField(key string, value bool) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendBool(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Bools appends a bool slice value to the log entry
func (e *Entry) Bools(value []bool) *Entry {
e.log.Format.AppendBools(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// BoolsField appends a bool slice value as key-value pair to the log entry
func (e *Entry) BoolsField(key string, value []bool) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendBools(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Time appends a time.Time value to the log entry
func (e *Entry) Time(value time.Time) *Entry {
e.log.Format.AppendTime(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// TimeField appends a time.Time value as key-value pair to the log entry
func (e *Entry) TimeField(key string, value time.Time) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendTime(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Times appends a time.Time slice value to the log entry
func (e *Entry) Times(value []time.Time) *Entry {
e.log.Format.AppendTimes(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// TimesField appends a time.Time slice value as key-value pair to the log entry
func (e *Entry) TimesField(key string, value []time.Time) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendTimes(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// DurationField appends a time.Duration value to the log entry
func (e *Entry) Duration(value time.Duration) *Entry {
e.log.Format.AppendDuration(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// DurationField appends a time.Duration value as key-value pair to the log entry
func (e *Entry) DurationField(key string, value time.Duration) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendDuration(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Durations appends a time.Duration slice value to the log entry
func (e *Entry) Durations(value []time.Duration) *Entry {
e.log.Format.AppendDurations(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// DurationsField appends a time.Duration slice value as key-value pair to the log entry
func (e *Entry) DurationsField(key string, value []time.Duration) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendDurations(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Field appends an interface value as key-value pair to the log entry
func (e *Entry) Field(key string, value interface{}) *Entry {
e.log.Format.AppendKey(e.buf, key)
e.log.Format.AppendValue(e.buf, value)
e.buf.WriteByte(' ')
return e
}
// Fields appends a map of key-value pairs to the log entry
func (e *Entry) Fields(fields map[string]interface{}) *Entry {
for key, value := range fields {
e.Field(key, value)
}
return e
}
// Values appends the given values to the log entry formatted as values, without a key.
func (e *Entry) Values(values ...interface{}) *Entry {
for _, value := range values {
e.log.Format.AppendValue(e.buf, value)
e.buf.WriteByte(' ')
}
return e
}
// Append will append the given args formatted using fmt.Sprint(a...) to the Entry.
func (e *Entry) Append(a ...interface{}) *Entry {
fmt.Fprint(e.buf, a...)
e.buf.WriteByte(' ')
return e
}
// Appendf will append the given format string and args using fmt.Sprintf(s, a...) to the Entry.
func (e *Entry) Appendf(s string, a ...interface{}) *Entry {
fmt.Fprintf(e.buf, s, a...)
e.buf.WriteByte(' ')
return e
}
// Msg appends the fmt.Sprint() formatted final message to the log and calls .Send()
func (e *Entry) Msg(a ...interface{}) {
e.log.Format.AppendMsg(e.buf, a...)
e.Send()
}
// Msgf appends the fmt.Sprintf() formatted final message to the log and calls .Send()
func (e *Entry) Msgf(s string, a ...interface{}) {
e.log.Format.AppendMsgf(e.buf, s, a...)
e.Send()
}
// Send triggers write of the log entry, skipping if the entry's log LEVEL
// is below the currently set Logger level, and releases the Entry back to
// the Logger's Entry pool. So it is NOT safe to continue using this Entry
// object after calling .Send(), .Msg() or .Msgf()
func (e *Entry) Send() {
// If nothing to do, return
if e.lvl < e.log.Level || e.buf.Len() < 1 {
e.reset()
return
}
// Ensure a final new line
if e.buf.B[e.buf.Len()-1] != '\n' {
e.buf.WriteByte('\n')
}
// Write, reset and release
e.log.Output.Write(e.buf.B)
e.reset()
}
func (e *Entry) reset() {
// Reset all
e.ctx = nil
e.buf.Reset()
e.lvl = unset
// Release to pool
e.log.pool.Put(e)
}

View File

@@ -1,87 +0,0 @@
package logger
import (
"time"
"codeberg.org/gruf/go-bytes"
)
// Check our types impl LogFormat
var _ LogFormat = &TextFormat{}
// Formattable defines a type capable of writing a string formatted form
// of itself to a supplied byte buffer, and returning the resulting byte
// buffer. Implementing this will greatly speed up formatting of custom
// types passed to LogFormat (assuming they implement checking for this).
type Formattable interface {
AppendFormat([]byte) []byte
}
// LogFormat defines a method of formatting log entries
type LogFormat interface {
// AppendKey appends given key to the log buffer
AppendKey(buf *bytes.Buffer, key string)
// AppendLevel appends given log level as key-value pair to the log buffer
AppendLevel(buf *bytes.Buffer, lvl LEVEL)
// AppendTimestamp appends given timestamp string as key-value pair to the log buffer
AppendTimestamp(buf *bytes.Buffer, fmtNow string)
// AppendValue appends given interface formatted as value to the log buffer
AppendValue(buf *bytes.Buffer, value interface{})
// AppendByte appends given byte value to the log buffer
AppendByte(buf *bytes.Buffer, value byte)
// AppendBytes appends given byte slice value to the log buffer
AppendBytes(buf *bytes.Buffer, value []byte)
// AppendString appends given string value to the log buffer
AppendString(buf *bytes.Buffer, value string)
// AppendStrings appends given string slice value to the log buffer
AppendStrings(buf *bytes.Buffer, value []string)
// AppendBool appends given bool value to the log buffer
AppendBool(buf *bytes.Buffer, value bool)
// AppendBools appends given bool slice value to the log buffer
AppendBools(buf *bytes.Buffer, value []bool)
// AppendInt appends given int value to the log buffer
AppendInt(buf *bytes.Buffer, value int)
// AppendInts appends given int slice value to the log buffer
AppendInts(buf *bytes.Buffer, value []int)
// AppendUint appends given uint value to the log buffer
AppendUint(buf *bytes.Buffer, value uint)
// AppendUints appends given uint slice value to the log buffer
AppendUints(buf *bytes.Buffer, value []uint)
// AppendFloat appends given float value to the log buffer
AppendFloat(buf *bytes.Buffer, value float64)
// AppendFloats appends given float slice value to the log buffer
AppendFloats(buf *bytes.Buffer, value []float64)
// AppendTime appends given time value to the log buffer
AppendTime(buf *bytes.Buffer, value time.Time)
// AppendTimes appends given time slice value to the log buffer
AppendTimes(buf *bytes.Buffer, value []time.Time)
// AppendDuration appends given duration value to the log buffer
AppendDuration(buf *bytes.Buffer, value time.Duration)
// AppendDurations appends given duration slice value to the log buffer
AppendDurations(buf *bytes.Buffer, value []time.Duration)
// AppendMsg appends given msg as key-value pair to the log buffer using fmt.Sprint(...) formatting
AppendMsg(buf *bytes.Buffer, a ...interface{})
// AppendMsgf appends given msg format string as key-value pair to the log buffer using fmt.Sprintf(...) formatting
AppendMsgf(buf *bytes.Buffer, s string, a ...interface{})
}

View File

@@ -1,914 +0,0 @@
package logger
import (
stdfmt "fmt"
"reflect"
"strconv"
"time"
"unsafe"
"codeberg.org/gruf/go-bytes"
)
// DefaultTextFormat is the default TextFormat instance
var DefaultTextFormat = TextFormat{
Strict: false,
Verbose: false,
MaxDepth: 10,
Levels: DefaultLevels(),
}
// TextFormat is the default LogFormat implementation, with very similar formatting to the
// standard "fmt" package's '%#v' operator. The main difference being that pointers are
// dereferenced as far as possible in order to reach a printable value. It is also *mildly* faster.
type TextFormat struct {
// Strict defines whether to use strict key-value pair formatting, i.e. should the level
// timestamp and msg be formatted as key-value pairs (with forced quoting for msg)
Strict bool
// Verbose defines whether to increase output verbosity, i.e. include types with nil values
// and force values implementing .String() / .AppendFormat() to be printed as a struct etc.
Verbose bool
// MaxDepth specifies the max depth of fields the formatter will iterate
MaxDepth uint8
// Levels defines the map of log LEVELs to level strings
Levels Levels
}
// fmt returns a new format instance based on receiver TextFormat and given buffer
func (f TextFormat) fmt(buf *bytes.Buffer) format {
var flags uint8
if f.Verbose {
flags |= vboseBit
}
return format{
flags: flags,
curd: 0,
maxd: f.MaxDepth,
buf: buf,
}
}
func (f TextFormat) AppendKey(buf *bytes.Buffer, key string) {
if len(key) > 0 {
// only append if key is non-zero length
appendString(f.fmt(buf).SetIsKey(true), key)
buf.WriteByte('=')
}
}
func (f TextFormat) AppendLevel(buf *bytes.Buffer, lvl LEVEL) {
if f.Strict {
// Strict format, append level key
buf.WriteString(`level=`)
buf.WriteString(f.Levels.Get(lvl))
return
}
// Write level string
buf.WriteByte('[')
buf.WriteString(f.Levels.Get(lvl))
buf.WriteByte(']')
}
func (f TextFormat) AppendTimestamp(buf *bytes.Buffer, now string) {
if f.Strict {
// Strict format, use key and quote
buf.WriteString(`time=`)
appendString(f.fmt(buf), now)
return
}
// Write time as-is
buf.WriteString(now)
}
func (f TextFormat) AppendValue(buf *bytes.Buffer, value interface{}) {
appendIfaceOrRValue(f.fmt(buf).SetIsKey(false), value)
}
func (f TextFormat) AppendByte(buf *bytes.Buffer, value byte) {
appendByte(f.fmt(buf), value)
}
func (f TextFormat) AppendBytes(buf *bytes.Buffer, value []byte) {
appendBytes(f.fmt(buf), value)
}
func (f TextFormat) AppendString(buf *bytes.Buffer, value string) {
appendString(f.fmt(buf), value)
}
func (f TextFormat) AppendStrings(buf *bytes.Buffer, value []string) {
appendStringSlice(f.fmt(buf), value)
}
func (f TextFormat) AppendBool(buf *bytes.Buffer, value bool) {
appendBool(f.fmt(buf), value)
}
func (f TextFormat) AppendBools(buf *bytes.Buffer, value []bool) {
appendBoolSlice(f.fmt(buf), value)
}
func (f TextFormat) AppendInt(buf *bytes.Buffer, value int) {
appendInt(f.fmt(buf), int64(value))
}
func (f TextFormat) AppendInts(buf *bytes.Buffer, value []int) {
appendIntSlice(f.fmt(buf), value)
}
func (f TextFormat) AppendUint(buf *bytes.Buffer, value uint) {
appendUint(f.fmt(buf), uint64(value))
}
func (f TextFormat) AppendUints(buf *bytes.Buffer, value []uint) {
appendUintSlice(f.fmt(buf), value)
}
func (f TextFormat) AppendFloat(buf *bytes.Buffer, value float64) {
appendFloat(f.fmt(buf), value)
}
func (f TextFormat) AppendFloats(buf *bytes.Buffer, value []float64) {
appendFloatSlice(f.fmt(buf), value)
}
func (f TextFormat) AppendTime(buf *bytes.Buffer, value time.Time) {
appendTime(f.fmt(buf), value)
}
func (f TextFormat) AppendTimes(buf *bytes.Buffer, value []time.Time) {
appendTimeSlice(f.fmt(buf), value)
}
func (f TextFormat) AppendDuration(buf *bytes.Buffer, value time.Duration) {
appendDuration(f.fmt(buf), value)
}
func (f TextFormat) AppendDurations(buf *bytes.Buffer, value []time.Duration) {
appendDurationSlice(f.fmt(buf), value)
}
func (f TextFormat) AppendMsg(buf *bytes.Buffer, a ...interface{}) {
if f.Strict {
// Strict format, use key and quote
buf.WriteString(`msg=`)
buf.B = strconv.AppendQuote(buf.B, stdfmt.Sprint(a...))
return
}
// Write message as-is
stdfmt.Fprint(buf, a...)
}
func (f TextFormat) AppendMsgf(buf *bytes.Buffer, s string, a ...interface{}) {
if f.Strict {
// Strict format, use key and quote
buf.WriteString(`msg=`)
buf.B = strconv.AppendQuote(buf.B, stdfmt.Sprintf(s, a...))
return
}
// Write message as-is
stdfmt.Fprintf(buf, s, a...)
}
// format is the object passed among the append___ formatting functions
type format struct {
flags uint8 // 'isKey' and 'verbose' flags
drefs uint8 // current value deref count
curd uint8 // current depth
maxd uint8 // maximum depth
buf *bytes.Buffer // out buffer
}
const (
// flag bit constants
isKeyBit = uint8(1) << 0
vboseBit = uint8(1) << 1
)
// AtMaxDepth returns whether format is currently at max depth.
func (f format) AtMaxDepth() bool {
return f.curd >= f.maxd
}
// Derefs returns no. times current value has been dereferenced.
func (f format) Derefs() uint8 {
return f.drefs
}
// IsKey returns whether the isKey flag is set.
func (f format) IsKey() bool {
return (f.flags & isKeyBit) != 0
}
// Verbose returns whether the verbose flag is set.
func (f format) Verbose() bool {
return (f.flags & vboseBit) != 0
}
// SetIsKey returns format instance with the isKey bit set to value.
func (f format) SetIsKey(is bool) format {
flags := f.flags
if is {
flags |= isKeyBit
} else {
flags &= ^isKeyBit
}
return format{
flags: flags,
drefs: f.drefs,
curd: f.curd,
maxd: f.maxd,
buf: f.buf,
}
}
// IncrDepth returns format instance with depth incremented.
func (f format) IncrDepth() format {
return format{
flags: f.flags,
drefs: f.drefs,
curd: f.curd + 1,
maxd: f.maxd,
buf: f.buf,
}
}
// IncrDerefs returns format instance with dereference count incremented.
func (f format) IncrDerefs() format {
return format{
flags: f.flags,
drefs: f.drefs + 1,
curd: f.curd,
maxd: f.maxd,
buf: f.buf,
}
}
// appendType appends a type using supplied type str.
func appendType(fmt format, t string) {
for i := uint8(0); i < fmt.Derefs(); i++ {
fmt.buf.WriteByte('*')
}
fmt.buf.WriteString(t)
}
// appendNilType writes nil to buf, type included if verbose.
func appendNilType(fmt format, t string) {
if fmt.Verbose() {
fmt.buf.WriteByte('(')
appendType(fmt, t)
fmt.buf.WriteString(`)(nil)`)
} else {
fmt.buf.WriteString(`nil`)
}
}
// appendNilFace writes nil to buf, type included if verbose.
func appendNilIface(fmt format, i interface{}) {
if fmt.Verbose() {
fmt.buf.WriteByte('(')
appendType(fmt, reflect.TypeOf(i).String())
fmt.buf.WriteString(`)(nil)`)
} else {
fmt.buf.WriteString(`nil`)
}
}
// appendNilRValue writes nil to buf, type included if verbose.
func appendNilRValue(fmt format, v reflect.Value) {
if fmt.Verbose() {
fmt.buf.WriteByte('(')
appendType(fmt, v.Type().String())
fmt.buf.WriteString(`)(nil)`)
} else {
fmt.buf.WriteString(`nil`)
}
}
// appendByte writes a single byte to buf
func appendByte(fmt format, b byte) {
fmt.buf.WriteByte(b)
}
// appendBytes writes a quoted byte slice to buf
func appendBytes(fmt format, b []byte) {
if !fmt.IsKey() && b == nil {
// Values CAN be nil formatted
appendNilType(fmt, `[]byte`)
} else {
// unsafe cast as string to prevent reallocation
appendString(fmt, *(*string)(unsafe.Pointer(&b)))
}
}
// appendString writes an escaped, double-quoted string to buf
func appendString(fmt format, s string) {
if !fmt.IsKey() || !strconv.CanBackquote(s) {
// All non-keys and multiline keys get quoted + escaped
fmt.buf.B = strconv.AppendQuote(fmt.buf.B, s)
return
} else if containsSpaceOrTab(s) {
// Key containing spaces/tabs, quote this
fmt.buf.WriteByte('"')
fmt.buf.WriteString(s)
fmt.buf.WriteByte('"')
return
}
// Safe to leave unquoted
fmt.buf.WriteString(s)
}
// appendStringSlice writes a slice of strings to buf
func appendStringSlice(fmt format, s []string) {
// Check for nil slice
if s == nil {
appendNilType(fmt, `[]string`)
return
}
fmt.buf.WriteByte('[')
// Write elements
for _, s := range s {
appendString(fmt.SetIsKey(false), s)
fmt.buf.WriteByte(',')
}
// Drop last comma
if len(s) > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.WriteByte(']')
}
// appendBool writes a formatted bool to buf
func appendBool(fmt format, b bool) {
fmt.buf.B = strconv.AppendBool(fmt.buf.B, b)
}
// appendBool writes a slice of formatted bools to buf
func appendBoolSlice(fmt format, b []bool) {
// Check for nil slice
if b == nil {
appendNilType(fmt, `[]bool`)
return
}
fmt.buf.WriteByte('[')
// Write elements
for _, b := range b {
appendBool(fmt, b)
fmt.buf.WriteByte(',')
}
// Drop last comma
if len(b) > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.WriteByte(']')
}
// appendInt writes a formatted int to buf
func appendInt(fmt format, i int64) {
fmt.buf.B = strconv.AppendInt(fmt.buf.B, i, 10)
}
// appendIntSlice writes a slice of formatted int to buf
func appendIntSlice(fmt format, i []int) {
// Check for nil slice
if i == nil {
appendNilType(fmt, `[]int`)
return
}
fmt.buf.WriteByte('[')
// Write elements
for _, i := range i {
appendInt(fmt, int64(i))
fmt.buf.WriteByte(',')
}
// Drop last comma
if len(i) > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.WriteByte(']')
}
// appendUint writes a formatted uint to buf
func appendUint(fmt format, u uint64) {
fmt.buf.B = strconv.AppendUint(fmt.buf.B, u, 10)
}
// appendUintSlice writes a slice of formatted uint to buf
func appendUintSlice(fmt format, u []uint) {
// Check for nil slice
if u == nil {
appendNilType(fmt, `[]uint`)
return
}
fmt.buf.WriteByte('[')
// Write elements
for _, u := range u {
appendUint(fmt, uint64(u))
fmt.buf.WriteByte(',')
}
// Drop last comma
if len(u) > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.WriteByte(']')
}
// appendFloat writes a formatted float to buf
func appendFloat(fmt format, f float64) {
fmt.buf.B = strconv.AppendFloat(fmt.buf.B, f, 'G', -1, 64)
}
// appendFloatSlice writes a slice formatted floats to buf
func appendFloatSlice(fmt format, f []float64) {
// Check for nil slice
if f == nil {
appendNilType(fmt, `[]float64`)
return
}
fmt.buf.WriteByte('[')
// Write elements
for _, f := range f {
appendFloat(fmt, f)
fmt.buf.WriteByte(',')
}
// Drop last comma
if len(f) > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.WriteByte(']')
}
// appendTime writes a formatted, quoted time string to buf
func appendTime(fmt format, t time.Time) {
appendString(fmt.SetIsKey(true), t.Format(time.RFC1123))
}
// appendTimeSlice writes a slice of formatted time strings to buf
func appendTimeSlice(fmt format, t []time.Time) {
// Check for nil slice
if t == nil {
appendNilType(fmt, `[]time.Time`)
return
}
fmt.buf.WriteByte('[')
// Write elements
for _, t := range t {
appendString(fmt.SetIsKey(true), t.Format(time.RFC1123))
fmt.buf.WriteByte(',')
}
// Drop last comma
if len(t) > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.WriteByte(']')
}
// appendDuration writes a formatted, quoted duration string to buf
func appendDuration(fmt format, d time.Duration) {
appendString(fmt.SetIsKey(true), d.String())
}
// appendDurationSlice writes a slice of formatted, quoted duration strings to buf
func appendDurationSlice(fmt format, d []time.Duration) {
// Check for nil slice
if d == nil {
appendNilType(fmt, `[]time.Duration`)
return
}
fmt.buf.WriteByte('[')
// Write elements
for _, d := range d {
appendString(fmt.SetIsKey(true), d.String())
fmt.buf.WriteByte(',')
}
// Drop last comma
if len(d) > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.WriteByte(']')
}
// appendComplex writes a formatted complex128 to buf
func appendComplex(fmt format, c complex128) {
appendFloat(fmt, real(c))
fmt.buf.WriteByte('+')
appendFloat(fmt, imag(c))
fmt.buf.WriteByte('i')
}
// appendComplexSlice writes a slice of formatted complex128s to buf
func appendComplexSlice(fmt format, c []complex128) {
// Check for nil slice
if c == nil {
appendNilType(fmt, `[]complex128`)
return
}
fmt.buf.WriteByte('[')
// Write elements
for _, c := range c {
appendComplex(fmt, c)
fmt.buf.WriteByte(',')
}
// Drop last comma
if len(c) > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.WriteByte(']')
}
// notNil will safely check if 'v' is nil without dealing with weird Go interface nil bullshit.
func notNil(i interface{}) bool {
// cast to get fat pointer
e := *(*struct {
typeOf unsafe.Pointer // ignored
valueOf unsafe.Pointer
})(unsafe.Pointer(&i))
// check if value part is nil
return (e.valueOf != nil)
}
// appendIfaceOrRValueNext performs appendIfaceOrRValue checking + incr depth
func appendIfaceOrRValueNext(fmt format, i interface{}) {
// Check we haven't hit max
if fmt.AtMaxDepth() {
fmt.buf.WriteString("...")
return
}
// Incr the depth
fmt = fmt.IncrDepth()
// Make actual call
appendIfaceOrRValue(fmt, i)
}
// appendIfaceOrReflectValue will attempt to append as interface, falling back to reflection
func appendIfaceOrRValue(fmt format, i interface{}) {
if !appendIface(fmt, i) {
appendRValue(fmt, reflect.ValueOf(i))
}
}
// appendValueOrIfaceNext performs appendRValueOrIface checking + incr depth
func appendRValueOrIfaceNext(fmt format, v reflect.Value) {
// Check we haven't hit max
if fmt.AtMaxDepth() {
fmt.buf.WriteString("...")
return
}
// Incr the depth
fmt = fmt.IncrDepth()
// Make actual call
appendRValueOrIface(fmt, v)
}
// appendRValueOrIface will attempt to interface the reflect.Value, falling back to using this directly
func appendRValueOrIface(fmt format, v reflect.Value) {
if !v.CanInterface() || !appendIface(fmt, v.Interface()) {
appendRValue(fmt, v)
}
}
// appendIface parses and writes a formatted interface value to buf
func appendIface(fmt format, i interface{}) bool {
switch i := i.(type) {
case nil:
fmt.buf.WriteString(`nil`)
case byte:
appendByte(fmt, i)
case []byte:
appendBytes(fmt, i)
case string:
appendString(fmt, i)
case []string:
appendStringSlice(fmt, i)
case int:
appendInt(fmt, int64(i))
case int8:
appendInt(fmt, int64(i))
case int16:
appendInt(fmt, int64(i))
case int32:
appendInt(fmt, int64(i))
case int64:
appendInt(fmt, i)
case []int:
appendIntSlice(fmt, i)
case uint:
appendUint(fmt, uint64(i))
case uint16:
appendUint(fmt, uint64(i))
case uint32:
appendUint(fmt, uint64(i))
case uint64:
appendUint(fmt, i)
case []uint:
appendUintSlice(fmt, i)
case float32:
appendFloat(fmt, float64(i))
case float64:
appendFloat(fmt, i)
case []float64:
appendFloatSlice(fmt, i)
case bool:
appendBool(fmt, i)
case []bool:
appendBoolSlice(fmt, i)
case time.Time:
appendTime(fmt, i)
case []time.Time:
appendTimeSlice(fmt, i)
case time.Duration:
appendDuration(fmt, i)
case []time.Duration:
appendDurationSlice(fmt, i)
case complex64:
appendComplex(fmt, complex128(i))
case complex128:
appendComplex(fmt, i)
case []complex128:
appendComplexSlice(fmt, i)
case map[string]interface{}:
appendIfaceMap(fmt, i)
case error:
if notNil(i) /* use safer nil check */ {
appendString(fmt, i.Error())
} else {
appendNilIface(fmt, i)
}
case Formattable:
switch {
// catch nil case first
case !notNil(i):
appendNilIface(fmt, i)
// not permitted
case fmt.Verbose():
return false
// use func
default:
fmt.buf.B = i.AppendFormat(fmt.buf.B)
}
case stdfmt.Stringer:
switch {
// catch nil case first
case !notNil(i):
appendNilIface(fmt, i)
// not permitted
case fmt.Verbose():
return false
// use func
default:
appendString(fmt, i.String())
}
default:
return false // could not handle
}
return true
}
// appendReflectValue will safely append a reflected value
func appendRValue(fmt format, v reflect.Value) {
switch v.Kind() {
case reflect.Float32, reflect.Float64:
appendFloat(fmt, v.Float())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
appendInt(fmt, v.Int())
case reflect.Uint8:
appendByte(fmt, uint8(v.Uint()))
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
appendUint(fmt, v.Uint())
case reflect.Bool:
appendBool(fmt, v.Bool())
case reflect.Array:
appendArrayType(fmt, v)
case reflect.Slice:
appendSliceType(fmt, v)
case reflect.Map:
appendMapType(fmt, v)
case reflect.Struct:
appendStructType(fmt, v)
case reflect.Ptr:
if v.IsNil() {
appendNilRValue(fmt, v)
} else {
appendRValue(fmt.IncrDerefs(), v.Elem())
}
case reflect.UnsafePointer:
fmt.buf.WriteString("(unsafe.Pointer)")
fmt.buf.WriteByte('(')
if u := v.Pointer(); u != 0 {
fmt.buf.WriteString("0x")
fmt.buf.B = strconv.AppendUint(fmt.buf.B, uint64(u), 16)
} else {
fmt.buf.WriteString(`nil`)
}
fmt.buf.WriteByte(')')
case reflect.Uintptr:
fmt.buf.WriteString("(uintptr)")
fmt.buf.WriteByte('(')
if u := v.Uint(); u != 0 {
fmt.buf.WriteString("0x")
fmt.buf.B = strconv.AppendUint(fmt.buf.B, u, 16)
} else {
fmt.buf.WriteString(`nil`)
}
fmt.buf.WriteByte(')')
case reflect.String:
appendString(fmt, v.String())
case reflect.Complex64, reflect.Complex128:
appendComplex(fmt, v.Complex())
case reflect.Func, reflect.Chan, reflect.Interface:
if v.IsNil() {
appendNilRValue(fmt, v)
} else {
fmt.buf.WriteString(v.String())
}
default:
fmt.buf.WriteString(v.String())
}
}
// appendIfaceMap writes a map of key-value pairs (as a set of fields) to buf
func appendIfaceMap(fmt format, v map[string]interface{}) {
// Catch nil map
if v == nil {
appendNilType(fmt, `map[string]interface{}`)
return
}
fmt.buf.WriteByte('{')
// Write map pairs!
for key, value := range v {
appendString(fmt.SetIsKey(true), key)
fmt.buf.WriteByte('=')
appendIfaceOrRValueNext(fmt.SetIsKey(false), value)
fmt.buf.WriteByte(' ')
}
// Drop last space
if len(v) > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.WriteByte('}')
}
// appendArrayType writes an array of unknown type (parsed by reflection) to buf, unlike appendSliceType does NOT catch nil slice
func appendArrayType(fmt format, v reflect.Value) {
// get no. elements
n := v.Len()
fmt.buf.WriteByte('[')
// Write values
for i := 0; i < n; i++ {
appendRValueOrIfaceNext(fmt.SetIsKey(false), v.Index(i))
fmt.buf.WriteByte(',')
}
// Drop last comma
if n > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.WriteByte(']')
}
// appendSliceType writes a slice of unknown type (parsed by reflection) to buf
func appendSliceType(fmt format, v reflect.Value) {
if v.IsNil() {
appendNilRValue(fmt, v)
} else {
appendArrayType(fmt, v)
}
}
// appendMapType writes a map of unknown types (parsed by reflection) to buf
func appendMapType(fmt format, v reflect.Value) {
// Catch nil map
if v.IsNil() {
appendNilRValue(fmt, v)
return
}
// Get a map iterator
r := v.MapRange()
n := v.Len()
fmt.buf.WriteByte('{')
// Iterate pairs
for r.Next() {
appendRValueOrIfaceNext(fmt.SetIsKey(true), r.Key())
fmt.buf.WriteByte('=')
appendRValueOrIfaceNext(fmt.SetIsKey(false), r.Value())
fmt.buf.WriteByte(' ')
}
// Drop last space
if n > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.WriteByte('}')
}
// appendStructType writes a struct (as a set of key-value fields) to buf
func appendStructType(fmt format, v reflect.Value) {
// Get value type & no. fields
t := v.Type()
n := v.NumField()
w := 0
// If verbose, append the type
fmt.buf.WriteByte('{')
// Iterate fields
for i := 0; i < n; i++ {
vfield := v.Field(i)
name := t.Field(i).Name
// Append field name
appendString(fmt.SetIsKey(true), name)
fmt.buf.WriteByte('=')
if !vfield.CanInterface() {
// This is an unexported field
appendRValue(fmt.SetIsKey(false), vfield)
} else {
// This is an exported field!
appendRValueOrIfaceNext(fmt.SetIsKey(false), vfield)
}
// Iter written count
fmt.buf.WriteByte(' ')
w++
}
// Drop last space
if w > 0 {
fmt.buf.Truncate(1)
}
fmt.buf.WriteByte('}')
}
// containsSpaceOrTab checks if "s" contains space or tabs
func containsSpaceOrTab(s string) bool {
for _, r := range s {
if r == ' ' || r == '\t' {
return true
}
}
return false
}

View File

@@ -1,13 +0,0 @@
package logger
// Hook defines a log Entry modifier
type Hook interface {
Do(*Entry)
}
// HookFunc is a simple adapter to allow functions to satisfy the Hook interface
type HookFunc func(*Entry)
func (hook HookFunc) Do(entry *Entry) {
hook(entry)
}

View File

@@ -1,38 +0,0 @@
package logger
// LEVEL defines a level of logging
type LEVEL uint8
// Available levels of logging.
const (
unset LEVEL = ^LEVEL(0)
DEBUG LEVEL = 5
INFO LEVEL = 10
WARN LEVEL = 15
ERROR LEVEL = 20
FATAL LEVEL = 25
)
var unknownLevel = "unknown"
// Levels defines a mapping of log LEVELs to formatted level strings
type Levels [^LEVEL(0)]string
// DefaultLevels returns the default set of log levels
func DefaultLevels() Levels {
return Levels{
DEBUG: "DEBUG",
INFO: "INFO",
WARN: "WARN",
ERROR: "ERROR",
FATAL: "FATAL",
}
}
// Get fetches the level string for the provided value, or "unknown"
func (l Levels) Get(lvl LEVEL) string {
if str := l[int(lvl)]; str != "" {
return str
}
return unknownLevel
}

View File

@@ -1,187 +0,0 @@
package logger
import (
"context"
"fmt"
"io"
"os"
"sync"
"sync/atomic"
"codeberg.org/gruf/go-bytes"
)
type Logger struct {
// Hooks defines a list of hooks which are called before an entry
// is written. This should NOT be modified while the Logger is in use
Hooks []Hook
// Level is the current log LEVEL, entries at level below the
// currently set level will not be output. This should NOT
// be modified while the Logger is in use
Level LEVEL
// Timestamp defines whether to automatically append timestamps
// to entries written via Logger convience methods and specifically
// Entry.TimestampIf(). This should NOT be modified while Logger in use
Timestamp bool
// Format is the log entry LogFormat to use. This should NOT
// be modified while the Logger is in use
Format LogFormat
// BufferSize is the Entry buffer size to use when allocating
// new Entry objects. This should be modified atomically
BufSize int64
// Output is the log's output writer. This should NOT be
// modified while the Logger is in use
Output io.Writer
// entry pool
pool sync.Pool
}
// New returns a new Logger instance with defaults
func New(out io.Writer) *Logger {
return NewWith(0 /* all */, true, DefaultTextFormat, 512, out)
}
// NewWith returns a new Logger instance with supplied configuration
func NewWith(lvl LEVEL, timestamp bool, fmt LogFormat, bufsize int64, out io.Writer) *Logger {
// Create new logger object
log := &Logger{
Level: lvl,
Timestamp: timestamp,
Format: fmt,
BufSize: bufsize,
Output: out,
}
// Ensure clock running
startClock()
// Set-up logger Entry pool
log.pool.New = func() interface{} {
return &Entry{
lvl: unset,
buf: &bytes.Buffer{B: make([]byte, 0, atomic.LoadInt64(&log.BufSize))},
log: log,
}
}
return log
}
// Entry returns a new Entry from the Logger's pool with background context
func (l *Logger) Entry() *Entry {
entry, _ := l.pool.Get().(*Entry)
entry.ctx = context.Background()
return entry
}
// Debug prints the provided arguments with the debug prefix
func (l *Logger) Debug(a ...interface{}) {
l.Log(DEBUG, a...)
}
// Debugf prints the provided format string and arguments with the debug prefix
func (l *Logger) Debugf(s string, a ...interface{}) {
l.Logf(DEBUG, s, a...)
}
// Info prints the provided arguments with the info prefix
func (l *Logger) Info(a ...interface{}) {
l.Log(INFO, a...)
}
// Infof prints the provided format string and arguments with the info prefix
func (l *Logger) Infof(s string, a ...interface{}) {
l.Logf(INFO, s, a...)
}
// Warn prints the provided arguments with the warn prefix
func (l *Logger) Warn(a ...interface{}) {
l.Log(WARN, a...)
}
// Warnf prints the provided format string and arguments with the warn prefix
func (l *Logger) Warnf(s string, a ...interface{}) {
l.Logf(WARN, s, a...)
}
// Error prints the provided arguments with the error prefix
func (l *Logger) Error(a ...interface{}) {
l.Log(ERROR, a...)
}
// Errorf prints the provided format string and arguments with the error prefix
func (l *Logger) Errorf(s string, a ...interface{}) {
l.Logf(ERROR, s, a...)
}
// Fatal prints provided arguments with the fatal prefix before exiting the program
// with os.Exit(1)
func (l *Logger) Fatal(a ...interface{}) {
defer os.Exit(1)
l.Log(FATAL, a...)
}
// Fatalf prints provided the provided format string and arguments with the fatal prefix
// before exiting the program with os.Exit(1)
func (l *Logger) Fatalf(s string, a ...interface{}) {
defer os.Exit(1)
l.Logf(FATAL, s, a...)
}
// Log prints the provided arguments at the supplied log level
func (l *Logger) Log(lvl LEVEL, a ...interface{}) {
if lvl >= l.Level {
l.Entry().TimestampIf().Level(lvl).Hooks().Msg(a...)
}
}
// Logf prints the provided format string and arguments at the supplied log level
func (l *Logger) Logf(lvl LEVEL, s string, a ...interface{}) {
if lvl >= l.Level {
l.Entry().TimestampIf().Level(lvl).Hooks().Msgf(s, a...)
}
}
// LogFields prints the provided fields formatted as key-value pairs at the supplied log level
func (l *Logger) LogFields(lvl LEVEL, fields map[string]interface{}) {
if lvl >= l.Level {
l.Entry().TimestampIf().Level(lvl).Fields(fields).Hooks().Send()
}
}
// LogValues prints the provided values formatted as-so at the supplied log level
func (l *Logger) LogValues(lvl LEVEL, a ...interface{}) {
if lvl >= l.Level {
l.Entry().TimestampIf().Level(lvl).Values(a...).Hooks().Send()
}
}
// Print simply prints provided arguments
func (l *Logger) Print(a ...interface{}) {
e := l.Entry().TimestampIf()
fmt.Fprint(e.buf, a...)
e.Send()
}
// Printf simply prints provided the provided format string and arguments
func (l *Logger) Printf(s string, a ...interface{}) {
e := l.Entry().TimestampIf()
fmt.Fprintf(e.buf, s, a...)
e.Send()
}
// PrintFields prints the provided fields formatted as key-value pairs
func (l *Logger) PrintFields(fields map[string]interface{}) {
l.Entry().TimestampIf().Fields(fields).Send()
}
// PrintValues prints the provided values formatted as-so
func (l *Logger) PrintValues(a ...interface{}) {
l.Entry().TimestampIf().Values(a...).Send()
}

View File

@@ -1,29 +0,0 @@
package logger
import (
"io"
"io/ioutil"
"sync"
)
// AddSafety wraps an io.Writer to provide mutex locking protection
func AddSafety(w io.Writer) io.Writer {
if w == nil {
w = ioutil.Discard
} else if sw, ok := w.(*safeWriter); ok {
return sw
}
return &safeWriter{wr: w}
}
// safeWriter wraps an io.Writer to provide mutex locking on write
type safeWriter struct {
wr io.Writer
mu sync.Mutex
}
func (w *safeWriter) Write(b []byte) (int, error) {
w.mu.Lock()
defer w.mu.Unlock()
return w.wr.Write(b)
}

View File

@@ -60,5 +60,5 @@ func (i *KVIterator) Value() ([]byte, error) {
}
// Attempt to fetch from store
return i.store.get(i.key)
return i.store.get(i.store.mutexMap.RLock, i.key)
}

View File

@@ -30,7 +30,7 @@ func (st *StateRO) Get(key string) ([]byte, error) {
}
// Pass request to store
return st.store.get(key)
return st.store.get(st.store.mutexMap.RLock, key)
}
func (st *StateRO) GetStream(key string) (io.ReadCloser, error) {
@@ -44,7 +44,7 @@ func (st *StateRO) GetStream(key string) (io.ReadCloser, error) {
}
// Pass request to store
return st.store.getStream(key)
return st.store.getStream(st.store.mutexMap.RLock, key)
}
func (st *StateRO) Has(key string) (bool, error) {
@@ -58,7 +58,7 @@ func (st *StateRO) Has(key string) (bool, error) {
}
// Pass request to store
return st.store.has(key)
return st.store.has(st.store.mutexMap.RLock, key)
}
func (st *StateRO) Release() {
@@ -94,7 +94,7 @@ func (st *StateRW) Get(key string) ([]byte, error) {
}
// Pass request to store
return st.store.get(key)
return st.store.get(st.store.mutexMap.RLock, key)
}
func (st *StateRW) GetStream(key string) (io.ReadCloser, error) {
@@ -108,7 +108,7 @@ func (st *StateRW) GetStream(key string) (io.ReadCloser, error) {
}
// Pass request to store
return st.store.getStream(key)
return st.store.getStream(st.store.mutexMap.RLock, key)
}
func (st *StateRW) Put(key string, value []byte) error {
@@ -122,7 +122,7 @@ func (st *StateRW) Put(key string, value []byte) error {
}
// Pass request to store
return st.store.put(key, value)
return st.store.put(st.store.mutexMap.Lock, key, value)
}
func (st *StateRW) PutStream(key string, r io.Reader) error {
@@ -136,7 +136,7 @@ func (st *StateRW) PutStream(key string, r io.Reader) error {
}
// Pass request to store
return st.store.putStream(key, r)
return st.store.putStream(st.store.mutexMap.Lock, key, r)
}
func (st *StateRW) Has(key string) (bool, error) {
@@ -150,7 +150,7 @@ func (st *StateRW) Has(key string) (bool, error) {
}
// Pass request to store
return st.store.has(key)
return st.store.has(st.store.mutexMap.RLock, key)
}
func (st *StateRW) Delete(key string) error {
@@ -164,7 +164,7 @@ func (st *StateRW) Delete(key string) error {
}
// Pass request to store
return st.store.delete(key)
return st.store.delete(st.store.mutexMap.Lock, key)
}
func (st *StateRW) Release() {

View File

@@ -53,19 +53,30 @@ func OpenStorage(storage storage.Storage) (*KVStore, error) {
}, nil
}
// Get fetches the bytes for supplied key in the store
func (st *KVStore) Get(key string) ([]byte, error) {
// Acquire store read lock
// RLock acquires a read-lock on supplied key, returning unlock function.
func (st *KVStore) RLock(key string) (runlock func()) {
st.mutex.RLock()
defer st.mutex.RUnlock()
// Pass to unprotected fn
return st.get(key)
runlock = st.mutexMap.RLock(key)
st.mutex.RUnlock()
return runlock
}
func (st *KVStore) get(key string) ([]byte, error) {
// Lock acquires a write-lock on supplied key, returning unlock function.
func (st *KVStore) Lock(key string) (unlock func()) {
st.mutex.Lock()
unlock = st.mutexMap.Lock(key)
st.mutex.Unlock()
return unlock
}
// Get fetches the bytes for supplied key in the store
func (st *KVStore) Get(key string) ([]byte, error) {
return st.get(st.RLock, key)
}
func (st *KVStore) get(rlock func(string) func(), key string) ([]byte, error) {
// Acquire read lock for key
runlock := st.mutexMap.RLock(key)
runlock := rlock(key)
defer runlock()
// Read file bytes
@@ -74,17 +85,12 @@ func (st *KVStore) get(key string) ([]byte, error) {
// GetStream fetches a ReadCloser for the bytes at the supplied key location in the store
func (st *KVStore) GetStream(key string) (io.ReadCloser, error) {
// Acquire store read lock
st.mutex.RLock()
defer st.mutex.RUnlock()
// Pass to unprotected fn
return st.getStream(key)
return st.getStream(st.RLock, key)
}
func (st *KVStore) getStream(key string) (io.ReadCloser, error) {
func (st *KVStore) getStream(rlock func(string) func(), key string) (io.ReadCloser, error) {
// Acquire read lock for key
runlock := st.mutexMap.RLock(key)
runlock := rlock(key)
// Attempt to open stream for read
rd, err := st.storage.ReadStream(key)
@@ -99,17 +105,12 @@ func (st *KVStore) getStream(key string) (io.ReadCloser, error) {
// Put places the bytes at the supplied key location in the store
func (st *KVStore) Put(key string, value []byte) error {
// Acquire store write lock
st.mutex.Lock()
defer st.mutex.Unlock()
// Pass to unprotected fn
return st.put(key, value)
return st.put(st.Lock, key, value)
}
func (st *KVStore) put(key string, value []byte) error {
func (st *KVStore) put(lock func(string) func(), key string, value []byte) error {
// Acquire write lock for key
unlock := st.mutexMap.Lock(key)
unlock := lock(key)
defer unlock()
// Write file bytes
@@ -118,17 +119,12 @@ func (st *KVStore) put(key string, value []byte) error {
// PutStream writes the bytes from the supplied Reader at the supplied key location in the store
func (st *KVStore) PutStream(key string, r io.Reader) error {
// Acquire store write lock
st.mutex.Lock()
defer st.mutex.Unlock()
// Pass to unprotected fn
return st.putStream(key, r)
return st.putStream(st.Lock, key, r)
}
func (st *KVStore) putStream(key string, r io.Reader) error {
func (st *KVStore) putStream(lock func(string) func(), key string, r io.Reader) error {
// Acquire write lock for key
unlock := st.mutexMap.Lock(key)
unlock := lock(key)
defer unlock()
// Write file stream
@@ -137,17 +133,12 @@ func (st *KVStore) putStream(key string, r io.Reader) error {
// Has checks whether the supplied key exists in the store
func (st *KVStore) Has(key string) (bool, error) {
// Acquire store read lock
st.mutex.RLock()
defer st.mutex.RUnlock()
// Pass to unprotected fn
return st.has(key)
return st.has(st.RLock, key)
}
func (st *KVStore) has(key string) (bool, error) {
func (st *KVStore) has(rlock func(string) func(), key string) (bool, error) {
// Acquire read lock for key
runlock := st.mutexMap.RLock(key)
runlock := rlock(key)
defer runlock()
// Stat file on disk
@@ -156,17 +147,12 @@ func (st *KVStore) has(key string) (bool, error) {
// Delete removes the supplied key-value pair from the store
func (st *KVStore) Delete(key string) error {
// Acquire store write lock
st.mutex.Lock()
defer st.mutex.Unlock()
// Pass to unprotected fn
return st.delete(key)
return st.delete(st.Lock, key)
}
func (st *KVStore) delete(key string) error {
func (st *KVStore) delete(lock func(string) func(), key string) error {
// Acquire write lock for key
unlock := st.mutexMap.Lock(key)
unlock := lock(key)
defer unlock()
// Remove file from disk

View File

@@ -1,7 +1,6 @@
package storage
import (
"crypto/sha256"
"io"
"io/fs"
"os"
@@ -14,6 +13,7 @@ import (
"codeberg.org/gruf/go-hashenc"
"codeberg.org/gruf/go-pools"
"codeberg.org/gruf/go-store/util"
"github.com/zeebo/blake3"
)
var (
@@ -77,7 +77,7 @@ func getBlockConfig(cfg *BlockConfig) BlockConfig {
// BlockStorage is a Storage implementation that stores input data as chunks on
// a filesystem. Each value is chunked into blocks of configured size and these
// blocks are stored with name equal to their base64-encoded SHA256 hash-sum. A
// blocks are stored with name equal to their base64-encoded BLAKE3 hash-sum. A
// "node" file is finally created containing an array of hashes contained within
// this value
type BlockStorage struct {
@@ -87,6 +87,7 @@ type BlockStorage struct {
config BlockConfig // cfg is the supplied configuration for this store
hashPool sync.Pool // hashPool is this store's hashEncoder pool
bufpool pools.BufferPool // bufpool is this store's bytes.Buffer pool
lock *LockableFile // lock is the opened lockfile for this storage instance
// NOTE:
// BlockStorage does not need to lock each of the underlying block files
@@ -138,6 +139,14 @@ func OpenBlock(path string, cfg *BlockConfig) (*BlockStorage, error) {
return nil, errPathIsFile
}
// Open and acquire storage lock for path
lock, err := OpenLock(pb.Join(path, LockFile))
if err != nil {
return nil, err
} else if err := lock.Lock(); err != nil {
return nil, err
}
// Figure out the largest size for bufpool slices
bufSz := encodedHashLen
if bufSz < config.BlockSize {
@@ -159,6 +168,7 @@ func OpenBlock(path string, cfg *BlockConfig) (*BlockStorage, error) {
},
},
bufpool: pools.NewBufferPool(bufSz),
lock: lock,
}, nil
}
@@ -443,11 +453,16 @@ loop:
continue loop
}
// Write in separate goroutine
// Check if reached EOF
atEOF := (n < buf.Len())
wg.Add(1)
go func() {
// Defer buffer release + signal done
// Perform writes in goroutine
defer func() {
// Defer release +
// signal we're done
st.bufpool.Put(buf)
wg.Done()
}()
@@ -460,8 +475,8 @@ loop:
}
}()
// We reached EOF
if n < buf.Len() {
// Break at end
if atEOF {
break loop
}
}
@@ -568,6 +583,12 @@ func (st *BlockStorage) Remove(key string) error {
return os.Remove(kpath)
}
// Close implements Storage.Close()
func (st *BlockStorage) Close() error {
defer st.lock.Close()
return st.lock.Unlock()
}
// WalkKeys implements Storage.WalkKeys()
func (st *BlockStorage) WalkKeys(opts WalkKeysOptions) error {
// Acquire path builder
@@ -610,7 +631,7 @@ func (st *BlockStorage) blockPathForKey(hash string) string {
}
// hashSeparator is the separating byte between block hashes
const hashSeparator = byte(':')
const hashSeparator = byte('\n')
// node represents the contents of a node file in storage
type node struct {
@@ -773,24 +794,28 @@ func (r *blockReader) Read(b []byte) (int, error) {
}
}
var (
// base64Encoding is our base64 encoding object.
base64Encoding = hashenc.Base64()
// encodedHashLen is the once-calculated encoded hash-sum length
encodedHashLen = base64Encoding.EncodedLen(
blake3.New().Size(),
)
)
// hashEncoder is a HashEncoder with built-in encode buffer
type hashEncoder struct {
henc hashenc.HashEncoder
ebuf []byte
}
// encodedHashLen is the once-calculated encoded hash-sum length
var encodedHashLen = hashenc.Base64().EncodedLen(
sha256.New().Size(),
)
// newHashEncoder returns a new hashEncoder instance
func newHashEncoder() *hashEncoder {
hash := sha256.New()
enc := hashenc.Base64()
hash := blake3.New()
return &hashEncoder{
henc: hashenc.New(hash, enc),
ebuf: make([]byte, enc.EncodedLen(hash.Size())),
henc: hashenc.New(hash, base64Encoding),
ebuf: make([]byte, encodedHashLen),
}
}

View File

@@ -71,6 +71,7 @@ type DiskStorage struct {
path string // path is the root path of this store
bufp pools.BufferPool // bufp is the buffer pool for this DiskStorage
config DiskConfig // cfg is the supplied configuration for this store
lock *LockableFile // lock is the opened lockfile for this storage instance
}
// OpenFile opens a DiskStorage instance for given folder path and configuration
@@ -81,13 +82,13 @@ func OpenFile(path string, cfg *DiskConfig) (*DiskStorage, error) {
// Clean provided path, ensure ends in '/' (should
// be dir, this helps with file path trimming later)
path = pb.Clean(path) + "/"
storePath := pb.Join(path, "store") + "/"
// Get checked config
config := getDiskConfig(cfg)
// Attempt to open dir path
file, err := os.OpenFile(path, defaultFileROFlags, defaultDirPerms)
file, err := os.OpenFile(storePath, defaultFileROFlags, defaultDirPerms)
if err != nil {
// If not a not-exist error, return
if !os.IsNotExist(err) {
@@ -95,13 +96,13 @@ func OpenFile(path string, cfg *DiskConfig) (*DiskStorage, error) {
}
// Attempt to make store path dirs
err = os.MkdirAll(path, defaultDirPerms)
err = os.MkdirAll(storePath, defaultDirPerms)
if err != nil {
return nil, err
}
// Reopen dir now it's been created
file, err = os.OpenFile(path, defaultFileROFlags, defaultDirPerms)
file, err = os.OpenFile(storePath, defaultFileROFlags, defaultDirPerms)
if err != nil {
return nil, err
}
@@ -116,11 +117,20 @@ func OpenFile(path string, cfg *DiskConfig) (*DiskStorage, error) {
return nil, errPathIsFile
}
// Open and acquire storage lock for path
lock, err := OpenLock(pb.Join(path, LockFile))
if err != nil {
return nil, err
} else if err := lock.Lock(); err != nil {
return nil, err
}
// Return new DiskStorage
return &DiskStorage{
path: path,
path: storePath,
bufp: pools.NewBufferPool(config.WriteBufSize),
config: config,
lock: lock,
}, nil
}
@@ -248,6 +258,12 @@ func (st *DiskStorage) Remove(key string) error {
return os.Remove(kpath)
}
// Close implements Storage.Close()
func (st *DiskStorage) Close() error {
defer st.lock.Close()
return st.lock.Unlock()
}
// WalkKeys implements Storage.WalkKeys()
func (st *DiskStorage) WalkKeys(opts WalkKeysOptions) error {
// Acquire path builder
@@ -256,8 +272,9 @@ func (st *DiskStorage) WalkKeys(opts WalkKeysOptions) error {
// Walk dir for entries
return util.WalkDir(pb, st.path, func(kpath string, fsentry fs.DirEntry) {
// Only deal with regular files
if fsentry.Type().IsRegular() {
// Only deal with regular files
// Get full item path (without root)
kpath = pb.Join(kpath, fsentry.Name())[len(st.path):]

View File

@@ -39,7 +39,7 @@ func stat(path string) (bool, error) {
return syscall.Stat(path, &stat)
})
if err != nil {
if err == syscall.ENOENT {
if err == syscall.ENOENT { //nolint
err = nil
}
return false, err

View File

@@ -7,27 +7,31 @@ import (
"codeberg.org/gruf/go-store/util"
)
type lockableFile struct {
// LockFile is our standard lockfile name.
const LockFile = "store.lock"
type LockableFile struct {
*os.File
}
func openLock(path string) (*lockableFile, error) {
// OpenLock opens a lockfile at path.
func OpenLock(path string) (*LockableFile, error) {
file, err := open(path, defaultFileLockFlags)
if err != nil {
return nil, err
}
return &lockableFile{file}, nil
return &LockableFile{file}, nil
}
func (f *lockableFile) lock() error {
func (f *LockableFile) Lock() error {
return f.flock(syscall.LOCK_EX | syscall.LOCK_NB)
}
func (f *lockableFile) unlock() error {
func (f *LockableFile) Unlock() error {
return f.flock(syscall.LOCK_UN | syscall.LOCK_NB)
}
func (f *lockableFile) flock(how int) error {
func (f *LockableFile) flock(how int) error {
return util.RetryOnEINTR(func() error {
return syscall.Flock(int(f.Fd()), how)
})

View File

@@ -2,6 +2,7 @@ package storage
import (
"io"
"sync"
"codeberg.org/gruf/go-bytes"
"codeberg.org/gruf/go-store/util"
@@ -10,13 +11,17 @@ import (
// MemoryStorage is a storage implementation that simply stores key-value
// pairs in a Go map in-memory. The map is protected by a mutex.
type MemoryStorage struct {
ow bool // overwrites
fs map[string][]byte
mu sync.Mutex
}
// OpenMemory opens a new MemoryStorage instance with internal map of 'size'.
func OpenMemory(size int) *MemoryStorage {
func OpenMemory(size int, overwrites bool) *MemoryStorage {
return &MemoryStorage{
fs: make(map[string][]byte, size),
mu: sync.Mutex{},
ow: overwrites,
}
}
@@ -27,19 +32,33 @@ func (st *MemoryStorage) Clean() error {
// ReadBytes implements Storage.ReadBytes().
func (st *MemoryStorage) ReadBytes(key string) ([]byte, error) {
// Safely check store
st.mu.Lock()
b, ok := st.fs[key]
st.mu.Unlock()
// Return early if not exist
if !ok {
return nil, ErrNotFound
}
// Create return copy
return bytes.Copy(b), nil
}
// ReadStream implements Storage.ReadStream().
func (st *MemoryStorage) ReadStream(key string) (io.ReadCloser, error) {
// Safely check store
st.mu.Lock()
b, ok := st.fs[key]
st.mu.Unlock()
// Return early if not exist
if !ok {
return nil, ErrNotFound
}
// Create io.ReadCloser from 'b' copy
b = bytes.Copy(b)
r := bytes.NewReader(b)
return util.NopReadCloser(r), nil
@@ -47,43 +66,73 @@ func (st *MemoryStorage) ReadStream(key string) (io.ReadCloser, error) {
// WriteBytes implements Storage.WriteBytes().
func (st *MemoryStorage) WriteBytes(key string, b []byte) error {
// Safely check store
st.mu.Lock()
_, ok := st.fs[key]
if ok {
// Check for already exist
if ok && !st.ow {
st.mu.Unlock()
return ErrAlreadyExists
}
// Write + unlock
st.fs[key] = bytes.Copy(b)
st.mu.Unlock()
return nil
}
// WriteStream implements Storage.WriteStream().
func (st *MemoryStorage) WriteStream(key string, r io.Reader) error {
// Read all from reader
b, err := io.ReadAll(r)
if err != nil {
return err
}
// Write to storage
return st.WriteBytes(key, b)
}
// Stat implements Storage.Stat().
func (st *MemoryStorage) Stat(key string) (bool, error) {
st.mu.Lock()
_, ok := st.fs[key]
st.mu.Unlock()
return ok, nil
}
// Remove implements Storage.Remove().
func (st *MemoryStorage) Remove(key string) error {
// Safely check store
st.mu.Lock()
_, ok := st.fs[key]
// Check in store
if !ok {
st.mu.Unlock()
return ErrNotFound
}
// Delete + unlock
delete(st.fs, key)
st.mu.Unlock()
return nil
}
// Close implements Storage.Close().
func (st *MemoryStorage) Close() error {
return nil
}
// WalkKeys implements Storage.WalkKeys().
func (st *MemoryStorage) WalkKeys(opts WalkKeysOptions) error {
// Safely walk storage keys
st.mu.Lock()
for key := range st.fs {
opts.WalkFn(entry(key))
}
st.mu.Unlock()
return nil
}

View File

@@ -19,9 +19,6 @@ func (e entry) Key() string {
// Storage defines a means of storing and accessing key value pairs
type Storage interface {
// Clean removes unused values and unclutters the storage (e.g. removing empty folders)
Clean() error
// ReadBytes returns the byte value for key in storage
ReadBytes(key string) ([]byte, error)
@@ -40,6 +37,12 @@ type Storage interface {
// Remove attempts to remove the supplied key-value pair from storage
Remove(key string) error
// Close will close the storage, releasing any file locks
Close() error
// Clean removes unused values and unclutters the storage (e.g. removing empty folders)
Clean() error
// WalkKeys walks the keys in the storage
WalkKeys(opts WalkKeysOptions) error
}