⚗️ Use future slog package for logfmt middleware
This commit is contained in:
parent
91c17a2afa
commit
c82e364a50
26 changed files with 2891 additions and 1040 deletions
1
go.mod
1
go.mod
|
@ -6,7 +6,6 @@ require (
|
|||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/brianvoe/gofakeit/v6 v6.22.0
|
||||
github.com/caarlos0/env/v7 v7.1.0
|
||||
github.com/go-logfmt/logfmt v0.6.0
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
|
|
2
go.sum
2
go.sum
|
@ -17,8 +17,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3
|
|||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/go-logfmt/logfmt"
|
||||
"golang.org/x/exp/slog" // TODO(toby3d): use "log/slog" after go1.21 release
|
||||
)
|
||||
|
||||
type (
|
||||
|
@ -27,6 +27,7 @@ type (
|
|||
http.ResponseWriter
|
||||
error error
|
||||
start time.Time
|
||||
end time.Time
|
||||
statusCode int
|
||||
responseLength int
|
||||
id uint64
|
||||
|
@ -57,10 +58,10 @@ func LogFmtWithConfig(config LogFmtConfig) Interceptor {
|
|||
config.Output = DefaultLogFmtConfig.Output
|
||||
}
|
||||
|
||||
encoder := logfmt.NewEncoder(config.Output)
|
||||
encoder := slog.New(slog.NewTextHandler(config.Output, nil))
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
rw := &logFmtResponse{
|
||||
tx := &logFmtResponse{
|
||||
id: nextConnID(),
|
||||
responseLength: 0,
|
||||
ResponseWriter: w,
|
||||
|
@ -68,43 +69,47 @@ func LogFmtWithConfig(config LogFmtConfig) Interceptor {
|
|||
statusCode: 0,
|
||||
}
|
||||
|
||||
next(rw, r)
|
||||
next(tx, r)
|
||||
|
||||
end := time.Now().UTC()
|
||||
|
||||
encoder.EncodeKeyvals(
|
||||
"bytes_in", r.ContentLength,
|
||||
"bytes_out", rw.responseLength,
|
||||
"error", rw.error,
|
||||
"host", r.Host,
|
||||
"id", rw.id,
|
||||
"latency", end.Sub(rw.start).Nanoseconds(),
|
||||
"latency_human", end.Sub(rw.start).String(),
|
||||
"method", r.Method,
|
||||
"path", r.URL.Path,
|
||||
"protocol", r.Proto,
|
||||
"referer", r.Referer(),
|
||||
"remote_ip", r.RemoteAddr,
|
||||
"status", rw.statusCode,
|
||||
"time_rfc3339", rw.start.Format(time.RFC3339),
|
||||
"time_rfc3339_nano", rw.start.Format(time.RFC3339Nano),
|
||||
"time_unix", rw.start.Unix(),
|
||||
"time_unix_nano", rw.start.UnixNano(),
|
||||
"uri", r.RequestURI,
|
||||
"user_agent", r.UserAgent(),
|
||||
)
|
||||
tx.end = time.Now().UTC()
|
||||
payload := []any{
|
||||
slog.Int64("bytes_in", r.ContentLength),
|
||||
slog.Int("bytes_out", tx.responseLength),
|
||||
slog.Any("error", tx.error),
|
||||
slog.String("host", r.Host),
|
||||
slog.Uint64("id", tx.id),
|
||||
slog.Int64("latency", tx.end.Sub(tx.start).Nanoseconds()),
|
||||
slog.String("latency_human", tx.end.Sub(tx.start).String()),
|
||||
slog.String("method", r.Method),
|
||||
slog.String("path", r.URL.Path),
|
||||
slog.String("protocol", r.Proto),
|
||||
slog.String("referer", r.Referer()),
|
||||
slog.String("remote_ip", r.RemoteAddr),
|
||||
slog.Int("status", tx.statusCode),
|
||||
slog.String("time_rfc3339", tx.start.Format(time.RFC3339)),
|
||||
slog.String("time_rfc3339_nano", tx.start.Format(time.RFC3339Nano)),
|
||||
slog.Int64("time_unix", tx.start.Unix()),
|
||||
slog.Int64("time_unix_nano", tx.start.UnixNano()),
|
||||
slog.String("uri", r.RequestURI),
|
||||
slog.String("user_agent", r.UserAgent()),
|
||||
}
|
||||
|
||||
for name, src := range map[string]map[string][]string{
|
||||
"form": r.PostForm,
|
||||
"header": r.Header,
|
||||
"query": r.URL.Query(),
|
||||
} {
|
||||
values := make([]any, 0)
|
||||
|
||||
for k, v := range src {
|
||||
encoder.EncodeKeyval(name+"_"+strings.ReplaceAll(strings.ToLower(k), "-", "_"), v)
|
||||
values = append(values, slog.String(strings.ReplaceAll(strings.ToLower(k), " ", "_"),
|
||||
strings.Join(v, " ")))
|
||||
}
|
||||
|
||||
payload = append(payload, slog.Group(name, values...))
|
||||
}
|
||||
|
||||
encoder.EndRecord()
|
||||
encoder.Log(r.Context(), slog.LevelInfo, "" /* msg */, payload...)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
1
vendor/github.com/go-logfmt/logfmt/.gitignore
generated
vendored
1
vendor/github.com/go-logfmt/logfmt/.gitignore
generated
vendored
|
@ -1 +0,0 @@
|
|||
.vscode/
|
82
vendor/github.com/go-logfmt/logfmt/CHANGELOG.md
generated
vendored
82
vendor/github.com/go-logfmt/logfmt/CHANGELOG.md
generated
vendored
|
@ -1,82 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.6.0] - 2023-01-30
|
||||
|
||||
[0.6.0]: https://github.com/go-logfmt/logfmt/compare/v0.5.1...v0.6.0
|
||||
|
||||
### Added
|
||||
|
||||
- NewDecoderSize by [@alexanderjophus]
|
||||
|
||||
## [0.5.1] - 2021-08-18
|
||||
|
||||
[0.5.1]: https://github.com/go-logfmt/logfmt/compare/v0.5.0...v0.5.1
|
||||
|
||||
### Changed
|
||||
|
||||
- Update the `go.mod` file for Go 1.17 as described in the [Go 1.17 release
|
||||
notes](https://golang.org/doc/go1.17#go-command)
|
||||
|
||||
## [0.5.0] - 2020-01-03
|
||||
|
||||
[0.5.0]: https://github.com/go-logfmt/logfmt/compare/v0.4.0...v0.5.0
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove the dependency on github.com/kr/logfmt by [@ChrisHines]
|
||||
- Move fuzz code to github.com/go-logfmt/fuzzlogfmt by [@ChrisHines]
|
||||
|
||||
## [0.4.0] - 2018-11-21
|
||||
|
||||
[0.4.0]: https://github.com/go-logfmt/logfmt/compare/v0.3.0...v0.4.0
|
||||
|
||||
### Added
|
||||
|
||||
- Go module support by [@ChrisHines]
|
||||
- CHANGELOG by [@ChrisHines]
|
||||
|
||||
### Changed
|
||||
|
||||
- Drop invalid runes from keys instead of returning ErrInvalidKey by [@ChrisHines]
|
||||
- On panic while printing, attempt to print panic value by [@bboreham]
|
||||
|
||||
## [0.3.0] - 2016-11-15
|
||||
|
||||
[0.3.0]: https://github.com/go-logfmt/logfmt/compare/v0.2.0...v0.3.0
|
||||
|
||||
### Added
|
||||
|
||||
- Pool buffers for quoted strings and byte slices by [@nussjustin]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fuzz fix, quote invalid UTF-8 values by [@judwhite]
|
||||
|
||||
## [0.2.0] - 2016-05-08
|
||||
|
||||
[0.2.0]: https://github.com/go-logfmt/logfmt/compare/v0.1.0...v0.2.0
|
||||
|
||||
### Added
|
||||
|
||||
- Encoder.EncodeKeyvals by [@ChrisHines]
|
||||
|
||||
## [0.1.0] - 2016-03-28
|
||||
|
||||
[0.1.0]: https://github.com/go-logfmt/logfmt/commits/v0.1.0
|
||||
|
||||
### Added
|
||||
|
||||
- Encoder by [@ChrisHines]
|
||||
- Decoder by [@ChrisHines]
|
||||
- MarshalKeyvals by [@ChrisHines]
|
||||
|
||||
[@ChrisHines]: https://github.com/ChrisHines
|
||||
[@bboreham]: https://github.com/bboreham
|
||||
[@judwhite]: https://github.com/judwhite
|
||||
[@nussjustin]: https://github.com/nussjustin
|
||||
[@alexanderjophus]: https://github.com/alexanderjophus
|
22
vendor/github.com/go-logfmt/logfmt/LICENSE
generated
vendored
22
vendor/github.com/go-logfmt/logfmt/LICENSE
generated
vendored
|
@ -1,22 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 go-logfmt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
41
vendor/github.com/go-logfmt/logfmt/README.md
generated
vendored
41
vendor/github.com/go-logfmt/logfmt/README.md
generated
vendored
|
@ -1,41 +0,0 @@
|
|||
# logfmt
|
||||
|
||||
[](https://pkg.go.dev/github.com/go-logfmt/logfmt)
|
||||
[](https://goreportcard.com/report/go-logfmt/logfmt)
|
||||
[](https://github.com/go-logfmt/logfmt/actions/workflows/test.yml)
|
||||
[](https://coveralls.io/github/go-logfmt/logfmt?branch=main)
|
||||
|
||||
Package logfmt implements utilities to marshal and unmarshal data in the [logfmt
|
||||
format][fmt]. It provides an API similar to [encoding/json][json] and
|
||||
[encoding/xml][xml].
|
||||
|
||||
[fmt]: https://brandur.org/logfmt
|
||||
[json]: https://pkg.go.dev/encoding/json
|
||||
[xml]: https://pkg.go.dev/encoding/xml
|
||||
|
||||
The logfmt format was first documented by Brandur Leach in [this
|
||||
article][origin]. The format has not been formally standardized. The most
|
||||
authoritative public specification to date has been the documentation of a Go
|
||||
Language [package][parser] written by Blake Mizerany and Keith Rarick.
|
||||
|
||||
[origin]: https://brandur.org/logfmt
|
||||
[parser]: https://pkg.go.dev/github.com/kr/logfmt
|
||||
|
||||
## Goals
|
||||
|
||||
This project attempts to conform as closely as possible to the prior art, while
|
||||
also removing ambiguity where necessary to provide well behaved encoder and
|
||||
decoder implementations.
|
||||
|
||||
## Non-goals
|
||||
|
||||
This project does not attempt to formally standardize the logfmt format. In the
|
||||
event that logfmt is standardized this project would take conforming to the
|
||||
standard as a goal.
|
||||
|
||||
## Versioning
|
||||
|
||||
This project publishes releases according to the Go language guidelines for
|
||||
[developing and publishing modules][pub].
|
||||
|
||||
[pub]: https://go.dev/doc/modules/developing
|
254
vendor/github.com/go-logfmt/logfmt/decode.go
generated
vendored
254
vendor/github.com/go-logfmt/logfmt/decode.go
generated
vendored
|
@ -1,254 +0,0 @@
|
|||
package logfmt
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// A Decoder reads and decodes logfmt records from an input stream.
|
||||
type Decoder struct {
|
||||
pos int
|
||||
key []byte
|
||||
value []byte
|
||||
lineNum int
|
||||
s *bufio.Scanner
|
||||
err error
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
//
|
||||
// The decoder introduces its own buffering and may read data from r beyond
|
||||
// the logfmt records requested.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
dec := &Decoder{
|
||||
s: bufio.NewScanner(r),
|
||||
}
|
||||
return dec
|
||||
}
|
||||
|
||||
// NewDecoderSize returns a new decoder that reads from r.
|
||||
//
|
||||
// The decoder introduces its own buffering and may read data from r beyond
|
||||
// the logfmt records requested.
|
||||
// The size argument specifies the size of the initial buffer that the
|
||||
// Decoder will use to read records from r.
|
||||
// If a log line is longer than the size argument, the Decoder will return
|
||||
// a bufio.ErrTooLong error.
|
||||
func NewDecoderSize(r io.Reader, size int) *Decoder {
|
||||
scanner := bufio.NewScanner(r)
|
||||
scanner.Buffer(make([]byte, 0, size), size)
|
||||
dec := &Decoder{
|
||||
s: scanner,
|
||||
}
|
||||
return dec
|
||||
}
|
||||
|
||||
// ScanRecord advances the Decoder to the next record, which can then be
|
||||
// parsed with the ScanKeyval method. It returns false when decoding stops,
|
||||
// either by reaching the end of the input or an error. After ScanRecord
|
||||
// returns false, the Err method will return any error that occurred during
|
||||
// decoding, except that if it was io.EOF, Err will return nil.
|
||||
func (dec *Decoder) ScanRecord() bool {
|
||||
if dec.err != nil {
|
||||
return false
|
||||
}
|
||||
if !dec.s.Scan() {
|
||||
dec.err = dec.s.Err()
|
||||
return false
|
||||
}
|
||||
dec.lineNum++
|
||||
dec.pos = 0
|
||||
return true
|
||||
}
|
||||
|
||||
// ScanKeyval advances the Decoder to the next key/value pair of the current
|
||||
// record, which can then be retrieved with the Key and Value methods. It
|
||||
// returns false when decoding stops, either by reaching the end of the
|
||||
// current record or an error.
|
||||
func (dec *Decoder) ScanKeyval() bool {
|
||||
dec.key, dec.value = nil, nil
|
||||
if dec.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
line := dec.s.Bytes()
|
||||
|
||||
// garbage
|
||||
for p, c := range line[dec.pos:] {
|
||||
if c > ' ' {
|
||||
dec.pos += p
|
||||
goto key
|
||||
}
|
||||
}
|
||||
dec.pos = len(line)
|
||||
return false
|
||||
|
||||
key:
|
||||
const invalidKeyError = "invalid key"
|
||||
|
||||
start, multibyte := dec.pos, false
|
||||
for p, c := range line[dec.pos:] {
|
||||
switch {
|
||||
case c == '=':
|
||||
dec.pos += p
|
||||
if dec.pos > start {
|
||||
dec.key = line[start:dec.pos]
|
||||
if multibyte && bytes.ContainsRune(dec.key, utf8.RuneError) {
|
||||
dec.syntaxError(invalidKeyError)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if dec.key == nil {
|
||||
dec.unexpectedByte(c)
|
||||
return false
|
||||
}
|
||||
goto equal
|
||||
case c == '"':
|
||||
dec.pos += p
|
||||
dec.unexpectedByte(c)
|
||||
return false
|
||||
case c <= ' ':
|
||||
dec.pos += p
|
||||
if dec.pos > start {
|
||||
dec.key = line[start:dec.pos]
|
||||
if multibyte && bytes.ContainsRune(dec.key, utf8.RuneError) {
|
||||
dec.syntaxError(invalidKeyError)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case c >= utf8.RuneSelf:
|
||||
multibyte = true
|
||||
}
|
||||
}
|
||||
dec.pos = len(line)
|
||||
if dec.pos > start {
|
||||
dec.key = line[start:dec.pos]
|
||||
if multibyte && bytes.ContainsRune(dec.key, utf8.RuneError) {
|
||||
dec.syntaxError(invalidKeyError)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
equal:
|
||||
dec.pos++
|
||||
if dec.pos >= len(line) {
|
||||
return true
|
||||
}
|
||||
switch c := line[dec.pos]; {
|
||||
case c <= ' ':
|
||||
return true
|
||||
case c == '"':
|
||||
goto qvalue
|
||||
}
|
||||
|
||||
// value
|
||||
start = dec.pos
|
||||
for p, c := range line[dec.pos:] {
|
||||
switch {
|
||||
case c == '=' || c == '"':
|
||||
dec.pos += p
|
||||
dec.unexpectedByte(c)
|
||||
return false
|
||||
case c <= ' ':
|
||||
dec.pos += p
|
||||
if dec.pos > start {
|
||||
dec.value = line[start:dec.pos]
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
dec.pos = len(line)
|
||||
if dec.pos > start {
|
||||
dec.value = line[start:dec.pos]
|
||||
}
|
||||
return true
|
||||
|
||||
qvalue:
|
||||
const (
|
||||
untermQuote = "unterminated quoted value"
|
||||
invalidQuote = "invalid quoted value"
|
||||
)
|
||||
|
||||
hasEsc, esc := false, false
|
||||
start = dec.pos
|
||||
for p, c := range line[dec.pos+1:] {
|
||||
switch {
|
||||
case esc:
|
||||
esc = false
|
||||
case c == '\\':
|
||||
hasEsc, esc = true, true
|
||||
case c == '"':
|
||||
dec.pos += p + 2
|
||||
if hasEsc {
|
||||
v, ok := unquoteBytes(line[start:dec.pos])
|
||||
if !ok {
|
||||
dec.syntaxError(invalidQuote)
|
||||
return false
|
||||
}
|
||||
dec.value = v
|
||||
} else {
|
||||
start++
|
||||
end := dec.pos - 1
|
||||
if end > start {
|
||||
dec.value = line[start:end]
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
dec.pos = len(line)
|
||||
dec.syntaxError(untermQuote)
|
||||
return false
|
||||
}
|
||||
|
||||
// Key returns the most recent key found by a call to ScanKeyval. The returned
|
||||
// slice may point to internal buffers and is only valid until the next call
|
||||
// to ScanRecord. It does no allocation.
|
||||
func (dec *Decoder) Key() []byte {
|
||||
return dec.key
|
||||
}
|
||||
|
||||
// Value returns the most recent value found by a call to ScanKeyval. The
|
||||
// returned slice may point to internal buffers and is only valid until the
|
||||
// next call to ScanRecord. It does no allocation when the value has no
|
||||
// escape sequences.
|
||||
func (dec *Decoder) Value() []byte {
|
||||
return dec.value
|
||||
}
|
||||
|
||||
// Err returns the first non-EOF error that was encountered by the Scanner.
|
||||
func (dec *Decoder) Err() error {
|
||||
return dec.err
|
||||
}
|
||||
|
||||
func (dec *Decoder) syntaxError(msg string) {
|
||||
dec.err = &SyntaxError{
|
||||
Msg: msg,
|
||||
Line: dec.lineNum,
|
||||
Pos: dec.pos + 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *Decoder) unexpectedByte(c byte) {
|
||||
dec.err = &SyntaxError{
|
||||
Msg: fmt.Sprintf("unexpected %q", c),
|
||||
Line: dec.lineNum,
|
||||
Pos: dec.pos + 1,
|
||||
}
|
||||
}
|
||||
|
||||
// A SyntaxError represents a syntax error in the logfmt input stream.
|
||||
type SyntaxError struct {
|
||||
Msg string
|
||||
Line int
|
||||
Pos int
|
||||
}
|
||||
|
||||
func (e *SyntaxError) Error() string {
|
||||
return fmt.Sprintf("logfmt syntax error at pos %d on line %d: %s", e.Pos, e.Line, e.Msg)
|
||||
}
|
6
vendor/github.com/go-logfmt/logfmt/doc.go
generated
vendored
6
vendor/github.com/go-logfmt/logfmt/doc.go
generated
vendored
|
@ -1,6 +0,0 @@
|
|||
// Package logfmt implements utilities to marshal and unmarshal data in the
|
||||
// logfmt format. The logfmt format records key/value pairs in a way that
|
||||
// balances readability for humans and simplicity of computer parsing. It is
|
||||
// most commonly used as a more human friendly alternative to JSON for
|
||||
// structured logging.
|
||||
package logfmt
|
322
vendor/github.com/go-logfmt/logfmt/encode.go
generated
vendored
322
vendor/github.com/go-logfmt/logfmt/encode.go
generated
vendored
|
@ -1,322 +0,0 @@
|
|||
package logfmt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// MarshalKeyvals returns the logfmt encoding of keyvals, a variadic sequence
|
||||
// of alternating keys and values.
|
||||
func MarshalKeyvals(keyvals ...interface{}) ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := NewEncoder(buf).EncodeKeyvals(keyvals...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// An Encoder writes logfmt data to an output stream.
|
||||
type Encoder struct {
|
||||
w io.Writer
|
||||
scratch bytes.Buffer
|
||||
needSep bool
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{
|
||||
w: w,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
space = []byte(" ")
|
||||
equals = []byte("=")
|
||||
newline = []byte("\n")
|
||||
null = []byte("null")
|
||||
)
|
||||
|
||||
// EncodeKeyval writes the logfmt encoding of key and value to the stream. A
|
||||
// single space is written before the second and subsequent keys in a record.
|
||||
// Nothing is written if a non-nil error is returned.
|
||||
func (enc *Encoder) EncodeKeyval(key, value interface{}) error {
|
||||
enc.scratch.Reset()
|
||||
if enc.needSep {
|
||||
if _, err := enc.scratch.Write(space); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := writeKey(&enc.scratch, key); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := enc.scratch.Write(equals); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeValue(&enc.scratch, value); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := enc.w.Write(enc.scratch.Bytes())
|
||||
enc.needSep = true
|
||||
return err
|
||||
}
|
||||
|
||||
// EncodeKeyvals writes the logfmt encoding of keyvals to the stream. Keyvals
|
||||
// is a variadic sequence of alternating keys and values. Keys of unsupported
|
||||
// type are skipped along with their corresponding value. Values of
|
||||
// unsupported type or that cause a MarshalerError are replaced by their error
|
||||
// but do not cause EncodeKeyvals to return an error. If a non-nil error is
|
||||
// returned some key/value pairs may not have be written.
|
||||
func (enc *Encoder) EncodeKeyvals(keyvals ...interface{}) error {
|
||||
if len(keyvals) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(keyvals)%2 == 1 {
|
||||
keyvals = append(keyvals, nil)
|
||||
}
|
||||
for i := 0; i < len(keyvals); i += 2 {
|
||||
k, v := keyvals[i], keyvals[i+1]
|
||||
err := enc.EncodeKeyval(k, v)
|
||||
if err == ErrUnsupportedKeyType {
|
||||
continue
|
||||
}
|
||||
if _, ok := err.(*MarshalerError); ok || err == ErrUnsupportedValueType {
|
||||
v = err
|
||||
err = enc.EncodeKeyval(k, v)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalerError represents an error encountered while marshaling a value.
|
||||
type MarshalerError struct {
|
||||
Type reflect.Type
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *MarshalerError) Error() string {
|
||||
return "error marshaling value of type " + e.Type.String() + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
// ErrNilKey is returned by Marshal functions and Encoder methods if a key is
|
||||
// a nil interface or pointer value.
|
||||
var ErrNilKey = errors.New("nil key")
|
||||
|
||||
// ErrInvalidKey is returned by Marshal functions and Encoder methods if, after
|
||||
// dropping invalid runes, a key is empty.
|
||||
var ErrInvalidKey = errors.New("invalid key")
|
||||
|
||||
// ErrUnsupportedKeyType is returned by Encoder methods if a key has an
|
||||
// unsupported type.
|
||||
var ErrUnsupportedKeyType = errors.New("unsupported key type")
|
||||
|
||||
// ErrUnsupportedValueType is returned by Encoder methods if a value has an
|
||||
// unsupported type.
|
||||
var ErrUnsupportedValueType = errors.New("unsupported value type")
|
||||
|
||||
func writeKey(w io.Writer, key interface{}) error {
|
||||
if key == nil {
|
||||
return ErrNilKey
|
||||
}
|
||||
|
||||
switch k := key.(type) {
|
||||
case string:
|
||||
return writeStringKey(w, k)
|
||||
case []byte:
|
||||
if k == nil {
|
||||
return ErrNilKey
|
||||
}
|
||||
return writeBytesKey(w, k)
|
||||
case encoding.TextMarshaler:
|
||||
kb, err := safeMarshal(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if kb == nil {
|
||||
return ErrNilKey
|
||||
}
|
||||
return writeBytesKey(w, kb)
|
||||
case fmt.Stringer:
|
||||
ks, ok := safeString(k)
|
||||
if !ok {
|
||||
return ErrNilKey
|
||||
}
|
||||
return writeStringKey(w, ks)
|
||||
default:
|
||||
rkey := reflect.ValueOf(key)
|
||||
switch rkey.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Struct:
|
||||
return ErrUnsupportedKeyType
|
||||
case reflect.Ptr:
|
||||
if rkey.IsNil() {
|
||||
return ErrNilKey
|
||||
}
|
||||
return writeKey(w, rkey.Elem().Interface())
|
||||
}
|
||||
return writeStringKey(w, fmt.Sprint(k))
|
||||
}
|
||||
}
|
||||
|
||||
// keyRuneFilter returns r for all valid key runes, and -1 for all invalid key
|
||||
// runes. When used as the mapping function for strings.Map and bytes.Map
|
||||
// functions it causes them to remove invalid key runes from strings or byte
|
||||
// slices respectively.
|
||||
func keyRuneFilter(r rune) rune {
|
||||
if r <= ' ' || r == '=' || r == '"' || r == utf8.RuneError {
|
||||
return -1
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func writeStringKey(w io.Writer, key string) error {
|
||||
k := strings.Map(keyRuneFilter, key)
|
||||
if k == "" {
|
||||
return ErrInvalidKey
|
||||
}
|
||||
_, err := io.WriteString(w, k)
|
||||
return err
|
||||
}
|
||||
|
||||
func writeBytesKey(w io.Writer, key []byte) error {
|
||||
k := bytes.Map(keyRuneFilter, key)
|
||||
if len(k) == 0 {
|
||||
return ErrInvalidKey
|
||||
}
|
||||
_, err := w.Write(k)
|
||||
return err
|
||||
}
|
||||
|
||||
func writeValue(w io.Writer, value interface{}) error {
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
return writeBytesValue(w, null)
|
||||
case string:
|
||||
return writeStringValue(w, v, true)
|
||||
case []byte:
|
||||
return writeBytesValue(w, v)
|
||||
case encoding.TextMarshaler:
|
||||
vb, err := safeMarshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if vb == nil {
|
||||
vb = null
|
||||
}
|
||||
return writeBytesValue(w, vb)
|
||||
case error:
|
||||
se, ok := safeError(v)
|
||||
return writeStringValue(w, se, ok)
|
||||
case fmt.Stringer:
|
||||
ss, ok := safeString(v)
|
||||
return writeStringValue(w, ss, ok)
|
||||
default:
|
||||
rvalue := reflect.ValueOf(value)
|
||||
switch rvalue.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Struct:
|
||||
return ErrUnsupportedValueType
|
||||
case reflect.Ptr:
|
||||
if rvalue.IsNil() {
|
||||
return writeBytesValue(w, null)
|
||||
}
|
||||
return writeValue(w, rvalue.Elem().Interface())
|
||||
}
|
||||
return writeStringValue(w, fmt.Sprint(v), true)
|
||||
}
|
||||
}
|
||||
|
||||
func needsQuotedValueRune(r rune) bool {
|
||||
return r <= ' ' || r == '=' || r == '"' || r == utf8.RuneError
|
||||
}
|
||||
|
||||
func writeStringValue(w io.Writer, value string, ok bool) error {
|
||||
var err error
|
||||
if ok && value == "null" {
|
||||
_, err = io.WriteString(w, `"null"`)
|
||||
} else if strings.IndexFunc(value, needsQuotedValueRune) != -1 {
|
||||
_, err = writeQuotedString(w, value)
|
||||
} else {
|
||||
_, err = io.WriteString(w, value)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func writeBytesValue(w io.Writer, value []byte) error {
|
||||
var err error
|
||||
if bytes.IndexFunc(value, needsQuotedValueRune) != -1 {
|
||||
_, err = writeQuotedBytes(w, value)
|
||||
} else {
|
||||
_, err = w.Write(value)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// EndRecord writes a newline character to the stream and resets the encoder
|
||||
// to the beginning of a new record.
|
||||
func (enc *Encoder) EndRecord() error {
|
||||
_, err := enc.w.Write(newline)
|
||||
if err == nil {
|
||||
enc.needSep = false
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Reset resets the encoder to the beginning of a new record.
|
||||
func (enc *Encoder) Reset() {
|
||||
enc.needSep = false
|
||||
}
|
||||
|
||||
func safeError(err error) (s string, ok bool) {
|
||||
defer func() {
|
||||
if panicVal := recover(); panicVal != nil {
|
||||
if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
s, ok = "null", false
|
||||
} else {
|
||||
s, ok = fmt.Sprintf("PANIC:%v", panicVal), false
|
||||
}
|
||||
}
|
||||
}()
|
||||
s, ok = err.Error(), true
|
||||
return
|
||||
}
|
||||
|
||||
func safeString(str fmt.Stringer) (s string, ok bool) {
|
||||
defer func() {
|
||||
if panicVal := recover(); panicVal != nil {
|
||||
if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
s, ok = "null", false
|
||||
} else {
|
||||
s, ok = fmt.Sprintf("PANIC:%v", panicVal), true
|
||||
}
|
||||
}
|
||||
}()
|
||||
s, ok = str.String(), true
|
||||
return
|
||||
}
|
||||
|
||||
func safeMarshal(tm encoding.TextMarshaler) (b []byte, err error) {
|
||||
defer func() {
|
||||
if panicVal := recover(); panicVal != nil {
|
||||
if v := reflect.ValueOf(tm); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
b, err = nil, nil
|
||||
} else {
|
||||
b, err = nil, fmt.Errorf("panic when marshalling: %s", panicVal)
|
||||
}
|
||||
}
|
||||
}()
|
||||
b, err = tm.MarshalText()
|
||||
if err != nil {
|
||||
return nil, &MarshalerError{
|
||||
Type: reflect.TypeOf(tm),
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
277
vendor/github.com/go-logfmt/logfmt/jsonstring.go
generated
vendored
277
vendor/github.com/go-logfmt/logfmt/jsonstring.go
generated
vendored
|
@ -1,277 +0,0 @@
|
|||
package logfmt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
"unicode"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Taken from Go's encoding/json and modified for use here.
|
||||
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
var hex = "0123456789abcdef"
|
||||
|
||||
var bufferPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &bytes.Buffer{}
|
||||
},
|
||||
}
|
||||
|
||||
func getBuffer() *bytes.Buffer {
|
||||
return bufferPool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func poolBuffer(buf *bytes.Buffer) {
|
||||
buf.Reset()
|
||||
bufferPool.Put(buf)
|
||||
}
|
||||
|
||||
// NOTE: keep in sync with writeQuotedBytes below.
|
||||
func writeQuotedString(w io.Writer, s string) (int, error) {
|
||||
buf := getBuffer()
|
||||
buf.WriteByte('"')
|
||||
start := 0
|
||||
for i := 0; i < len(s); {
|
||||
if b := s[i]; b < utf8.RuneSelf {
|
||||
if 0x20 <= b && b != '\\' && b != '"' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if start < i {
|
||||
buf.WriteString(s[start:i])
|
||||
}
|
||||
switch b {
|
||||
case '\\', '"':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte(b)
|
||||
case '\n':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('n')
|
||||
case '\r':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('r')
|
||||
case '\t':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('t')
|
||||
default:
|
||||
// This encodes bytes < 0x20 except for \n, \r, and \t.
|
||||
buf.WriteString(`\u00`)
|
||||
buf.WriteByte(hex[b>>4])
|
||||
buf.WriteByte(hex[b&0xF])
|
||||
}
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
c, size := utf8.DecodeRuneInString(s[i:])
|
||||
if c == utf8.RuneError {
|
||||
if start < i {
|
||||
buf.WriteString(s[start:i])
|
||||
}
|
||||
buf.WriteString(`\ufffd`)
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
i += size
|
||||
}
|
||||
if start < len(s) {
|
||||
buf.WriteString(s[start:])
|
||||
}
|
||||
buf.WriteByte('"')
|
||||
n, err := w.Write(buf.Bytes())
|
||||
poolBuffer(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NOTE: keep in sync with writeQuoteString above.
|
||||
func writeQuotedBytes(w io.Writer, s []byte) (int, error) {
|
||||
buf := getBuffer()
|
||||
buf.WriteByte('"')
|
||||
start := 0
|
||||
for i := 0; i < len(s); {
|
||||
if b := s[i]; b < utf8.RuneSelf {
|
||||
if 0x20 <= b && b != '\\' && b != '"' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if start < i {
|
||||
buf.Write(s[start:i])
|
||||
}
|
||||
switch b {
|
||||
case '\\', '"':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte(b)
|
||||
case '\n':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('n')
|
||||
case '\r':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('r')
|
||||
case '\t':
|
||||
buf.WriteByte('\\')
|
||||
buf.WriteByte('t')
|
||||
default:
|
||||
// This encodes bytes < 0x20 except for \n, \r, and \t.
|
||||
buf.WriteString(`\u00`)
|
||||
buf.WriteByte(hex[b>>4])
|
||||
buf.WriteByte(hex[b&0xF])
|
||||
}
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
c, size := utf8.DecodeRune(s[i:])
|
||||
if c == utf8.RuneError {
|
||||
if start < i {
|
||||
buf.Write(s[start:i])
|
||||
}
|
||||
buf.WriteString(`\ufffd`)
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
i += size
|
||||
}
|
||||
if start < len(s) {
|
||||
buf.Write(s[start:])
|
||||
}
|
||||
buf.WriteByte('"')
|
||||
n, err := w.Write(buf.Bytes())
|
||||
poolBuffer(buf)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// getu4 decodes \uXXXX from the beginning of s, returning the hex value,
|
||||
// or it returns -1.
|
||||
func getu4(s []byte) rune {
|
||||
if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
|
||||
return -1
|
||||
}
|
||||
r, err := strconv.ParseUint(string(s[2:6]), 16, 64)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return rune(r)
|
||||
}
|
||||
|
||||
func unquoteBytes(s []byte) (t []byte, ok bool) {
|
||||
if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
|
||||
return
|
||||
}
|
||||
s = s[1 : len(s)-1]
|
||||
|
||||
// Check for unusual characters. If there are none,
|
||||
// then no unquoting is needed, so return a slice of the
|
||||
// original bytes.
|
||||
r := 0
|
||||
for r < len(s) {
|
||||
c := s[r]
|
||||
if c == '\\' || c == '"' || c < ' ' {
|
||||
break
|
||||
}
|
||||
if c < utf8.RuneSelf {
|
||||
r++
|
||||
continue
|
||||
}
|
||||
rr, size := utf8.DecodeRune(s[r:])
|
||||
if rr == utf8.RuneError {
|
||||
break
|
||||
}
|
||||
r += size
|
||||
}
|
||||
if r == len(s) {
|
||||
return s, true
|
||||
}
|
||||
|
||||
b := make([]byte, len(s)+2*utf8.UTFMax)
|
||||
w := copy(b, s[0:r])
|
||||
for r < len(s) {
|
||||
// Out of room? Can only happen if s is full of
|
||||
// malformed UTF-8 and we're replacing each
|
||||
// byte with RuneError.
|
||||
if w >= len(b)-2*utf8.UTFMax {
|
||||
nb := make([]byte, (len(b)+utf8.UTFMax)*2)
|
||||
copy(nb, b[0:w])
|
||||
b = nb
|
||||
}
|
||||
switch c := s[r]; {
|
||||
case c == '\\':
|
||||
r++
|
||||
if r >= len(s) {
|
||||
return
|
||||
}
|
||||
switch s[r] {
|
||||
default:
|
||||
return
|
||||
case '"', '\\', '/', '\'':
|
||||
b[w] = s[r]
|
||||
r++
|
||||
w++
|
||||
case 'b':
|
||||
b[w] = '\b'
|
||||
r++
|
||||
w++
|
||||
case 'f':
|
||||
b[w] = '\f'
|
||||
r++
|
||||
w++
|
||||
case 'n':
|
||||
b[w] = '\n'
|
||||
r++
|
||||
w++
|
||||
case 'r':
|
||||
b[w] = '\r'
|
||||
r++
|
||||
w++
|
||||
case 't':
|
||||
b[w] = '\t'
|
||||
r++
|
||||
w++
|
||||
case 'u':
|
||||
r--
|
||||
rr := getu4(s[r:])
|
||||
if rr < 0 {
|
||||
return
|
||||
}
|
||||
r += 6
|
||||
if utf16.IsSurrogate(rr) {
|
||||
rr1 := getu4(s[r:])
|
||||
if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar {
|
||||
// A valid pair; consume.
|
||||
r += 6
|
||||
w += utf8.EncodeRune(b[w:], dec)
|
||||
break
|
||||
}
|
||||
// Invalid surrogate; fall back to replacement rune.
|
||||
rr = unicode.ReplacementChar
|
||||
}
|
||||
w += utf8.EncodeRune(b[w:], rr)
|
||||
}
|
||||
|
||||
// Quote, control characters are invalid.
|
||||
case c == '"', c < ' ':
|
||||
return
|
||||
|
||||
// ASCII
|
||||
case c < utf8.RuneSelf:
|
||||
b[w] = c
|
||||
r++
|
||||
w++
|
||||
|
||||
// Coerce to well-formed UTF-8.
|
||||
default:
|
||||
rr, size := utf8.DecodeRune(s[r:])
|
||||
r += size
|
||||
w += utf8.EncodeRune(b[w:], rr)
|
||||
}
|
||||
}
|
||||
return b[0:w], true
|
||||
}
|
102
vendor/golang.org/x/exp/slog/attr.go
generated
vendored
Normal file
102
vendor/golang.org/x/exp/slog/attr.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// An Attr is a key-value pair.
|
||||
type Attr struct {
|
||||
Key string
|
||||
Value Value
|
||||
}
|
||||
|
||||
// String returns an Attr for a string value.
|
||||
func String(key, value string) Attr {
|
||||
return Attr{key, StringValue(value)}
|
||||
}
|
||||
|
||||
// Int64 returns an Attr for an int64.
|
||||
func Int64(key string, value int64) Attr {
|
||||
return Attr{key, Int64Value(value)}
|
||||
}
|
||||
|
||||
// Int converts an int to an int64 and returns
|
||||
// an Attr with that value.
|
||||
func Int(key string, value int) Attr {
|
||||
return Int64(key, int64(value))
|
||||
}
|
||||
|
||||
// Uint64 returns an Attr for a uint64.
|
||||
func Uint64(key string, v uint64) Attr {
|
||||
return Attr{key, Uint64Value(v)}
|
||||
}
|
||||
|
||||
// Float64 returns an Attr for a floating-point number.
|
||||
func Float64(key string, v float64) Attr {
|
||||
return Attr{key, Float64Value(v)}
|
||||
}
|
||||
|
||||
// Bool returns an Attr for a bool.
|
||||
func Bool(key string, v bool) Attr {
|
||||
return Attr{key, BoolValue(v)}
|
||||
}
|
||||
|
||||
// Time returns an Attr for a time.Time.
|
||||
// It discards the monotonic portion.
|
||||
func Time(key string, v time.Time) Attr {
|
||||
return Attr{key, TimeValue(v)}
|
||||
}
|
||||
|
||||
// Duration returns an Attr for a time.Duration.
|
||||
func Duration(key string, v time.Duration) Attr {
|
||||
return Attr{key, DurationValue(v)}
|
||||
}
|
||||
|
||||
// Group returns an Attr for a Group Value.
|
||||
// The first argument is the key; the remaining arguments
|
||||
// are converted to Attrs as in [Logger.Log].
|
||||
//
|
||||
// Use Group to collect several key-value pairs under a single
|
||||
// key on a log line, or as the result of LogValue
|
||||
// in order to log a single value as multiple Attrs.
|
||||
func Group(key string, args ...any) Attr {
|
||||
return Attr{key, GroupValue(argsToAttrSlice(args)...)}
|
||||
}
|
||||
|
||||
func argsToAttrSlice(args []any) []Attr {
|
||||
var (
|
||||
attr Attr
|
||||
attrs []Attr
|
||||
)
|
||||
for len(args) > 0 {
|
||||
attr, args = argsToAttr(args)
|
||||
attrs = append(attrs, attr)
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
// Any returns an Attr for the supplied value.
|
||||
// See [Value.AnyValue] for how values are treated.
|
||||
func Any(key string, value any) Attr {
|
||||
return Attr{key, AnyValue(value)}
|
||||
}
|
||||
|
||||
// Equal reports whether a and b have equal keys and values.
|
||||
func (a Attr) Equal(b Attr) bool {
|
||||
return a.Key == b.Key && a.Value.Equal(b.Value)
|
||||
}
|
||||
|
||||
func (a Attr) String() string {
|
||||
return fmt.Sprintf("%s=%s", a.Key, a.Value)
|
||||
}
|
||||
|
||||
// isEmpty reports whether a has an empty key and a nil value.
|
||||
// That can be written as Attr{} or Any("", nil).
|
||||
func (a Attr) isEmpty() bool {
|
||||
return a.Key == "" && a.Value.num == 0 && a.Value.any == nil
|
||||
}
|
316
vendor/golang.org/x/exp/slog/doc.go
generated
vendored
Normal file
316
vendor/golang.org/x/exp/slog/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,316 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package slog provides structured logging,
|
||||
in which log records include a message,
|
||||
a severity level, and various other attributes
|
||||
expressed as key-value pairs.
|
||||
|
||||
It defines a type, [Logger],
|
||||
which provides several methods (such as [Logger.Info] and [Logger.Error])
|
||||
for reporting events of interest.
|
||||
|
||||
Each Logger is associated with a [Handler].
|
||||
A Logger output method creates a [Record] from the method arguments
|
||||
and passes it to the Handler, which decides how to handle it.
|
||||
There is a default Logger accessible through top-level functions
|
||||
(such as [Info] and [Error]) that call the corresponding Logger methods.
|
||||
|
||||
A log record consists of a time, a level, a message, and a set of key-value
|
||||
pairs, where the keys are strings and the values may be of any type.
|
||||
As an example,
|
||||
|
||||
slog.Info("hello", "count", 3)
|
||||
|
||||
creates a record containing the time of the call,
|
||||
a level of Info, the message "hello", and a single
|
||||
pair with key "count" and value 3.
|
||||
|
||||
The [Info] top-level function calls the [Logger.Info] method on the default Logger.
|
||||
In addition to [Logger.Info], there are methods for Debug, Warn and Error levels.
|
||||
Besides these convenience methods for common levels,
|
||||
there is also a [Logger.Log] method which takes the level as an argument.
|
||||
Each of these methods has a corresponding top-level function that uses the
|
||||
default logger.
|
||||
|
||||
The default handler formats the log record's message, time, level, and attributes
|
||||
as a string and passes it to the [log] package.
|
||||
|
||||
2022/11/08 15:28:26 INFO hello count=3
|
||||
|
||||
For more control over the output format, create a logger with a different handler.
|
||||
This statement uses [New] to create a new logger with a TextHandler
|
||||
that writes structured records in text form to standard error:
|
||||
|
||||
logger := slog.New(slog.NewTextHandler(os.Stderr, nil))
|
||||
|
||||
[TextHandler] output is a sequence of key=value pairs, easily and unambiguously
|
||||
parsed by machine. This statement:
|
||||
|
||||
logger.Info("hello", "count", 3)
|
||||
|
||||
produces this output:
|
||||
|
||||
time=2022-11-08T15:28:26.000-05:00 level=INFO msg=hello count=3
|
||||
|
||||
The package also provides [JSONHandler], whose output is line-delimited JSON:
|
||||
|
||||
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||
logger.Info("hello", "count", 3)
|
||||
|
||||
produces this output:
|
||||
|
||||
{"time":"2022-11-08T15:28:26.000000000-05:00","level":"INFO","msg":"hello","count":3}
|
||||
|
||||
Both [TextHandler] and [JSONHandler] can be configured with [HandlerOptions].
|
||||
There are options for setting the minimum level (see Levels, below),
|
||||
displaying the source file and line of the log call, and
|
||||
modifying attributes before they are logged.
|
||||
|
||||
Setting a logger as the default with
|
||||
|
||||
slog.SetDefault(logger)
|
||||
|
||||
will cause the top-level functions like [Info] to use it.
|
||||
[SetDefault] also updates the default logger used by the [log] package,
|
||||
so that existing applications that use [log.Printf] and related functions
|
||||
will send log records to the logger's handler without needing to be rewritten.
|
||||
|
||||
Some attributes are common to many log calls.
|
||||
For example, you may wish to include the URL or trace identifier of a server request
|
||||
with all log events arising from the request.
|
||||
Rather than repeat the attribute with every log call, you can use [Logger.With]
|
||||
to construct a new Logger containing the attributes:
|
||||
|
||||
logger2 := logger.With("url", r.URL)
|
||||
|
||||
The arguments to With are the same key-value pairs used in [Logger.Info].
|
||||
The result is a new Logger with the same handler as the original, but additional
|
||||
attributes that will appear in the output of every call.
|
||||
|
||||
# Levels
|
||||
|
||||
A [Level] is an integer representing the importance or severity of a log event.
|
||||
The higher the level, the more severe the event.
|
||||
This package defines constants for the most common levels,
|
||||
but any int can be used as a level.
|
||||
|
||||
In an application, you may wish to log messages only at a certain level or greater.
|
||||
One common configuration is to log messages at Info or higher levels,
|
||||
suppressing debug logging until it is needed.
|
||||
The built-in handlers can be configured with the minimum level to output by
|
||||
setting [HandlerOptions.Level].
|
||||
The program's `main` function typically does this.
|
||||
The default value is LevelInfo.
|
||||
|
||||
Setting the [HandlerOptions.Level] field to a [Level] value
|
||||
fixes the handler's minimum level throughout its lifetime.
|
||||
Setting it to a [LevelVar] allows the level to be varied dynamically.
|
||||
A LevelVar holds a Level and is safe to read or write from multiple
|
||||
goroutines.
|
||||
To vary the level dynamically for an entire program, first initialize
|
||||
a global LevelVar:
|
||||
|
||||
var programLevel = new(slog.LevelVar) // Info by default
|
||||
|
||||
Then use the LevelVar to construct a handler, and make it the default:
|
||||
|
||||
h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel})
|
||||
slog.SetDefault(slog.New(h))
|
||||
|
||||
Now the program can change its logging level with a single statement:
|
||||
|
||||
programLevel.Set(slog.LevelDebug)
|
||||
|
||||
# Groups
|
||||
|
||||
Attributes can be collected into groups.
|
||||
A group has a name that is used to qualify the names of its attributes.
|
||||
How this qualification is displayed depends on the handler.
|
||||
[TextHandler] separates the group and attribute names with a dot.
|
||||
[JSONHandler] treats each group as a separate JSON object, with the group name as the key.
|
||||
|
||||
Use [Group] to create a Group attribute from a name and a list of key-value pairs:
|
||||
|
||||
slog.Group("request",
|
||||
"method", r.Method,
|
||||
"url", r.URL)
|
||||
|
||||
TextHandler would display this group as
|
||||
|
||||
request.method=GET request.url=http://example.com
|
||||
|
||||
JSONHandler would display it as
|
||||
|
||||
"request":{"method":"GET","url":"http://example.com"}
|
||||
|
||||
Use [Logger.WithGroup] to qualify all of a Logger's output
|
||||
with a group name. Calling WithGroup on a Logger results in a
|
||||
new Logger with the same Handler as the original, but with all
|
||||
its attributes qualified by the group name.
|
||||
|
||||
This can help prevent duplicate attribute keys in large systems,
|
||||
where subsystems might use the same keys.
|
||||
Pass each subsystem a different Logger with its own group name so that
|
||||
potential duplicates are qualified:
|
||||
|
||||
logger := slog.Default().With("id", systemID)
|
||||
parserLogger := logger.WithGroup("parser")
|
||||
parseInput(input, parserLogger)
|
||||
|
||||
When parseInput logs with parserLogger, its keys will be qualified with "parser",
|
||||
so even if it uses the common key "id", the log line will have distinct keys.
|
||||
|
||||
# Contexts
|
||||
|
||||
Some handlers may wish to include information from the [context.Context] that is
|
||||
available at the call site. One example of such information
|
||||
is the identifier for the current span when tracing is enabled.
|
||||
|
||||
The [Logger.Log] and [Logger.LogAttrs] methods take a context as a first
|
||||
argument, as do their corresponding top-level functions.
|
||||
|
||||
Although the convenience methods on Logger (Info and so on) and the
|
||||
corresponding top-level functions do not take a context, the alternatives ending
|
||||
in "Ctx" do. For example,
|
||||
|
||||
slog.InfoCtx(ctx, "message")
|
||||
|
||||
It is recommended to pass a context to an output method if one is available.
|
||||
|
||||
# Attrs and Values
|
||||
|
||||
An [Attr] is a key-value pair. The Logger output methods accept Attrs as well as
|
||||
alternating keys and values. The statement
|
||||
|
||||
slog.Info("hello", slog.Int("count", 3))
|
||||
|
||||
behaves the same as
|
||||
|
||||
slog.Info("hello", "count", 3)
|
||||
|
||||
There are convenience constructors for [Attr] such as [Int], [String], and [Bool]
|
||||
for common types, as well as the function [Any] for constructing Attrs of any
|
||||
type.
|
||||
|
||||
The value part of an Attr is a type called [Value].
|
||||
Like an [any], a Value can hold any Go value,
|
||||
but it can represent typical values, including all numbers and strings,
|
||||
without an allocation.
|
||||
|
||||
For the most efficient log output, use [Logger.LogAttrs].
|
||||
It is similar to [Logger.Log] but accepts only Attrs, not alternating
|
||||
keys and values; this allows it, too, to avoid allocation.
|
||||
|
||||
The call
|
||||
|
||||
logger.LogAttrs(nil, slog.LevelInfo, "hello", slog.Int("count", 3))
|
||||
|
||||
is the most efficient way to achieve the same output as
|
||||
|
||||
slog.Info("hello", "count", 3)
|
||||
|
||||
# Customizing a type's logging behavior
|
||||
|
||||
If a type implements the [LogValuer] interface, the [Value] returned from its LogValue
|
||||
method is used for logging. You can use this to control how values of the type
|
||||
appear in logs. For example, you can redact secret information like passwords,
|
||||
or gather a struct's fields in a Group. See the examples under [LogValuer] for
|
||||
details.
|
||||
|
||||
A LogValue method may return a Value that itself implements [LogValuer]. The [Value.Resolve]
|
||||
method handles these cases carefully, avoiding infinite loops and unbounded recursion.
|
||||
Handler authors and others may wish to use Value.Resolve instead of calling LogValue directly.
|
||||
|
||||
# Wrapping output methods
|
||||
|
||||
The logger functions use reflection over the call stack to find the file name
|
||||
and line number of the logging call within the application. This can produce
|
||||
incorrect source information for functions that wrap slog. For instance, if you
|
||||
define this function in file mylog.go:
|
||||
|
||||
func Infof(format string, args ...any) {
|
||||
slog.Default().Info(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
and you call it like this in main.go:
|
||||
|
||||
Infof(slog.Default(), "hello, %s", "world")
|
||||
|
||||
then slog will report the source file as mylog.go, not main.go.
|
||||
|
||||
A correct implementation of Infof will obtain the source location
|
||||
(pc) and pass it to NewRecord.
|
||||
The Infof function in the package-level example called "wrapping"
|
||||
demonstrates how to do this.
|
||||
|
||||
# Working with Records
|
||||
|
||||
Sometimes a Handler will need to modify a Record
|
||||
before passing it on to another Handler or backend.
|
||||
A Record contains a mixture of simple public fields (e.g. Time, Level, Message)
|
||||
and hidden fields that refer to state (such as attributes) indirectly. This
|
||||
means that modifying a simple copy of a Record (e.g. by calling
|
||||
[Record.Add] or [Record.AddAttrs] to add attributes)
|
||||
may have unexpected effects on the original.
|
||||
Before modifying a Record, use [Clone] to
|
||||
create a copy that shares no state with the original,
|
||||
or create a new Record with [NewRecord]
|
||||
and build up its Attrs by traversing the old ones with [Record.Attrs].
|
||||
|
||||
# Performance considerations
|
||||
|
||||
If profiling your application demonstrates that logging is taking significant time,
|
||||
the following suggestions may help.
|
||||
|
||||
If many log lines have a common attribute, use [Logger.With] to create a Logger with
|
||||
that attribute. The built-in handlers will format that attribute only once, at the
|
||||
call to [Logger.With]. The [Handler] interface is designed to allow that optimization,
|
||||
and a well-written Handler should take advantage of it.
|
||||
|
||||
The arguments to a log call are always evaluated, even if the log event is discarded.
|
||||
If possible, defer computation so that it happens only if the value is actually logged.
|
||||
For example, consider the call
|
||||
|
||||
slog.Info("starting request", "url", r.URL.String()) // may compute String unnecessarily
|
||||
|
||||
The URL.String method will be called even if the logger discards Info-level events.
|
||||
Instead, pass the URL directly:
|
||||
|
||||
slog.Info("starting request", "url", &r.URL) // calls URL.String only if needed
|
||||
|
||||
The built-in [TextHandler] will call its String method, but only
|
||||
if the log event is enabled.
|
||||
Avoiding the call to String also preserves the structure of the underlying value.
|
||||
For example [JSONHandler] emits the components of the parsed URL as a JSON object.
|
||||
If you want to avoid eagerly paying the cost of the String call
|
||||
without causing the handler to potentially inspect the structure of the value,
|
||||
wrap the value in a fmt.Stringer implementation that hides its Marshal methods.
|
||||
|
||||
You can also use the [LogValuer] interface to avoid unnecessary work in disabled log
|
||||
calls. Say you need to log some expensive value:
|
||||
|
||||
slog.Debug("frobbing", "value", computeExpensiveValue(arg))
|
||||
|
||||
Even if this line is disabled, computeExpensiveValue will be called.
|
||||
To avoid that, define a type implementing LogValuer:
|
||||
|
||||
type expensive struct { arg int }
|
||||
|
||||
func (e expensive) LogValue() slog.Value {
|
||||
return slog.AnyValue(computeExpensiveValue(e.arg))
|
||||
}
|
||||
|
||||
Then use a value of that type in log calls:
|
||||
|
||||
slog.Debug("frobbing", "value", expensive{arg})
|
||||
|
||||
Now computeExpensiveValue will only be called when the line is enabled.
|
||||
|
||||
The built-in handlers acquire a lock before calling [io.Writer.Write]
|
||||
to ensure that each record is written in one piece. User-defined
|
||||
handlers are responsible for their own locking.
|
||||
*/
|
||||
package slog
|
559
vendor/golang.org/x/exp/slog/handler.go
generated
vendored
Normal file
559
vendor/golang.org/x/exp/slog/handler.go
generated
vendored
Normal file
|
@ -0,0 +1,559 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/exp/slog/internal/buffer"
|
||||
)
|
||||
|
||||
// A Handler handles log records produced by a Logger..
|
||||
//
|
||||
// A typical handler may print log records to standard error,
|
||||
// or write them to a file or database, or perhaps augment them
|
||||
// with additional attributes and pass them on to another handler.
|
||||
//
|
||||
// Any of the Handler's methods may be called concurrently with itself
|
||||
// or with other methods. It is the responsibility of the Handler to
|
||||
// manage this concurrency.
|
||||
//
|
||||
// Users of the slog package should not invoke Handler methods directly.
|
||||
// They should use the methods of [Logger] instead.
|
||||
type Handler interface {
|
||||
// Enabled reports whether the handler handles records at the given level.
|
||||
// The handler ignores records whose level is lower.
|
||||
// It is called early, before any arguments are processed,
|
||||
// to save effort if the log event should be discarded.
|
||||
// If called from a Logger method, the first argument is the context
|
||||
// passed to that method, or context.Background() if nil was passed
|
||||
// or the method does not take a context.
|
||||
// The context is passed so Enabled can use its values
|
||||
// to make a decision.
|
||||
Enabled(context.Context, Level) bool
|
||||
|
||||
// Handle handles the Record.
|
||||
// It will only be called when Enabled returns true.
|
||||
// The Context argument is as for Enabled.
|
||||
// It is present solely to provide Handlers access to the context's values.
|
||||
// Canceling the context should not affect record processing.
|
||||
// (Among other things, log messages may be necessary to debug a
|
||||
// cancellation-related problem.)
|
||||
//
|
||||
// Handle methods that produce output should observe the following rules:
|
||||
// - If r.Time is the zero time, ignore the time.
|
||||
// - If r.PC is zero, ignore it.
|
||||
// - Attr's values should be resolved.
|
||||
// - If an Attr's key and value are both the zero value, ignore the Attr.
|
||||
// This can be tested with attr.Equal(Attr{}).
|
||||
// - If a group's key is empty, inline the group's Attrs.
|
||||
// - If a group has no Attrs (even if it has a non-empty key),
|
||||
// ignore it.
|
||||
Handle(context.Context, Record) error
|
||||
|
||||
// WithAttrs returns a new Handler whose attributes consist of
|
||||
// both the receiver's attributes and the arguments.
|
||||
// The Handler owns the slice: it may retain, modify or discard it.
|
||||
WithAttrs(attrs []Attr) Handler
|
||||
|
||||
// WithGroup returns a new Handler with the given group appended to
|
||||
// the receiver's existing groups.
|
||||
// The keys of all subsequent attributes, whether added by With or in a
|
||||
// Record, should be qualified by the sequence of group names.
|
||||
//
|
||||
// How this qualification happens is up to the Handler, so long as
|
||||
// this Handler's attribute keys differ from those of another Handler
|
||||
// with a different sequence of group names.
|
||||
//
|
||||
// A Handler should treat WithGroup as starting a Group of Attrs that ends
|
||||
// at the end of the log event. That is,
|
||||
//
|
||||
// logger.WithGroup("s").LogAttrs(level, msg, slog.Int("a", 1), slog.Int("b", 2))
|
||||
//
|
||||
// should behave like
|
||||
//
|
||||
// logger.LogAttrs(level, msg, slog.Group("s", slog.Int("a", 1), slog.Int("b", 2)))
|
||||
//
|
||||
// If the name is empty, WithGroup returns the receiver.
|
||||
WithGroup(name string) Handler
|
||||
}
|
||||
|
||||
type defaultHandler struct {
|
||||
ch *commonHandler
|
||||
// log.Output, except for testing
|
||||
output func(calldepth int, message string) error
|
||||
}
|
||||
|
||||
func newDefaultHandler(output func(int, string) error) *defaultHandler {
|
||||
return &defaultHandler{
|
||||
ch: &commonHandler{json: false},
|
||||
output: output,
|
||||
}
|
||||
}
|
||||
|
||||
func (*defaultHandler) Enabled(_ context.Context, l Level) bool {
|
||||
return l >= LevelInfo
|
||||
}
|
||||
|
||||
// Collect the level, attributes and message in a string and
|
||||
// write it with the default log.Logger.
|
||||
// Let the log.Logger handle time and file/line.
|
||||
func (h *defaultHandler) Handle(ctx context.Context, r Record) error {
|
||||
buf := buffer.New()
|
||||
buf.WriteString(r.Level.String())
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(r.Message)
|
||||
state := h.ch.newHandleState(buf, true, " ", nil)
|
||||
defer state.free()
|
||||
state.appendNonBuiltIns(r)
|
||||
|
||||
// skip [h.output, defaultHandler.Handle, handlerWriter.Write, log.Output]
|
||||
return h.output(4, buf.String())
|
||||
}
|
||||
|
||||
func (h *defaultHandler) WithAttrs(as []Attr) Handler {
|
||||
return &defaultHandler{h.ch.withAttrs(as), h.output}
|
||||
}
|
||||
|
||||
func (h *defaultHandler) WithGroup(name string) Handler {
|
||||
return &defaultHandler{h.ch.withGroup(name), h.output}
|
||||
}
|
||||
|
||||
// HandlerOptions are options for a TextHandler or JSONHandler.
|
||||
// A zero HandlerOptions consists entirely of default values.
|
||||
type HandlerOptions struct {
|
||||
// AddSource causes the handler to compute the source code position
|
||||
// of the log statement and add a SourceKey attribute to the output.
|
||||
AddSource bool
|
||||
|
||||
// Level reports the minimum record level that will be logged.
|
||||
// The handler discards records with lower levels.
|
||||
// If Level is nil, the handler assumes LevelInfo.
|
||||
// The handler calls Level.Level for each record processed;
|
||||
// to adjust the minimum level dynamically, use a LevelVar.
|
||||
Level Leveler
|
||||
|
||||
// ReplaceAttr is called to rewrite each non-group attribute before it is logged.
|
||||
// The attribute's value has been resolved (see [Value.Resolve]).
|
||||
// If ReplaceAttr returns an Attr with Key == "", the attribute is discarded.
|
||||
//
|
||||
// The built-in attributes with keys "time", "level", "source", and "msg"
|
||||
// are passed to this function, except that time is omitted
|
||||
// if zero, and source is omitted if AddSource is false.
|
||||
//
|
||||
// The first argument is a list of currently open groups that contain the
|
||||
// Attr. It must not be retained or modified. ReplaceAttr is never called
|
||||
// for Group attributes, only their contents. For example, the attribute
|
||||
// list
|
||||
//
|
||||
// Int("a", 1), Group("g", Int("b", 2)), Int("c", 3)
|
||||
//
|
||||
// results in consecutive calls to ReplaceAttr with the following arguments:
|
||||
//
|
||||
// nil, Int("a", 1)
|
||||
// []string{"g"}, Int("b", 2)
|
||||
// nil, Int("c", 3)
|
||||
//
|
||||
// ReplaceAttr can be used to change the default keys of the built-in
|
||||
// attributes, convert types (for example, to replace a `time.Time` with the
|
||||
// integer seconds since the Unix epoch), sanitize personal information, or
|
||||
// remove attributes from the output.
|
||||
ReplaceAttr func(groups []string, a Attr) Attr
|
||||
}
|
||||
|
||||
// Keys for "built-in" attributes.
|
||||
const (
|
||||
// TimeKey is the key used by the built-in handlers for the time
|
||||
// when the log method is called. The associated Value is a [time.Time].
|
||||
TimeKey = "time"
|
||||
// LevelKey is the key used by the built-in handlers for the level
|
||||
// of the log call. The associated value is a [Level].
|
||||
LevelKey = "level"
|
||||
// MessageKey is the key used by the built-in handlers for the
|
||||
// message of the log call. The associated value is a string.
|
||||
MessageKey = "msg"
|
||||
// SourceKey is the key used by the built-in handlers for the source file
|
||||
// and line of the log call. The associated value is a string.
|
||||
SourceKey = "source"
|
||||
)
|
||||
|
||||
type commonHandler struct {
|
||||
json bool // true => output JSON; false => output text
|
||||
opts HandlerOptions
|
||||
preformattedAttrs []byte
|
||||
groupPrefix string // for text: prefix of groups opened in preformatting
|
||||
groups []string // all groups started from WithGroup
|
||||
nOpenGroups int // the number of groups opened in preformattedAttrs
|
||||
mu sync.Mutex
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (h *commonHandler) clone() *commonHandler {
|
||||
// We can't use assignment because we can't copy the mutex.
|
||||
return &commonHandler{
|
||||
json: h.json,
|
||||
opts: h.opts,
|
||||
preformattedAttrs: slices.Clip(h.preformattedAttrs),
|
||||
groupPrefix: h.groupPrefix,
|
||||
groups: slices.Clip(h.groups),
|
||||
nOpenGroups: h.nOpenGroups,
|
||||
w: h.w,
|
||||
}
|
||||
}
|
||||
|
||||
// enabled reports whether l is greater than or equal to the
|
||||
// minimum level.
|
||||
func (h *commonHandler) enabled(l Level) bool {
|
||||
minLevel := LevelInfo
|
||||
if h.opts.Level != nil {
|
||||
minLevel = h.opts.Level.Level()
|
||||
}
|
||||
return l >= minLevel
|
||||
}
|
||||
|
||||
func (h *commonHandler) withAttrs(as []Attr) *commonHandler {
|
||||
h2 := h.clone()
|
||||
// Pre-format the attributes as an optimization.
|
||||
prefix := buffer.New()
|
||||
defer prefix.Free()
|
||||
prefix.WriteString(h.groupPrefix)
|
||||
state := h2.newHandleState((*buffer.Buffer)(&h2.preformattedAttrs), false, "", prefix)
|
||||
defer state.free()
|
||||
if len(h2.preformattedAttrs) > 0 {
|
||||
state.sep = h.attrSep()
|
||||
}
|
||||
state.openGroups()
|
||||
for _, a := range as {
|
||||
state.appendAttr(a)
|
||||
}
|
||||
// Remember the new prefix for later keys.
|
||||
h2.groupPrefix = state.prefix.String()
|
||||
// Remember how many opened groups are in preformattedAttrs,
|
||||
// so we don't open them again when we handle a Record.
|
||||
h2.nOpenGroups = len(h2.groups)
|
||||
return h2
|
||||
}
|
||||
|
||||
func (h *commonHandler) withGroup(name string) *commonHandler {
|
||||
if name == "" {
|
||||
return h
|
||||
}
|
||||
h2 := h.clone()
|
||||
h2.groups = append(h2.groups, name)
|
||||
return h2
|
||||
}
|
||||
|
||||
func (h *commonHandler) handle(r Record) error {
|
||||
state := h.newHandleState(buffer.New(), true, "", nil)
|
||||
defer state.free()
|
||||
if h.json {
|
||||
state.buf.WriteByte('{')
|
||||
}
|
||||
// Built-in attributes. They are not in a group.
|
||||
stateGroups := state.groups
|
||||
state.groups = nil // So ReplaceAttrs sees no groups instead of the pre groups.
|
||||
rep := h.opts.ReplaceAttr
|
||||
// time
|
||||
if !r.Time.IsZero() {
|
||||
key := TimeKey
|
||||
val := r.Time.Round(0) // strip monotonic to match Attr behavior
|
||||
if rep == nil {
|
||||
state.appendKey(key)
|
||||
state.appendTime(val)
|
||||
} else {
|
||||
state.appendAttr(Time(key, val))
|
||||
}
|
||||
}
|
||||
// level
|
||||
key := LevelKey
|
||||
val := r.Level
|
||||
if rep == nil {
|
||||
state.appendKey(key)
|
||||
state.appendString(val.String())
|
||||
} else {
|
||||
state.appendAttr(Any(key, val))
|
||||
}
|
||||
// source
|
||||
if h.opts.AddSource {
|
||||
state.appendAttr(Any(SourceKey, r.source()))
|
||||
}
|
||||
key = MessageKey
|
||||
msg := r.Message
|
||||
if rep == nil {
|
||||
state.appendKey(key)
|
||||
state.appendString(msg)
|
||||
} else {
|
||||
state.appendAttr(String(key, msg))
|
||||
}
|
||||
state.groups = stateGroups // Restore groups passed to ReplaceAttrs.
|
||||
state.appendNonBuiltIns(r)
|
||||
state.buf.WriteByte('\n')
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
_, err := h.w.Write(*state.buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *handleState) appendNonBuiltIns(r Record) {
|
||||
// preformatted Attrs
|
||||
if len(s.h.preformattedAttrs) > 0 {
|
||||
s.buf.WriteString(s.sep)
|
||||
s.buf.Write(s.h.preformattedAttrs)
|
||||
s.sep = s.h.attrSep()
|
||||
}
|
||||
// Attrs in Record -- unlike the built-in ones, they are in groups started
|
||||
// from WithGroup.
|
||||
s.prefix = buffer.New()
|
||||
defer s.prefix.Free()
|
||||
s.prefix.WriteString(s.h.groupPrefix)
|
||||
s.openGroups()
|
||||
r.Attrs(func(a Attr) bool {
|
||||
s.appendAttr(a)
|
||||
return true
|
||||
})
|
||||
if s.h.json {
|
||||
// Close all open groups.
|
||||
for range s.h.groups {
|
||||
s.buf.WriteByte('}')
|
||||
}
|
||||
// Close the top-level object.
|
||||
s.buf.WriteByte('}')
|
||||
}
|
||||
}
|
||||
|
||||
// attrSep returns the separator between attributes.
|
||||
func (h *commonHandler) attrSep() string {
|
||||
if h.json {
|
||||
return ","
|
||||
}
|
||||
return " "
|
||||
}
|
||||
|
||||
// handleState holds state for a single call to commonHandler.handle.
|
||||
// The initial value of sep determines whether to emit a separator
|
||||
// before the next key, after which it stays true.
|
||||
type handleState struct {
|
||||
h *commonHandler
|
||||
buf *buffer.Buffer
|
||||
freeBuf bool // should buf be freed?
|
||||
sep string // separator to write before next key
|
||||
prefix *buffer.Buffer // for text: key prefix
|
||||
groups *[]string // pool-allocated slice of active groups, for ReplaceAttr
|
||||
}
|
||||
|
||||
var groupPool = sync.Pool{New: func() any {
|
||||
s := make([]string, 0, 10)
|
||||
return &s
|
||||
}}
|
||||
|
||||
func (h *commonHandler) newHandleState(buf *buffer.Buffer, freeBuf bool, sep string, prefix *buffer.Buffer) handleState {
|
||||
s := handleState{
|
||||
h: h,
|
||||
buf: buf,
|
||||
freeBuf: freeBuf,
|
||||
sep: sep,
|
||||
prefix: prefix,
|
||||
}
|
||||
if h.opts.ReplaceAttr != nil {
|
||||
s.groups = groupPool.Get().(*[]string)
|
||||
*s.groups = append(*s.groups, h.groups[:h.nOpenGroups]...)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *handleState) free() {
|
||||
if s.freeBuf {
|
||||
s.buf.Free()
|
||||
}
|
||||
if gs := s.groups; gs != nil {
|
||||
*gs = (*gs)[:0]
|
||||
groupPool.Put(gs)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handleState) openGroups() {
|
||||
for _, n := range s.h.groups[s.h.nOpenGroups:] {
|
||||
s.openGroup(n)
|
||||
}
|
||||
}
|
||||
|
||||
// Separator for group names and keys.
|
||||
const keyComponentSep = '.'
|
||||
|
||||
// openGroup starts a new group of attributes
|
||||
// with the given name.
|
||||
func (s *handleState) openGroup(name string) {
|
||||
if s.h.json {
|
||||
s.appendKey(name)
|
||||
s.buf.WriteByte('{')
|
||||
s.sep = ""
|
||||
} else {
|
||||
s.prefix.WriteString(name)
|
||||
s.prefix.WriteByte(keyComponentSep)
|
||||
}
|
||||
// Collect group names for ReplaceAttr.
|
||||
if s.groups != nil {
|
||||
*s.groups = append(*s.groups, name)
|
||||
}
|
||||
}
|
||||
|
||||
// closeGroup ends the group with the given name.
|
||||
func (s *handleState) closeGroup(name string) {
|
||||
if s.h.json {
|
||||
s.buf.WriteByte('}')
|
||||
} else {
|
||||
(*s.prefix) = (*s.prefix)[:len(*s.prefix)-len(name)-1 /* for keyComponentSep */]
|
||||
}
|
||||
s.sep = s.h.attrSep()
|
||||
if s.groups != nil {
|
||||
*s.groups = (*s.groups)[:len(*s.groups)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// appendAttr appends the Attr's key and value using app.
|
||||
// It handles replacement and checking for an empty key.
|
||||
// after replacement).
|
||||
func (s *handleState) appendAttr(a Attr) {
|
||||
if rep := s.h.opts.ReplaceAttr; rep != nil && a.Value.Kind() != KindGroup {
|
||||
var gs []string
|
||||
if s.groups != nil {
|
||||
gs = *s.groups
|
||||
}
|
||||
// Resolve before calling ReplaceAttr, so the user doesn't have to.
|
||||
a.Value = a.Value.Resolve()
|
||||
a = rep(gs, a)
|
||||
}
|
||||
a.Value = a.Value.Resolve()
|
||||
// Elide empty Attrs.
|
||||
if a.isEmpty() {
|
||||
return
|
||||
}
|
||||
// Special case: Source.
|
||||
if v := a.Value; v.Kind() == KindAny {
|
||||
if src, ok := v.Any().(*Source); ok {
|
||||
if s.h.json {
|
||||
a.Value = src.group()
|
||||
} else {
|
||||
a.Value = StringValue(fmt.Sprintf("%s:%d", src.File, src.Line))
|
||||
}
|
||||
}
|
||||
}
|
||||
if a.Value.Kind() == KindGroup {
|
||||
attrs := a.Value.Group()
|
||||
// Output only non-empty groups.
|
||||
if len(attrs) > 0 {
|
||||
// Inline a group with an empty key.
|
||||
if a.Key != "" {
|
||||
s.openGroup(a.Key)
|
||||
}
|
||||
for _, aa := range attrs {
|
||||
s.appendAttr(aa)
|
||||
}
|
||||
if a.Key != "" {
|
||||
s.closeGroup(a.Key)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.appendKey(a.Key)
|
||||
s.appendValue(a.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handleState) appendError(err error) {
|
||||
s.appendString(fmt.Sprintf("!ERROR:%v", err))
|
||||
}
|
||||
|
||||
func (s *handleState) appendKey(key string) {
|
||||
s.buf.WriteString(s.sep)
|
||||
if s.prefix != nil {
|
||||
// TODO: optimize by avoiding allocation.
|
||||
s.appendString(string(*s.prefix) + key)
|
||||
} else {
|
||||
s.appendString(key)
|
||||
}
|
||||
if s.h.json {
|
||||
s.buf.WriteByte(':')
|
||||
} else {
|
||||
s.buf.WriteByte('=')
|
||||
}
|
||||
s.sep = s.h.attrSep()
|
||||
}
|
||||
|
||||
func (s *handleState) appendString(str string) {
|
||||
if s.h.json {
|
||||
s.buf.WriteByte('"')
|
||||
*s.buf = appendEscapedJSONString(*s.buf, str)
|
||||
s.buf.WriteByte('"')
|
||||
} else {
|
||||
// text
|
||||
if needsQuoting(str) {
|
||||
*s.buf = strconv.AppendQuote(*s.buf, str)
|
||||
} else {
|
||||
s.buf.WriteString(str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handleState) appendValue(v Value) {
|
||||
var err error
|
||||
if s.h.json {
|
||||
err = appendJSONValue(s, v)
|
||||
} else {
|
||||
err = appendTextValue(s, v)
|
||||
}
|
||||
if err != nil {
|
||||
s.appendError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handleState) appendTime(t time.Time) {
|
||||
if s.h.json {
|
||||
appendJSONTime(s, t)
|
||||
} else {
|
||||
writeTimeRFC3339Millis(s.buf, t)
|
||||
}
|
||||
}
|
||||
|
||||
// This takes half the time of Time.AppendFormat.
|
||||
func writeTimeRFC3339Millis(buf *buffer.Buffer, t time.Time) {
|
||||
year, month, day := t.Date()
|
||||
buf.WritePosIntWidth(year, 4)
|
||||
buf.WriteByte('-')
|
||||
buf.WritePosIntWidth(int(month), 2)
|
||||
buf.WriteByte('-')
|
||||
buf.WritePosIntWidth(day, 2)
|
||||
buf.WriteByte('T')
|
||||
hour, min, sec := t.Clock()
|
||||
buf.WritePosIntWidth(hour, 2)
|
||||
buf.WriteByte(':')
|
||||
buf.WritePosIntWidth(min, 2)
|
||||
buf.WriteByte(':')
|
||||
buf.WritePosIntWidth(sec, 2)
|
||||
ns := t.Nanosecond()
|
||||
buf.WriteByte('.')
|
||||
buf.WritePosIntWidth(ns/1e6, 3)
|
||||
_, offsetSeconds := t.Zone()
|
||||
if offsetSeconds == 0 {
|
||||
buf.WriteByte('Z')
|
||||
} else {
|
||||
offsetMinutes := offsetSeconds / 60
|
||||
if offsetMinutes < 0 {
|
||||
buf.WriteByte('-')
|
||||
offsetMinutes = -offsetMinutes
|
||||
} else {
|
||||
buf.WriteByte('+')
|
||||
}
|
||||
buf.WritePosIntWidth(offsetMinutes/60, 2)
|
||||
buf.WriteByte(':')
|
||||
buf.WritePosIntWidth(offsetMinutes%60, 2)
|
||||
}
|
||||
}
|
84
vendor/golang.org/x/exp/slog/internal/buffer/buffer.go
generated
vendored
Normal file
84
vendor/golang.org/x/exp/slog/internal/buffer/buffer.go
generated
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package buffer provides a pool-allocated byte buffer.
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Buffer adapted from go/src/fmt/print.go
|
||||
type Buffer []byte
|
||||
|
||||
// Having an initial size gives a dramatic speedup.
|
||||
var bufPool = sync.Pool{
|
||||
New: func() any {
|
||||
b := make([]byte, 0, 1024)
|
||||
return (*Buffer)(&b)
|
||||
},
|
||||
}
|
||||
|
||||
func New() *Buffer {
|
||||
return bufPool.Get().(*Buffer)
|
||||
}
|
||||
|
||||
func (b *Buffer) Free() {
|
||||
// To reduce peak allocation, return only smaller buffers to the pool.
|
||||
const maxBufferSize = 16 << 10
|
||||
if cap(*b) <= maxBufferSize {
|
||||
*b = (*b)[:0]
|
||||
bufPool.Put(b)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) Reset() {
|
||||
*b = (*b)[:0]
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(p []byte) (int, error) {
|
||||
*b = append(*b, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteString(s string) {
|
||||
*b = append(*b, s...)
|
||||
}
|
||||
|
||||
func (b *Buffer) WriteByte(c byte) {
|
||||
*b = append(*b, c)
|
||||
}
|
||||
|
||||
func (b *Buffer) WritePosInt(i int) {
|
||||
b.WritePosIntWidth(i, 0)
|
||||
}
|
||||
|
||||
// WritePosIntWidth writes non-negative integer i to the buffer, padded on the left
|
||||
// by zeroes to the given width. Use a width of 0 to omit padding.
|
||||
func (b *Buffer) WritePosIntWidth(i, width int) {
|
||||
// Cheap integer to fixed-width decimal ASCII.
|
||||
// Copied from log/log.go.
|
||||
|
||||
if i < 0 {
|
||||
panic("negative int")
|
||||
}
|
||||
|
||||
// Assemble decimal in reverse order.
|
||||
var bb [20]byte
|
||||
bp := len(bb) - 1
|
||||
for i >= 10 || width > 1 {
|
||||
width--
|
||||
q := i / 10
|
||||
bb[bp] = byte('0' + i - q*10)
|
||||
bp--
|
||||
i = q
|
||||
}
|
||||
// i < 10
|
||||
bb[bp] = byte('0' + i)
|
||||
b.Write(bb[bp:])
|
||||
}
|
||||
|
||||
func (b *Buffer) String() string {
|
||||
return string(*b)
|
||||
}
|
9
vendor/golang.org/x/exp/slog/internal/ignorepc.go
generated
vendored
Normal file
9
vendor/golang.org/x/exp/slog/internal/ignorepc.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package internal
|
||||
|
||||
// If IgnorePC is true, do not invoke runtime.Callers to get the pc.
|
||||
// This is solely for benchmarking the slowdown from runtime.Callers.
|
||||
var IgnorePC = false
|
336
vendor/golang.org/x/exp/slog/json_handler.go
generated
vendored
Normal file
336
vendor/golang.org/x/exp/slog/json_handler.go
generated
vendored
Normal file
|
@ -0,0 +1,336 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/exp/slog/internal/buffer"
|
||||
)
|
||||
|
||||
// JSONHandler is a Handler that writes Records to an io.Writer as
|
||||
// line-delimited JSON objects.
|
||||
type JSONHandler struct {
|
||||
*commonHandler
|
||||
}
|
||||
|
||||
// NewJSONHandler creates a JSONHandler that writes to w,
|
||||
// using the given options.
|
||||
// If opts is nil, the default options are used.
|
||||
func NewJSONHandler(w io.Writer, opts *HandlerOptions) *JSONHandler {
|
||||
if opts == nil {
|
||||
opts = &HandlerOptions{}
|
||||
}
|
||||
return &JSONHandler{
|
||||
&commonHandler{
|
||||
json: true,
|
||||
w: w,
|
||||
opts: *opts,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled reports whether the handler handles records at the given level.
|
||||
// The handler ignores records whose level is lower.
|
||||
func (h *JSONHandler) Enabled(_ context.Context, level Level) bool {
|
||||
return h.commonHandler.enabled(level)
|
||||
}
|
||||
|
||||
// WithAttrs returns a new JSONHandler whose attributes consists
|
||||
// of h's attributes followed by attrs.
|
||||
func (h *JSONHandler) WithAttrs(attrs []Attr) Handler {
|
||||
return &JSONHandler{commonHandler: h.commonHandler.withAttrs(attrs)}
|
||||
}
|
||||
|
||||
func (h *JSONHandler) WithGroup(name string) Handler {
|
||||
return &JSONHandler{commonHandler: h.commonHandler.withGroup(name)}
|
||||
}
|
||||
|
||||
// Handle formats its argument Record as a JSON object on a single line.
|
||||
//
|
||||
// If the Record's time is zero, the time is omitted.
|
||||
// Otherwise, the key is "time"
|
||||
// and the value is output as with json.Marshal.
|
||||
//
|
||||
// If the Record's level is zero, the level is omitted.
|
||||
// Otherwise, the key is "level"
|
||||
// and the value of [Level.String] is output.
|
||||
//
|
||||
// If the AddSource option is set and source information is available,
|
||||
// the key is "source"
|
||||
// and the value is output as "FILE:LINE".
|
||||
//
|
||||
// The message's key is "msg".
|
||||
//
|
||||
// To modify these or other attributes, or remove them from the output, use
|
||||
// [HandlerOptions.ReplaceAttr].
|
||||
//
|
||||
// Values are formatted as with an [encoding/json.Encoder] with SetEscapeHTML(false),
|
||||
// with two exceptions.
|
||||
//
|
||||
// First, an Attr whose Value is of type error is formatted as a string, by
|
||||
// calling its Error method. Only errors in Attrs receive this special treatment,
|
||||
// not errors embedded in structs, slices, maps or other data structures that
|
||||
// are processed by the encoding/json package.
|
||||
//
|
||||
// Second, an encoding failure does not cause Handle to return an error.
|
||||
// Instead, the error message is formatted as a string.
|
||||
//
|
||||
// Each call to Handle results in a single serialized call to io.Writer.Write.
|
||||
func (h *JSONHandler) Handle(_ context.Context, r Record) error {
|
||||
return h.commonHandler.handle(r)
|
||||
}
|
||||
|
||||
// Adapted from time.Time.MarshalJSON to avoid allocation.
|
||||
func appendJSONTime(s *handleState, t time.Time) {
|
||||
if y := t.Year(); y < 0 || y >= 10000 {
|
||||
// RFC 3339 is clear that years are 4 digits exactly.
|
||||
// See golang.org/issue/4556#c15 for more discussion.
|
||||
s.appendError(errors.New("time.Time year outside of range [0,9999]"))
|
||||
}
|
||||
s.buf.WriteByte('"')
|
||||
*s.buf = t.AppendFormat(*s.buf, time.RFC3339Nano)
|
||||
s.buf.WriteByte('"')
|
||||
}
|
||||
|
||||
func appendJSONValue(s *handleState, v Value) error {
|
||||
switch v.Kind() {
|
||||
case KindString:
|
||||
s.appendString(v.str())
|
||||
case KindInt64:
|
||||
*s.buf = strconv.AppendInt(*s.buf, v.Int64(), 10)
|
||||
case KindUint64:
|
||||
*s.buf = strconv.AppendUint(*s.buf, v.Uint64(), 10)
|
||||
case KindFloat64:
|
||||
// json.Marshal is funny about floats; it doesn't
|
||||
// always match strconv.AppendFloat. So just call it.
|
||||
// That's expensive, but floats are rare.
|
||||
if err := appendJSONMarshal(s.buf, v.Float64()); err != nil {
|
||||
return err
|
||||
}
|
||||
case KindBool:
|
||||
*s.buf = strconv.AppendBool(*s.buf, v.Bool())
|
||||
case KindDuration:
|
||||
// Do what json.Marshal does.
|
||||
*s.buf = strconv.AppendInt(*s.buf, int64(v.Duration()), 10)
|
||||
case KindTime:
|
||||
s.appendTime(v.Time())
|
||||
case KindAny:
|
||||
a := v.Any()
|
||||
_, jm := a.(json.Marshaler)
|
||||
if err, ok := a.(error); ok && !jm {
|
||||
s.appendString(err.Error())
|
||||
} else {
|
||||
return appendJSONMarshal(s.buf, a)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendJSONMarshal(buf *buffer.Buffer, v any) error {
|
||||
// Use a json.Encoder to avoid escaping HTML.
|
||||
var bb bytes.Buffer
|
||||
enc := json.NewEncoder(&bb)
|
||||
enc.SetEscapeHTML(false)
|
||||
if err := enc.Encode(v); err != nil {
|
||||
return err
|
||||
}
|
||||
bs := bb.Bytes()
|
||||
buf.Write(bs[:len(bs)-1]) // remove final newline
|
||||
return nil
|
||||
}
|
||||
|
||||
// appendEscapedJSONString escapes s for JSON and appends it to buf.
|
||||
// It does not surround the string in quotation marks.
|
||||
//
|
||||
// Modified from encoding/json/encode.go:encodeState.string,
|
||||
// with escapeHTML set to false.
|
||||
func appendEscapedJSONString(buf []byte, s string) []byte {
|
||||
char := func(b byte) { buf = append(buf, b) }
|
||||
str := func(s string) { buf = append(buf, s...) }
|
||||
|
||||
start := 0
|
||||
for i := 0; i < len(s); {
|
||||
if b := s[i]; b < utf8.RuneSelf {
|
||||
if safeSet[b] {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if start < i {
|
||||
str(s[start:i])
|
||||
}
|
||||
char('\\')
|
||||
switch b {
|
||||
case '\\', '"':
|
||||
char(b)
|
||||
case '\n':
|
||||
char('n')
|
||||
case '\r':
|
||||
char('r')
|
||||
case '\t':
|
||||
char('t')
|
||||
default:
|
||||
// This encodes bytes < 0x20 except for \t, \n and \r.
|
||||
str(`u00`)
|
||||
char(hex[b>>4])
|
||||
char(hex[b&0xF])
|
||||
}
|
||||
i++
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
c, size := utf8.DecodeRuneInString(s[i:])
|
||||
if c == utf8.RuneError && size == 1 {
|
||||
if start < i {
|
||||
str(s[start:i])
|
||||
}
|
||||
str(`\ufffd`)
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
// U+2028 is LINE SEPARATOR.
|
||||
// U+2029 is PARAGRAPH SEPARATOR.
|
||||
// They are both technically valid characters in JSON strings,
|
||||
// but don't work in JSONP, which has to be evaluated as JavaScript,
|
||||
// and can lead to security holes there. It is valid JSON to
|
||||
// escape them, so we do so unconditionally.
|
||||
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
|
||||
if c == '\u2028' || c == '\u2029' {
|
||||
if start < i {
|
||||
str(s[start:i])
|
||||
}
|
||||
str(`\u202`)
|
||||
char(hex[c&0xF])
|
||||
i += size
|
||||
start = i
|
||||
continue
|
||||
}
|
||||
i += size
|
||||
}
|
||||
if start < len(s) {
|
||||
str(s[start:])
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
var hex = "0123456789abcdef"
|
||||
|
||||
// Copied from encoding/json/tables.go.
|
||||
//
|
||||
// safeSet holds the value true if the ASCII character with the given array
|
||||
// position can be represented inside a JSON string without any further
|
||||
// escaping.
|
||||
//
|
||||
// All values are true except for the ASCII control characters (0-31), the
|
||||
// double quote ("), and the backslash character ("\").
|
||||
var safeSet = [utf8.RuneSelf]bool{
|
||||
' ': true,
|
||||
'!': true,
|
||||
'"': false,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': true,
|
||||
'\'': true,
|
||||
'(': true,
|
||||
')': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
',': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'/': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
':': true,
|
||||
';': true,
|
||||
'<': true,
|
||||
'=': true,
|
||||
'>': true,
|
||||
'?': true,
|
||||
'@': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'V': true,
|
||||
'W': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'[': true,
|
||||
'\\': false,
|
||||
']': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'{': true,
|
||||
'|': true,
|
||||
'}': true,
|
||||
'~': true,
|
||||
'\u007f': true,
|
||||
}
|
201
vendor/golang.org/x/exp/slog/level.go
generated
vendored
Normal file
201
vendor/golang.org/x/exp/slog/level.go
generated
vendored
Normal file
|
@ -0,0 +1,201 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// A Level is the importance or severity of a log event.
|
||||
// The higher the level, the more important or severe the event.
|
||||
type Level int
|
||||
|
||||
// Level numbers are inherently arbitrary,
|
||||
// but we picked them to satisfy three constraints.
|
||||
// Any system can map them to another numbering scheme if it wishes.
|
||||
//
|
||||
// First, we wanted the default level to be Info, Since Levels are ints, Info is
|
||||
// the default value for int, zero.
|
||||
//
|
||||
|
||||
// Second, we wanted to make it easy to use levels to specify logger verbosity.
|
||||
// Since a larger level means a more severe event, a logger that accepts events
|
||||
// with smaller (or more negative) level means a more verbose logger. Logger
|
||||
// verbosity is thus the negation of event severity, and the default verbosity
|
||||
// of 0 accepts all events at least as severe as INFO.
|
||||
//
|
||||
// Third, we wanted some room between levels to accommodate schemes with named
|
||||
// levels between ours. For example, Google Cloud Logging defines a Notice level
|
||||
// between Info and Warn. Since there are only a few of these intermediate
|
||||
// levels, the gap between the numbers need not be large. Our gap of 4 matches
|
||||
// OpenTelemetry's mapping. Subtracting 9 from an OpenTelemetry level in the
|
||||
// DEBUG, INFO, WARN and ERROR ranges converts it to the corresponding slog
|
||||
// Level range. OpenTelemetry also has the names TRACE and FATAL, which slog
|
||||
// does not. But those OpenTelemetry levels can still be represented as slog
|
||||
// Levels by using the appropriate integers.
|
||||
//
|
||||
// Names for common levels.
|
||||
const (
|
||||
LevelDebug Level = -4
|
||||
LevelInfo Level = 0
|
||||
LevelWarn Level = 4
|
||||
LevelError Level = 8
|
||||
)
|
||||
|
||||
// String returns a name for the level.
|
||||
// If the level has a name, then that name
|
||||
// in uppercase is returned.
|
||||
// If the level is between named values, then
|
||||
// an integer is appended to the uppercased name.
|
||||
// Examples:
|
||||
//
|
||||
// LevelWarn.String() => "WARN"
|
||||
// (LevelInfo+2).String() => "INFO+2"
|
||||
func (l Level) String() string {
|
||||
str := func(base string, val Level) string {
|
||||
if val == 0 {
|
||||
return base
|
||||
}
|
||||
return fmt.Sprintf("%s%+d", base, val)
|
||||
}
|
||||
|
||||
switch {
|
||||
case l < LevelInfo:
|
||||
return str("DEBUG", l-LevelDebug)
|
||||
case l < LevelWarn:
|
||||
return str("INFO", l-LevelInfo)
|
||||
case l < LevelError:
|
||||
return str("WARN", l-LevelWarn)
|
||||
default:
|
||||
return str("ERROR", l-LevelError)
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements [encoding/json.Marshaler]
|
||||
// by quoting the output of [Level.String].
|
||||
func (l Level) MarshalJSON() ([]byte, error) {
|
||||
// AppendQuote is sufficient for JSON-encoding all Level strings.
|
||||
// They don't contain any runes that would produce invalid JSON
|
||||
// when escaped.
|
||||
return strconv.AppendQuote(nil, l.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements [encoding/json.Unmarshaler]
|
||||
// It accepts any string produced by [Level.MarshalJSON],
|
||||
// ignoring case.
|
||||
// It also accepts numeric offsets that would result in a different string on
|
||||
// output. For example, "Error-8" would marshal as "INFO".
|
||||
func (l *Level) UnmarshalJSON(data []byte) error {
|
||||
s, err := strconv.Unquote(string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return l.parse(s)
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler]
|
||||
// by calling [Level.String].
|
||||
func (l Level) MarshalText() ([]byte, error) {
|
||||
return []byte(l.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler].
|
||||
// It accepts any string produced by [Level.MarshalText],
|
||||
// ignoring case.
|
||||
// It also accepts numeric offsets that would result in a different string on
|
||||
// output. For example, "Error-8" would marshal as "INFO".
|
||||
func (l *Level) UnmarshalText(data []byte) error {
|
||||
return l.parse(string(data))
|
||||
}
|
||||
|
||||
func (l *Level) parse(s string) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("slog: level string %q: %w", s, err)
|
||||
}
|
||||
}()
|
||||
|
||||
name := s
|
||||
offset := 0
|
||||
if i := strings.IndexAny(s, "+-"); i >= 0 {
|
||||
name = s[:i]
|
||||
offset, err = strconv.Atoi(s[i:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
switch strings.ToUpper(name) {
|
||||
case "DEBUG":
|
||||
*l = LevelDebug
|
||||
case "INFO":
|
||||
*l = LevelInfo
|
||||
case "WARN":
|
||||
*l = LevelWarn
|
||||
case "ERROR":
|
||||
*l = LevelError
|
||||
default:
|
||||
return errors.New("unknown name")
|
||||
}
|
||||
*l += Level(offset)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Level returns the receiver.
|
||||
// It implements Leveler.
|
||||
func (l Level) Level() Level { return l }
|
||||
|
||||
// A LevelVar is a Level variable, to allow a Handler level to change
|
||||
// dynamically.
|
||||
// It implements Leveler as well as a Set method,
|
||||
// and it is safe for use by multiple goroutines.
|
||||
// The zero LevelVar corresponds to LevelInfo.
|
||||
type LevelVar struct {
|
||||
val atomic.Int64
|
||||
}
|
||||
|
||||
// Level returns v's level.
|
||||
func (v *LevelVar) Level() Level {
|
||||
return Level(int(v.val.Load()))
|
||||
}
|
||||
|
||||
// Set sets v's level to l.
|
||||
func (v *LevelVar) Set(l Level) {
|
||||
v.val.Store(int64(l))
|
||||
}
|
||||
|
||||
func (v *LevelVar) String() string {
|
||||
return fmt.Sprintf("LevelVar(%s)", v.Level())
|
||||
}
|
||||
|
||||
// MarshalText implements [encoding.TextMarshaler]
|
||||
// by calling [Level.MarshalText].
|
||||
func (v *LevelVar) MarshalText() ([]byte, error) {
|
||||
return v.Level().MarshalText()
|
||||
}
|
||||
|
||||
// UnmarshalText implements [encoding.TextUnmarshaler]
|
||||
// by calling [Level.UnmarshalText].
|
||||
func (v *LevelVar) UnmarshalText(data []byte) error {
|
||||
var l Level
|
||||
if err := l.UnmarshalText(data); err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set(l)
|
||||
return nil
|
||||
}
|
||||
|
||||
// A Leveler provides a Level value.
|
||||
//
|
||||
// As Level itself implements Leveler, clients typically supply
|
||||
// a Level value wherever a Leveler is needed, such as in HandlerOptions.
|
||||
// Clients who need to vary the level dynamically can provide a more complex
|
||||
// Leveler implementation such as *LevelVar.
|
||||
type Leveler interface {
|
||||
Level() Level
|
||||
}
|
295
vendor/golang.org/x/exp/slog/logger.go
generated
vendored
Normal file
295
vendor/golang.org/x/exp/slog/logger.go
generated
vendored
Normal file
|
@ -0,0 +1,295 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slog/internal"
|
||||
)
|
||||
|
||||
var defaultLogger atomic.Value
|
||||
|
||||
func init() {
|
||||
defaultLogger.Store(New(newDefaultHandler(log.Output)))
|
||||
}
|
||||
|
||||
// Default returns the default Logger.
|
||||
func Default() *Logger { return defaultLogger.Load().(*Logger) }
|
||||
|
||||
// SetDefault makes l the default Logger.
|
||||
// After this call, output from the log package's default Logger
|
||||
// (as with [log.Print], etc.) will be logged at LevelInfo using l's Handler.
|
||||
func SetDefault(l *Logger) {
|
||||
defaultLogger.Store(l)
|
||||
// If the default's handler is a defaultHandler, then don't use a handleWriter,
|
||||
// or we'll deadlock as they both try to acquire the log default mutex.
|
||||
// The defaultHandler will use whatever the log default writer is currently
|
||||
// set to, which is correct.
|
||||
// This can occur with SetDefault(Default()).
|
||||
// See TestSetDefault.
|
||||
if _, ok := l.Handler().(*defaultHandler); !ok {
|
||||
capturePC := log.Flags()&(log.Lshortfile|log.Llongfile) != 0
|
||||
log.SetOutput(&handlerWriter{l.Handler(), LevelInfo, capturePC})
|
||||
log.SetFlags(0) // we want just the log message, no time or location
|
||||
}
|
||||
}
|
||||
|
||||
// handlerWriter is an io.Writer that calls a Handler.
|
||||
// It is used to link the default log.Logger to the default slog.Logger.
|
||||
type handlerWriter struct {
|
||||
h Handler
|
||||
level Level
|
||||
capturePC bool
|
||||
}
|
||||
|
||||
func (w *handlerWriter) Write(buf []byte) (int, error) {
|
||||
if !w.h.Enabled(context.Background(), w.level) {
|
||||
return 0, nil
|
||||
}
|
||||
var pc uintptr
|
||||
if !internal.IgnorePC && w.capturePC {
|
||||
// skip [runtime.Callers, w.Write, Logger.Output, log.Print]
|
||||
var pcs [1]uintptr
|
||||
runtime.Callers(4, pcs[:])
|
||||
pc = pcs[0]
|
||||
}
|
||||
|
||||
// Remove final newline.
|
||||
origLen := len(buf) // Report that the entire buf was written.
|
||||
if len(buf) > 0 && buf[len(buf)-1] == '\n' {
|
||||
buf = buf[:len(buf)-1]
|
||||
}
|
||||
r := NewRecord(time.Now(), w.level, string(buf), pc)
|
||||
return origLen, w.h.Handle(context.Background(), r)
|
||||
}
|
||||
|
||||
// A Logger records structured information about each call to its
|
||||
// Log, Debug, Info, Warn, and Error methods.
|
||||
// For each call, it creates a Record and passes it to a Handler.
|
||||
//
|
||||
// To create a new Logger, call [New] or a Logger method
|
||||
// that begins "With".
|
||||
type Logger struct {
|
||||
handler Handler // for structured logging
|
||||
}
|
||||
|
||||
func (l *Logger) clone() *Logger {
|
||||
c := *l
|
||||
return &c
|
||||
}
|
||||
|
||||
// Handler returns l's Handler.
|
||||
func (l *Logger) Handler() Handler { return l.handler }
|
||||
|
||||
// With returns a new Logger that includes the given arguments, converted to
|
||||
// Attrs as in [Logger.Log].
|
||||
// The Attrs will be added to each output from the Logger.
|
||||
// The new Logger shares the old Logger's context.
|
||||
// The new Logger's handler is the result of calling WithAttrs on the receiver's
|
||||
// handler.
|
||||
func (l *Logger) With(args ...any) *Logger {
|
||||
c := l.clone()
|
||||
c.handler = l.handler.WithAttrs(argsToAttrSlice(args))
|
||||
return c
|
||||
}
|
||||
|
||||
// WithGroup returns a new Logger that starts a group. The keys of all
|
||||
// attributes added to the Logger will be qualified by the given name.
|
||||
// (How that qualification happens depends on the [Handler.WithGroup]
|
||||
// method of the Logger's Handler.)
|
||||
// The new Logger shares the old Logger's context.
|
||||
//
|
||||
// The new Logger's handler is the result of calling WithGroup on the receiver's
|
||||
// handler.
|
||||
func (l *Logger) WithGroup(name string) *Logger {
|
||||
c := l.clone()
|
||||
c.handler = l.handler.WithGroup(name)
|
||||
return c
|
||||
|
||||
}
|
||||
|
||||
// New creates a new Logger with the given non-nil Handler and a nil context.
|
||||
func New(h Handler) *Logger {
|
||||
if h == nil {
|
||||
panic("nil Handler")
|
||||
}
|
||||
return &Logger{handler: h}
|
||||
}
|
||||
|
||||
// With calls Logger.With on the default logger.
|
||||
func With(args ...any) *Logger {
|
||||
return Default().With(args...)
|
||||
}
|
||||
|
||||
// Enabled reports whether l emits log records at the given context and level.
|
||||
func (l *Logger) Enabled(ctx context.Context, level Level) bool {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return l.Handler().Enabled(ctx, level)
|
||||
}
|
||||
|
||||
// NewLogLogger returns a new log.Logger such that each call to its Output method
|
||||
// dispatches a Record to the specified handler. The logger acts as a bridge from
|
||||
// the older log API to newer structured logging handlers.
|
||||
func NewLogLogger(h Handler, level Level) *log.Logger {
|
||||
return log.New(&handlerWriter{h, level, true}, "", 0)
|
||||
}
|
||||
|
||||
// Log emits a log record with the current time and the given level and message.
|
||||
// The Record's Attrs consist of the Logger's attributes followed by
|
||||
// the Attrs specified by args.
|
||||
//
|
||||
// The attribute arguments are processed as follows:
|
||||
// - If an argument is an Attr, it is used as is.
|
||||
// - If an argument is a string and this is not the last argument,
|
||||
// the following argument is treated as the value and the two are combined
|
||||
// into an Attr.
|
||||
// - Otherwise, the argument is treated as a value with key "!BADKEY".
|
||||
func (l *Logger) Log(ctx context.Context, level Level, msg string, args ...any) {
|
||||
l.log(ctx, level, msg, args...)
|
||||
}
|
||||
|
||||
// LogAttrs is a more efficient version of [Logger.Log] that accepts only Attrs.
|
||||
func (l *Logger) LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
|
||||
l.logAttrs(ctx, level, msg, attrs...)
|
||||
}
|
||||
|
||||
// Debug logs at LevelDebug.
|
||||
func (l *Logger) Debug(msg string, args ...any) {
|
||||
l.log(nil, LevelDebug, msg, args...)
|
||||
}
|
||||
|
||||
// DebugCtx logs at LevelDebug with the given context.
|
||||
func (l *Logger) DebugCtx(ctx context.Context, msg string, args ...any) {
|
||||
l.log(ctx, LevelDebug, msg, args...)
|
||||
}
|
||||
|
||||
// Info logs at LevelInfo.
|
||||
func (l *Logger) Info(msg string, args ...any) {
|
||||
l.log(nil, LevelInfo, msg, args...)
|
||||
}
|
||||
|
||||
// InfoCtx logs at LevelInfo with the given context.
|
||||
func (l *Logger) InfoCtx(ctx context.Context, msg string, args ...any) {
|
||||
l.log(ctx, LevelInfo, msg, args...)
|
||||
}
|
||||
|
||||
// Warn logs at LevelWarn.
|
||||
func (l *Logger) Warn(msg string, args ...any) {
|
||||
l.log(nil, LevelWarn, msg, args...)
|
||||
}
|
||||
|
||||
// WarnCtx logs at LevelWarn with the given context.
|
||||
func (l *Logger) WarnCtx(ctx context.Context, msg string, args ...any) {
|
||||
l.log(ctx, LevelWarn, msg, args...)
|
||||
}
|
||||
|
||||
// Error logs at LevelError.
|
||||
func (l *Logger) Error(msg string, args ...any) {
|
||||
l.log(nil, LevelError, msg, args...)
|
||||
}
|
||||
|
||||
// ErrorCtx logs at LevelError with the given context.
|
||||
func (l *Logger) ErrorCtx(ctx context.Context, msg string, args ...any) {
|
||||
l.log(ctx, LevelError, msg, args...)
|
||||
}
|
||||
|
||||
// log is the low-level logging method for methods that take ...any.
|
||||
// It must always be called directly by an exported logging method
|
||||
// or function, because it uses a fixed call depth to obtain the pc.
|
||||
func (l *Logger) log(ctx context.Context, level Level, msg string, args ...any) {
|
||||
if !l.Enabled(ctx, level) {
|
||||
return
|
||||
}
|
||||
var pc uintptr
|
||||
if !internal.IgnorePC {
|
||||
var pcs [1]uintptr
|
||||
// skip [runtime.Callers, this function, this function's caller]
|
||||
runtime.Callers(3, pcs[:])
|
||||
pc = pcs[0]
|
||||
}
|
||||
r := NewRecord(time.Now(), level, msg, pc)
|
||||
r.Add(args...)
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
_ = l.Handler().Handle(ctx, r)
|
||||
}
|
||||
|
||||
// logAttrs is like [Logger.log], but for methods that take ...Attr.
|
||||
func (l *Logger) logAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
|
||||
if !l.Enabled(ctx, level) {
|
||||
return
|
||||
}
|
||||
var pc uintptr
|
||||
if !internal.IgnorePC {
|
||||
var pcs [1]uintptr
|
||||
// skip [runtime.Callers, this function, this function's caller]
|
||||
runtime.Callers(3, pcs[:])
|
||||
pc = pcs[0]
|
||||
}
|
||||
r := NewRecord(time.Now(), level, msg, pc)
|
||||
r.AddAttrs(attrs...)
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
_ = l.Handler().Handle(ctx, r)
|
||||
}
|
||||
|
||||
// Debug calls Logger.Debug on the default logger.
|
||||
func Debug(msg string, args ...any) {
|
||||
Default().log(nil, LevelDebug, msg, args...)
|
||||
}
|
||||
|
||||
// DebugCtx calls Logger.DebugCtx on the default logger.
|
||||
func DebugCtx(ctx context.Context, msg string, args ...any) {
|
||||
Default().log(ctx, LevelDebug, msg, args...)
|
||||
}
|
||||
|
||||
// Info calls Logger.Info on the default logger.
|
||||
func Info(msg string, args ...any) {
|
||||
Default().log(nil, LevelInfo, msg, args...)
|
||||
}
|
||||
|
||||
// InfoCtx calls Logger.InfoCtx on the default logger.
|
||||
func InfoCtx(ctx context.Context, msg string, args ...any) {
|
||||
Default().log(ctx, LevelInfo, msg, args...)
|
||||
}
|
||||
|
||||
// Warn calls Logger.Warn on the default logger.
|
||||
func Warn(msg string, args ...any) {
|
||||
Default().log(nil, LevelWarn, msg, args...)
|
||||
}
|
||||
|
||||
// WarnCtx calls Logger.WarnCtx on the default logger.
|
||||
func WarnCtx(ctx context.Context, msg string, args ...any) {
|
||||
Default().log(ctx, LevelWarn, msg, args...)
|
||||
}
|
||||
|
||||
// Error calls Logger.Error on the default logger.
|
||||
func Error(msg string, args ...any) {
|
||||
Default().log(nil, LevelError, msg, args...)
|
||||
}
|
||||
|
||||
// ErrorCtx calls Logger.ErrorCtx on the default logger.
|
||||
func ErrorCtx(ctx context.Context, msg string, args ...any) {
|
||||
Default().log(ctx, LevelError, msg, args...)
|
||||
}
|
||||
|
||||
// Log calls Logger.Log on the default logger.
|
||||
func Log(ctx context.Context, level Level, msg string, args ...any) {
|
||||
Default().log(ctx, level, msg, args...)
|
||||
}
|
||||
|
||||
// LogAttrs calls Logger.LogAttrs on the default logger.
|
||||
func LogAttrs(ctx context.Context, level Level, msg string, attrs ...Attr) {
|
||||
Default().logAttrs(ctx, level, msg, attrs...)
|
||||
}
|
36
vendor/golang.org/x/exp/slog/noplog.bench
generated
vendored
Normal file
36
vendor/golang.org/x/exp/slog/noplog.bench
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: golang.org/x/exp/slog
|
||||
cpu: Intel(R) Xeon(R) CPU @ 2.20GHz
|
||||
BenchmarkNopLog/attrs-8 1000000 1090 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-8 1000000 1097 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-8 1000000 1078 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-8 1000000 1095 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-8 1000000 1096 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-parallel-8 4007268 308.2 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-parallel-8 4016138 299.7 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-parallel-8 4020529 305.9 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-parallel-8 3977829 303.4 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/attrs-parallel-8 3225438 318.5 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/keys-values-8 1179256 994.2 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/keys-values-8 1000000 1002 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/keys-values-8 1216710 993.2 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/keys-values-8 1000000 1013 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/keys-values-8 1000000 1016 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-8 989066 1163 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-8 994116 1163 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-8 1000000 1152 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-8 991675 1165 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-8 965268 1166 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-parallel-8 3955503 303.3 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-parallel-8 3861188 307.8 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-parallel-8 3967752 303.9 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-parallel-8 3955203 302.7 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/WithContext-parallel-8 3948278 301.1 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/Ctx-8 940622 1247 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/Ctx-8 936381 1257 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/Ctx-8 959730 1266 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/Ctx-8 943473 1290 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkNopLog/Ctx-8 919414 1259 ns/op 0 B/op 0 allocs/op
|
||||
PASS
|
||||
ok golang.org/x/exp/slog 40.566s
|
207
vendor/golang.org/x/exp/slog/record.go
generated
vendored
Normal file
207
vendor/golang.org/x/exp/slog/record.go
generated
vendored
Normal file
|
@ -0,0 +1,207 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const nAttrsInline = 5
|
||||
|
||||
// A Record holds information about a log event.
|
||||
// Copies of a Record share state.
|
||||
// Do not modify a Record after handing out a copy to it.
|
||||
// Use [Record.Clone] to create a copy with no shared state.
|
||||
type Record struct {
|
||||
// The time at which the output method (Log, Info, etc.) was called.
|
||||
Time time.Time
|
||||
|
||||
// The log message.
|
||||
Message string
|
||||
|
||||
// The level of the event.
|
||||
Level Level
|
||||
|
||||
// The program counter at the time the record was constructed, as determined
|
||||
// by runtime.Callers. If zero, no program counter is available.
|
||||
//
|
||||
// The only valid use for this value is as an argument to
|
||||
// [runtime.CallersFrames]. In particular, it must not be passed to
|
||||
// [runtime.FuncForPC].
|
||||
PC uintptr
|
||||
|
||||
// Allocation optimization: an inline array sized to hold
|
||||
// the majority of log calls (based on examination of open-source
|
||||
// code). It holds the start of the list of Attrs.
|
||||
front [nAttrsInline]Attr
|
||||
|
||||
// The number of Attrs in front.
|
||||
nFront int
|
||||
|
||||
// The list of Attrs except for those in front.
|
||||
// Invariants:
|
||||
// - len(back) > 0 iff nFront == len(front)
|
||||
// - Unused array elements are zero. Used to detect mistakes.
|
||||
back []Attr
|
||||
}
|
||||
|
||||
// NewRecord creates a Record from the given arguments.
|
||||
// Use [Record.AddAttrs] to add attributes to the Record.
|
||||
//
|
||||
// NewRecord is intended for logging APIs that want to support a [Handler] as
|
||||
// a backend.
|
||||
func NewRecord(t time.Time, level Level, msg string, pc uintptr) Record {
|
||||
return Record{
|
||||
Time: t,
|
||||
Message: msg,
|
||||
Level: level,
|
||||
PC: pc,
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a copy of the record with no shared state.
|
||||
// The original record and the clone can both be modified
|
||||
// without interfering with each other.
|
||||
func (r Record) Clone() Record {
|
||||
r.back = slices.Clip(r.back) // prevent append from mutating shared array
|
||||
return r
|
||||
}
|
||||
|
||||
// NumAttrs returns the number of attributes in the Record.
|
||||
func (r Record) NumAttrs() int {
|
||||
return r.nFront + len(r.back)
|
||||
}
|
||||
|
||||
// Attrs calls f on each Attr in the Record.
|
||||
// Iteration stops if f returns false.
|
||||
func (r Record) Attrs(f func(Attr) bool) {
|
||||
for i := 0; i < r.nFront; i++ {
|
||||
if !f(r.front[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, a := range r.back {
|
||||
if !f(a) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddAttrs appends the given Attrs to the Record's list of Attrs.
|
||||
func (r *Record) AddAttrs(attrs ...Attr) {
|
||||
n := copy(r.front[r.nFront:], attrs)
|
||||
r.nFront += n
|
||||
// Check if a copy was modified by slicing past the end
|
||||
// and seeing if the Attr there is non-zero.
|
||||
if cap(r.back) > len(r.back) {
|
||||
end := r.back[:len(r.back)+1][len(r.back)]
|
||||
if !end.isEmpty() {
|
||||
panic("copies of a slog.Record were both modified")
|
||||
}
|
||||
}
|
||||
r.back = append(r.back, attrs[n:]...)
|
||||
}
|
||||
|
||||
// Add converts the args to Attrs as described in [Logger.Log],
|
||||
// then appends the Attrs to the Record's list of Attrs.
|
||||
func (r *Record) Add(args ...any) {
|
||||
var a Attr
|
||||
for len(args) > 0 {
|
||||
a, args = argsToAttr(args)
|
||||
if r.nFront < len(r.front) {
|
||||
r.front[r.nFront] = a
|
||||
r.nFront++
|
||||
} else {
|
||||
if r.back == nil {
|
||||
r.back = make([]Attr, 0, countAttrs(args))
|
||||
}
|
||||
r.back = append(r.back, a)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// countAttrs returns the number of Attrs that would be created from args.
|
||||
func countAttrs(args []any) int {
|
||||
n := 0
|
||||
for i := 0; i < len(args); i++ {
|
||||
n++
|
||||
if _, ok := args[i].(string); ok {
|
||||
i++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
const badKey = "!BADKEY"
|
||||
|
||||
// argsToAttr turns a prefix of the nonempty args slice into an Attr
|
||||
// and returns the unconsumed portion of the slice.
|
||||
// If args[0] is an Attr, it returns it.
|
||||
// If args[0] is a string, it treats the first two elements as
|
||||
// a key-value pair.
|
||||
// Otherwise, it treats args[0] as a value with a missing key.
|
||||
func argsToAttr(args []any) (Attr, []any) {
|
||||
switch x := args[0].(type) {
|
||||
case string:
|
||||
if len(args) == 1 {
|
||||
return String(badKey, x), nil
|
||||
}
|
||||
return Any(x, args[1]), args[2:]
|
||||
|
||||
case Attr:
|
||||
return x, args[1:]
|
||||
|
||||
default:
|
||||
return Any(badKey, x), args[1:]
|
||||
}
|
||||
}
|
||||
|
||||
// Source describes the location of a line of source code.
|
||||
type Source struct {
|
||||
// Function is the package path-qualified function name containing the
|
||||
// source line. If non-empty, this string uniquely identifies a single
|
||||
// function in the program. This may be the empty string if not known.
|
||||
Function string `json:"function"`
|
||||
// File and Line are the file name and line number (1-based) of the source
|
||||
// line. These may be the empty string and zero, respectively, if not known.
|
||||
File string `json:"file"`
|
||||
Line int `json:"line"`
|
||||
}
|
||||
|
||||
// attrs returns the non-zero fields of s as a slice of attrs.
|
||||
// It is similar to a LogValue method, but we don't want Source
|
||||
// to implement LogValuer because it would be resolved before
|
||||
// the ReplaceAttr function was called.
|
||||
func (s *Source) group() Value {
|
||||
var as []Attr
|
||||
if s.Function != "" {
|
||||
as = append(as, String("function", s.Function))
|
||||
}
|
||||
if s.File != "" {
|
||||
as = append(as, String("file", s.File))
|
||||
}
|
||||
if s.Line != 0 {
|
||||
as = append(as, Int("line", s.Line))
|
||||
}
|
||||
return GroupValue(as...)
|
||||
}
|
||||
|
||||
// source returns a Source for the log event.
|
||||
// If the Record was created without the necessary information,
|
||||
// or if the location is unavailable, it returns a non-nil *Source
|
||||
// with zero fields.
|
||||
func (r Record) source() *Source {
|
||||
fs := runtime.CallersFrames([]uintptr{r.PC})
|
||||
f, _ := fs.Next()
|
||||
return &Source{
|
||||
Function: f.Function,
|
||||
File: f.File,
|
||||
Line: f.Line,
|
||||
}
|
||||
}
|
161
vendor/golang.org/x/exp/slog/text_handler.go
generated
vendored
Normal file
161
vendor/golang.org/x/exp/slog/text_handler.go
generated
vendored
Normal file
|
@ -0,0 +1,161 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// TextHandler is a Handler that writes Records to an io.Writer as a
|
||||
// sequence of key=value pairs separated by spaces and followed by a newline.
|
||||
type TextHandler struct {
|
||||
*commonHandler
|
||||
}
|
||||
|
||||
// NewTextHandler creates a TextHandler that writes to w,
|
||||
// using the given options.
|
||||
// If opts is nil, the default options are used.
|
||||
func NewTextHandler(w io.Writer, opts *HandlerOptions) *TextHandler {
|
||||
if opts == nil {
|
||||
opts = &HandlerOptions{}
|
||||
}
|
||||
return &TextHandler{
|
||||
&commonHandler{
|
||||
json: false,
|
||||
w: w,
|
||||
opts: *opts,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled reports whether the handler handles records at the given level.
|
||||
// The handler ignores records whose level is lower.
|
||||
func (h *TextHandler) Enabled(_ context.Context, level Level) bool {
|
||||
return h.commonHandler.enabled(level)
|
||||
}
|
||||
|
||||
// WithAttrs returns a new TextHandler whose attributes consists
|
||||
// of h's attributes followed by attrs.
|
||||
func (h *TextHandler) WithAttrs(attrs []Attr) Handler {
|
||||
return &TextHandler{commonHandler: h.commonHandler.withAttrs(attrs)}
|
||||
}
|
||||
|
||||
func (h *TextHandler) WithGroup(name string) Handler {
|
||||
return &TextHandler{commonHandler: h.commonHandler.withGroup(name)}
|
||||
}
|
||||
|
||||
// Handle formats its argument Record as a single line of space-separated
|
||||
// key=value items.
|
||||
//
|
||||
// If the Record's time is zero, the time is omitted.
|
||||
// Otherwise, the key is "time"
|
||||
// and the value is output in RFC3339 format with millisecond precision.
|
||||
//
|
||||
// If the Record's level is zero, the level is omitted.
|
||||
// Otherwise, the key is "level"
|
||||
// and the value of [Level.String] is output.
|
||||
//
|
||||
// If the AddSource option is set and source information is available,
|
||||
// the key is "source" and the value is output as FILE:LINE.
|
||||
//
|
||||
// The message's key is "msg".
|
||||
//
|
||||
// To modify these or other attributes, or remove them from the output, use
|
||||
// [HandlerOptions.ReplaceAttr].
|
||||
//
|
||||
// If a value implements [encoding.TextMarshaler], the result of MarshalText is
|
||||
// written. Otherwise, the result of fmt.Sprint is written.
|
||||
//
|
||||
// Keys and values are quoted with [strconv.Quote] if they contain Unicode space
|
||||
// characters, non-printing characters, '"' or '='.
|
||||
//
|
||||
// Keys inside groups consist of components (keys or group names) separated by
|
||||
// dots. No further escaping is performed.
|
||||
// Thus there is no way to determine from the key "a.b.c" whether there
|
||||
// are two groups "a" and "b" and a key "c", or a single group "a.b" and a key "c",
|
||||
// or single group "a" and a key "b.c".
|
||||
// If it is necessary to reconstruct the group structure of a key
|
||||
// even in the presence of dots inside components, use
|
||||
// [HandlerOptions.ReplaceAttr] to encode that information in the key.
|
||||
//
|
||||
// Each call to Handle results in a single serialized call to
|
||||
// io.Writer.Write.
|
||||
func (h *TextHandler) Handle(_ context.Context, r Record) error {
|
||||
return h.commonHandler.handle(r)
|
||||
}
|
||||
|
||||
func appendTextValue(s *handleState, v Value) error {
|
||||
switch v.Kind() {
|
||||
case KindString:
|
||||
s.appendString(v.str())
|
||||
case KindTime:
|
||||
s.appendTime(v.time())
|
||||
case KindAny:
|
||||
if tm, ok := v.any.(encoding.TextMarshaler); ok {
|
||||
data, err := tm.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: avoid the conversion to string.
|
||||
s.appendString(string(data))
|
||||
return nil
|
||||
}
|
||||
if bs, ok := byteSlice(v.any); ok {
|
||||
// As of Go 1.19, this only allocates for strings longer than 32 bytes.
|
||||
s.buf.WriteString(strconv.Quote(string(bs)))
|
||||
return nil
|
||||
}
|
||||
s.appendString(fmt.Sprintf("%+v", v.Any()))
|
||||
default:
|
||||
*s.buf = v.append(*s.buf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// byteSlice returns its argument as a []byte if the argument's
|
||||
// underlying type is []byte, along with a second return value of true.
|
||||
// Otherwise it returns nil, false.
|
||||
func byteSlice(a any) ([]byte, bool) {
|
||||
if bs, ok := a.([]byte); ok {
|
||||
return bs, true
|
||||
}
|
||||
// Like Printf's %s, we allow both the slice type and the byte element type to be named.
|
||||
t := reflect.TypeOf(a)
|
||||
if t != nil && t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 {
|
||||
return reflect.ValueOf(a).Bytes(), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func needsQuoting(s string) bool {
|
||||
if len(s) == 0 {
|
||||
return true
|
||||
}
|
||||
for i := 0; i < len(s); {
|
||||
b := s[i]
|
||||
if b < utf8.RuneSelf {
|
||||
// Quote anything except a backslash that would need quoting in a
|
||||
// JSON string, as well as space and '='
|
||||
if b != '\\' && (b == ' ' || b == '=' || !safeSet[b]) {
|
||||
return true
|
||||
}
|
||||
i++
|
||||
continue
|
||||
}
|
||||
r, size := utf8.DecodeRuneInString(s[i:])
|
||||
if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) {
|
||||
return true
|
||||
}
|
||||
i += size
|
||||
}
|
||||
return false
|
||||
}
|
456
vendor/golang.org/x/exp/slog/value.go
generated
vendored
Normal file
456
vendor/golang.org/x/exp/slog/value.go
generated
vendored
Normal file
|
@ -0,0 +1,456 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// A Value can represent any Go value, but unlike type any,
|
||||
// it can represent most small values without an allocation.
|
||||
// The zero Value corresponds to nil.
|
||||
type Value struct {
|
||||
_ [0]func() // disallow ==
|
||||
// num holds the value for Kinds Int64, Uint64, Float64, Bool and Duration,
|
||||
// the string length for KindString, and nanoseconds since the epoch for KindTime.
|
||||
num uint64
|
||||
// If any is of type Kind, then the value is in num as described above.
|
||||
// If any is of type *time.Location, then the Kind is Time and time.Time value
|
||||
// can be constructed from the Unix nanos in num and the location (monotonic time
|
||||
// is not preserved).
|
||||
// If any is of type stringptr, then the Kind is String and the string value
|
||||
// consists of the length in num and the pointer in any.
|
||||
// Otherwise, the Kind is Any and any is the value.
|
||||
// (This implies that Attrs cannot store values of type Kind, *time.Location
|
||||
// or stringptr.)
|
||||
any any
|
||||
}
|
||||
|
||||
// Kind is the kind of a Value.
|
||||
type Kind int
|
||||
|
||||
// The following list is sorted alphabetically, but it's also important that
|
||||
// KindAny is 0 so that a zero Value represents nil.
|
||||
|
||||
const (
|
||||
KindAny Kind = iota
|
||||
KindBool
|
||||
KindDuration
|
||||
KindFloat64
|
||||
KindInt64
|
||||
KindString
|
||||
KindTime
|
||||
KindUint64
|
||||
KindGroup
|
||||
KindLogValuer
|
||||
)
|
||||
|
||||
var kindStrings = []string{
|
||||
"Any",
|
||||
"Bool",
|
||||
"Duration",
|
||||
"Float64",
|
||||
"Int64",
|
||||
"String",
|
||||
"Time",
|
||||
"Uint64",
|
||||
"Group",
|
||||
"LogValuer",
|
||||
}
|
||||
|
||||
func (k Kind) String() string {
|
||||
if k >= 0 && int(k) < len(kindStrings) {
|
||||
return kindStrings[k]
|
||||
}
|
||||
return "<unknown slog.Kind>"
|
||||
}
|
||||
|
||||
// Unexported version of Kind, just so we can store Kinds in Values.
|
||||
// (No user-provided value has this type.)
|
||||
type kind Kind
|
||||
|
||||
// Kind returns v's Kind.
|
||||
func (v Value) Kind() Kind {
|
||||
switch x := v.any.(type) {
|
||||
case Kind:
|
||||
return x
|
||||
case stringptr:
|
||||
return KindString
|
||||
case timeLocation:
|
||||
return KindTime
|
||||
case groupptr:
|
||||
return KindGroup
|
||||
case LogValuer:
|
||||
return KindLogValuer
|
||||
case kind: // a kind is just a wrapper for a Kind
|
||||
return KindAny
|
||||
default:
|
||||
return KindAny
|
||||
}
|
||||
}
|
||||
|
||||
//////////////// Constructors
|
||||
|
||||
// IntValue returns a Value for an int.
|
||||
func IntValue(v int) Value {
|
||||
return Int64Value(int64(v))
|
||||
}
|
||||
|
||||
// Int64Value returns a Value for an int64.
|
||||
func Int64Value(v int64) Value {
|
||||
return Value{num: uint64(v), any: KindInt64}
|
||||
}
|
||||
|
||||
// Uint64Value returns a Value for a uint64.
|
||||
func Uint64Value(v uint64) Value {
|
||||
return Value{num: v, any: KindUint64}
|
||||
}
|
||||
|
||||
// Float64Value returns a Value for a floating-point number.
|
||||
func Float64Value(v float64) Value {
|
||||
return Value{num: math.Float64bits(v), any: KindFloat64}
|
||||
}
|
||||
|
||||
// BoolValue returns a Value for a bool.
|
||||
func BoolValue(v bool) Value {
|
||||
u := uint64(0)
|
||||
if v {
|
||||
u = 1
|
||||
}
|
||||
return Value{num: u, any: KindBool}
|
||||
}
|
||||
|
||||
// Unexported version of *time.Location, just so we can store *time.Locations in
|
||||
// Values. (No user-provided value has this type.)
|
||||
type timeLocation *time.Location
|
||||
|
||||
// TimeValue returns a Value for a time.Time.
|
||||
// It discards the monotonic portion.
|
||||
func TimeValue(v time.Time) Value {
|
||||
if v.IsZero() {
|
||||
// UnixNano on the zero time is undefined, so represent the zero time
|
||||
// with a nil *time.Location instead. time.Time.Location method never
|
||||
// returns nil, so a Value with any == timeLocation(nil) cannot be
|
||||
// mistaken for any other Value, time.Time or otherwise.
|
||||
return Value{any: timeLocation(nil)}
|
||||
}
|
||||
return Value{num: uint64(v.UnixNano()), any: timeLocation(v.Location())}
|
||||
}
|
||||
|
||||
// DurationValue returns a Value for a time.Duration.
|
||||
func DurationValue(v time.Duration) Value {
|
||||
return Value{num: uint64(v.Nanoseconds()), any: KindDuration}
|
||||
}
|
||||
|
||||
// AnyValue returns a Value for the supplied value.
|
||||
//
|
||||
// If the supplied value is of type Value, it is returned
|
||||
// unmodified.
|
||||
//
|
||||
// Given a value of one of Go's predeclared string, bool, or
|
||||
// (non-complex) numeric types, AnyValue returns a Value of kind
|
||||
// String, Bool, Uint64, Int64, or Float64. The width of the
|
||||
// original numeric type is not preserved.
|
||||
//
|
||||
// Given a time.Time or time.Duration value, AnyValue returns a Value of kind
|
||||
// KindTime or KindDuration. The monotonic time is not preserved.
|
||||
//
|
||||
// For nil, or values of all other types, including named types whose
|
||||
// underlying type is numeric, AnyValue returns a value of kind KindAny.
|
||||
func AnyValue(v any) Value {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
return StringValue(v)
|
||||
case int:
|
||||
return Int64Value(int64(v))
|
||||
case uint:
|
||||
return Uint64Value(uint64(v))
|
||||
case int64:
|
||||
return Int64Value(v)
|
||||
case uint64:
|
||||
return Uint64Value(v)
|
||||
case bool:
|
||||
return BoolValue(v)
|
||||
case time.Duration:
|
||||
return DurationValue(v)
|
||||
case time.Time:
|
||||
return TimeValue(v)
|
||||
case uint8:
|
||||
return Uint64Value(uint64(v))
|
||||
case uint16:
|
||||
return Uint64Value(uint64(v))
|
||||
case uint32:
|
||||
return Uint64Value(uint64(v))
|
||||
case uintptr:
|
||||
return Uint64Value(uint64(v))
|
||||
case int8:
|
||||
return Int64Value(int64(v))
|
||||
case int16:
|
||||
return Int64Value(int64(v))
|
||||
case int32:
|
||||
return Int64Value(int64(v))
|
||||
case float64:
|
||||
return Float64Value(v)
|
||||
case float32:
|
||||
return Float64Value(float64(v))
|
||||
case []Attr:
|
||||
return GroupValue(v...)
|
||||
case Kind:
|
||||
return Value{any: kind(v)}
|
||||
case Value:
|
||||
return v
|
||||
default:
|
||||
return Value{any: v}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////// Accessors
|
||||
|
||||
// Any returns v's value as an any.
|
||||
func (v Value) Any() any {
|
||||
switch v.Kind() {
|
||||
case KindAny:
|
||||
if k, ok := v.any.(kind); ok {
|
||||
return Kind(k)
|
||||
}
|
||||
return v.any
|
||||
case KindLogValuer:
|
||||
return v.any
|
||||
case KindGroup:
|
||||
return v.group()
|
||||
case KindInt64:
|
||||
return int64(v.num)
|
||||
case KindUint64:
|
||||
return v.num
|
||||
case KindFloat64:
|
||||
return v.float()
|
||||
case KindString:
|
||||
return v.str()
|
||||
case KindBool:
|
||||
return v.bool()
|
||||
case KindDuration:
|
||||
return v.duration()
|
||||
case KindTime:
|
||||
return v.time()
|
||||
default:
|
||||
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
// Int64 returns v's value as an int64. It panics
|
||||
// if v is not a signed integer.
|
||||
func (v Value) Int64() int64 {
|
||||
if g, w := v.Kind(), KindInt64; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
return int64(v.num)
|
||||
}
|
||||
|
||||
// Uint64 returns v's value as a uint64. It panics
|
||||
// if v is not an unsigned integer.
|
||||
func (v Value) Uint64() uint64 {
|
||||
if g, w := v.Kind(), KindUint64; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
return v.num
|
||||
}
|
||||
|
||||
// Bool returns v's value as a bool. It panics
|
||||
// if v is not a bool.
|
||||
func (v Value) Bool() bool {
|
||||
if g, w := v.Kind(), KindBool; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
return v.bool()
|
||||
}
|
||||
|
||||
func (a Value) bool() bool {
|
||||
return a.num == 1
|
||||
}
|
||||
|
||||
// Duration returns v's value as a time.Duration. It panics
|
||||
// if v is not a time.Duration.
|
||||
func (a Value) Duration() time.Duration {
|
||||
if g, w := a.Kind(), KindDuration; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
|
||||
return a.duration()
|
||||
}
|
||||
|
||||
func (a Value) duration() time.Duration {
|
||||
return time.Duration(int64(a.num))
|
||||
}
|
||||
|
||||
// Float64 returns v's value as a float64. It panics
|
||||
// if v is not a float64.
|
||||
func (v Value) Float64() float64 {
|
||||
if g, w := v.Kind(), KindFloat64; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
|
||||
return v.float()
|
||||
}
|
||||
|
||||
func (a Value) float() float64 {
|
||||
return math.Float64frombits(a.num)
|
||||
}
|
||||
|
||||
// Time returns v's value as a time.Time. It panics
|
||||
// if v is not a time.Time.
|
||||
func (v Value) Time() time.Time {
|
||||
if g, w := v.Kind(), KindTime; g != w {
|
||||
panic(fmt.Sprintf("Value kind is %s, not %s", g, w))
|
||||
}
|
||||
return v.time()
|
||||
}
|
||||
|
||||
func (v Value) time() time.Time {
|
||||
loc := v.any.(timeLocation)
|
||||
if loc == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return time.Unix(0, int64(v.num)).In(loc)
|
||||
}
|
||||
|
||||
// LogValuer returns v's value as a LogValuer. It panics
|
||||
// if v is not a LogValuer.
|
||||
func (v Value) LogValuer() LogValuer {
|
||||
return v.any.(LogValuer)
|
||||
}
|
||||
|
||||
// Group returns v's value as a []Attr.
|
||||
// It panics if v's Kind is not KindGroup.
|
||||
func (v Value) Group() []Attr {
|
||||
if sp, ok := v.any.(groupptr); ok {
|
||||
return unsafe.Slice((*Attr)(sp), v.num)
|
||||
}
|
||||
panic("Group: bad kind")
|
||||
}
|
||||
|
||||
func (v Value) group() []Attr {
|
||||
return unsafe.Slice((*Attr)(v.any.(groupptr)), v.num)
|
||||
}
|
||||
|
||||
//////////////// Other
|
||||
|
||||
// Equal reports whether v and w represent the same Go value.
|
||||
func (v Value) Equal(w Value) bool {
|
||||
k1 := v.Kind()
|
||||
k2 := w.Kind()
|
||||
if k1 != k2 {
|
||||
return false
|
||||
}
|
||||
switch k1 {
|
||||
case KindInt64, KindUint64, KindBool, KindDuration:
|
||||
return v.num == w.num
|
||||
case KindString:
|
||||
return v.str() == w.str()
|
||||
case KindFloat64:
|
||||
return v.float() == w.float()
|
||||
case KindTime:
|
||||
return v.time().Equal(w.time())
|
||||
case KindAny, KindLogValuer:
|
||||
return v.any == w.any // may panic if non-comparable
|
||||
case KindGroup:
|
||||
return slices.EqualFunc(v.group(), w.group(), Attr.Equal)
|
||||
default:
|
||||
panic(fmt.Sprintf("bad kind: %s", k1))
|
||||
}
|
||||
}
|
||||
|
||||
// append appends a text representation of v to dst.
|
||||
// v is formatted as with fmt.Sprint.
|
||||
func (v Value) append(dst []byte) []byte {
|
||||
switch v.Kind() {
|
||||
case KindString:
|
||||
return append(dst, v.str()...)
|
||||
case KindInt64:
|
||||
return strconv.AppendInt(dst, int64(v.num), 10)
|
||||
case KindUint64:
|
||||
return strconv.AppendUint(dst, v.num, 10)
|
||||
case KindFloat64:
|
||||
return strconv.AppendFloat(dst, v.float(), 'g', -1, 64)
|
||||
case KindBool:
|
||||
return strconv.AppendBool(dst, v.bool())
|
||||
case KindDuration:
|
||||
return append(dst, v.duration().String()...)
|
||||
case KindTime:
|
||||
return append(dst, v.time().String()...)
|
||||
case KindGroup:
|
||||
return fmt.Append(dst, v.group())
|
||||
case KindAny, KindLogValuer:
|
||||
return fmt.Append(dst, v.any)
|
||||
default:
|
||||
panic(fmt.Sprintf("bad kind: %s", v.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
// A LogValuer is any Go value that can convert itself into a Value for logging.
|
||||
//
|
||||
// This mechanism may be used to defer expensive operations until they are
|
||||
// needed, or to expand a single value into a sequence of components.
|
||||
type LogValuer interface {
|
||||
LogValue() Value
|
||||
}
|
||||
|
||||
const maxLogValues = 100
|
||||
|
||||
// Resolve repeatedly calls LogValue on v while it implements LogValuer,
|
||||
// and returns the result.
|
||||
// If v resolves to a group, the group's attributes' values are not recursively
|
||||
// resolved.
|
||||
// If the number of LogValue calls exceeds a threshold, a Value containing an
|
||||
// error is returned.
|
||||
// Resolve's return value is guaranteed not to be of Kind KindLogValuer.
|
||||
func (v Value) Resolve() (rv Value) {
|
||||
orig := v
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
rv = AnyValue(fmt.Errorf("LogValue panicked\n%s", stack(3, 5)))
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < maxLogValues; i++ {
|
||||
if v.Kind() != KindLogValuer {
|
||||
return v
|
||||
}
|
||||
v = v.LogValuer().LogValue()
|
||||
}
|
||||
err := fmt.Errorf("LogValue called too many times on Value of type %T", orig.Any())
|
||||
return AnyValue(err)
|
||||
}
|
||||
|
||||
func stack(skip, nFrames int) string {
|
||||
pcs := make([]uintptr, nFrames+1)
|
||||
n := runtime.Callers(skip+1, pcs)
|
||||
if n == 0 {
|
||||
return "(no stack)"
|
||||
}
|
||||
frames := runtime.CallersFrames(pcs[:n])
|
||||
var b strings.Builder
|
||||
i := 0
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
fmt.Fprintf(&b, "called from %s (%s:%d)\n", frame.Function, frame.File, frame.Line)
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
i++
|
||||
if i >= nFrames {
|
||||
fmt.Fprintf(&b, "(rest of stack elided)\n")
|
||||
break
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
53
vendor/golang.org/x/exp/slog/value_119.go
generated
vendored
Normal file
53
vendor/golang.org/x/exp/slog/value_119.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.19 && !go1.20
|
||||
|
||||
package slog
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type (
|
||||
stringptr unsafe.Pointer // used in Value.any when the Value is a string
|
||||
groupptr unsafe.Pointer // used in Value.any when the Value is a []Attr
|
||||
)
|
||||
|
||||
// StringValue returns a new Value for a string.
|
||||
func StringValue(value string) Value {
|
||||
hdr := (*reflect.StringHeader)(unsafe.Pointer(&value))
|
||||
return Value{num: uint64(hdr.Len), any: stringptr(hdr.Data)}
|
||||
}
|
||||
|
||||
func (v Value) str() string {
|
||||
var s string
|
||||
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
hdr.Data = uintptr(v.any.(stringptr))
|
||||
hdr.Len = int(v.num)
|
||||
return s
|
||||
}
|
||||
|
||||
// String returns Value's value as a string, formatted like fmt.Sprint. Unlike
|
||||
// the methods Int64, Float64, and so on, which panic if v is of the
|
||||
// wrong kind, String never panics.
|
||||
func (v Value) String() string {
|
||||
if sp, ok := v.any.(stringptr); ok {
|
||||
// Inlining this code makes a huge difference.
|
||||
var s string
|
||||
hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
hdr.Data = uintptr(sp)
|
||||
hdr.Len = int(v.num)
|
||||
return s
|
||||
}
|
||||
return string(v.append(nil))
|
||||
}
|
||||
|
||||
// GroupValue returns a new Value for a list of Attrs.
|
||||
// The caller must not subsequently mutate the argument slice.
|
||||
func GroupValue(as ...Attr) Value {
|
||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&as))
|
||||
return Value{num: uint64(hdr.Len), any: groupptr(hdr.Data)}
|
||||
}
|
39
vendor/golang.org/x/exp/slog/value_120.go
generated
vendored
Normal file
39
vendor/golang.org/x/exp/slog/value_120.go
generated
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.20
|
||||
|
||||
package slog
|
||||
|
||||
import "unsafe"
|
||||
|
||||
type (
|
||||
stringptr *byte // used in Value.any when the Value is a string
|
||||
groupptr *Attr // used in Value.any when the Value is a []Attr
|
||||
)
|
||||
|
||||
// StringValue returns a new Value for a string.
|
||||
func StringValue(value string) Value {
|
||||
return Value{num: uint64(len(value)), any: stringptr(unsafe.StringData(value))}
|
||||
}
|
||||
|
||||
// GroupValue returns a new Value for a list of Attrs.
|
||||
// The caller must not subsequently mutate the argument slice.
|
||||
func GroupValue(as ...Attr) Value {
|
||||
return Value{num: uint64(len(as)), any: groupptr(unsafe.SliceData(as))}
|
||||
}
|
||||
|
||||
// String returns Value's value as a string, formatted like fmt.Sprint. Unlike
|
||||
// the methods Int64, Float64, and so on, which panic if v is of the
|
||||
// wrong kind, String never panics.
|
||||
func (v Value) String() string {
|
||||
if sp, ok := v.any.(stringptr); ok {
|
||||
return unsafe.String(sp, v.num)
|
||||
}
|
||||
return string(v.append(nil))
|
||||
}
|
||||
|
||||
func (v Value) str() string {
|
||||
return unsafe.String(v.any.(stringptr), v.num)
|
||||
}
|
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
|
@ -17,9 +17,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4
|
|||
# github.com/dustin/go-humanize v1.0.1
|
||||
## explicit; go 1.16
|
||||
github.com/dustin/go-humanize
|
||||
# github.com/go-logfmt/logfmt v0.6.0
|
||||
## explicit; go 1.17
|
||||
github.com/go-logfmt/logfmt
|
||||
# github.com/goccy/go-json v0.10.2
|
||||
## explicit; go 1.12
|
||||
github.com/goccy/go-json
|
||||
|
@ -145,6 +142,9 @@ golang.org/x/crypto/pbkdf2
|
|||
## explicit; go 1.20
|
||||
golang.org/x/exp/constraints
|
||||
golang.org/x/exp/slices
|
||||
golang.org/x/exp/slog
|
||||
golang.org/x/exp/slog/internal
|
||||
golang.org/x/exp/slog/internal/buffer
|
||||
# golang.org/x/mod v0.12.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/mod/semver
|
||||
|
|
Loading…
Add table
Reference in a new issue