mirror of
https://github.com/docker/cli.git
synced 2026-08-24 10:05:37 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d072fe407f | ||
|
|
3fe73079aa | ||
|
|
28f756087e | ||
|
|
387b8e2af8 | ||
|
|
55e4a7eef5 | ||
|
|
58d9cdd2d9 | ||
|
|
8659aa5d4c | ||
|
|
8d08c75e81 | ||
|
|
24af004cbf | ||
|
|
eb68ed19d2 | ||
|
|
8a375034e2 | ||
|
|
a7c0741fa7 | ||
|
|
12892075d2 | ||
|
|
e1f9b284df | ||
|
|
4f84911bfe | ||
|
|
228f29e810 | ||
|
|
d8c3543407 | ||
|
|
2dd7b7e15b | ||
|
|
a7dcaa6fdb | ||
|
|
400b45f682 | ||
|
|
38887ec4ed | ||
|
|
904aef7f69 | ||
|
|
f08e60eb09 | ||
|
|
abfd89157d | ||
|
|
519eb45d03 | ||
|
|
8717af7168 |
@@ -99,7 +99,7 @@ jobs:
|
|||||||
|
|
||||||
bin-image:
|
bin-image:
|
||||||
if: ${{ github.event_name != 'pull_request' && github.repository == 'docker/cli' }}
|
if: ${{ github.event_name != 'pull_request' && github.repository == 'docker/cli' }}
|
||||||
uses: docker/github-builder/.github/workflows/bake.yml@27ade872c1e2296e62ef15ab3b10d37665e57cf7 # v1.15.0
|
uses: docker/github-builder/.github/workflows/bake.yml@a492c6d04fd3315f67230809b44d60cc0acd50b3 # v1.16.0
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # same as global permission
|
contents: read # same as global permission
|
||||||
id-token: write # for signing attestation(s) and authenticating to Docker Hub with GitHub OIDC Token
|
id-token: write # for signing attestation(s) and authenticating to Docker Hub with GitHub OIDC Token
|
||||||
|
|||||||
@@ -64,18 +64,18 @@ jobs:
|
|||||||
name: Update Go
|
name: Update Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.26.5"
|
go-version: "1.26.7"
|
||||||
cache: false
|
cache: false
|
||||||
-
|
-
|
||||||
name: Initialize CodeQL
|
name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
|
||||||
with:
|
with:
|
||||||
languages: go
|
languages: go
|
||||||
-
|
-
|
||||||
name: Autobuild
|
name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
uses: github/codeql-action/autobuild@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
|
||||||
-
|
-
|
||||||
name: Perform CodeQL Analysis
|
name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
|
||||||
with:
|
with:
|
||||||
category: "/language:go"
|
category: "/language:go"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
review:
|
review:
|
||||||
uses: docker/docker-agent-action/.github/workflows/review-pr.yml@774b6e0e60d6c648b0f2dc43bd5221377a0a7420 # v2.0.2
|
uses: docker/docker-agent-action/.github/workflows/review-pr.yml@baf90543d81f5de59751dfd10e6cf45e21a5a982 # v2.0.3
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # Read repository files and PR diffs
|
contents: read # Read repository files and PR diffs
|
||||||
pull-requests: write # Post review comments
|
pull-requests: write # Post review comments
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.26.5"
|
go-version: "1.26.7"
|
||||||
cache: false
|
cache: false
|
||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ jobs:
|
|||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.26.5"
|
go-version: "1.26.7"
|
||||||
cache: false
|
cache: false
|
||||||
-
|
-
|
||||||
name: Run gocompat check
|
name: Run gocompat check
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ run:
|
|||||||
# which causes it to fallback to go1.17 semantics.
|
# which causes it to fallback to go1.17 semantics.
|
||||||
#
|
#
|
||||||
# TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24
|
# TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24
|
||||||
go: "1.26.5"
|
go: "1.26.7"
|
||||||
|
|
||||||
timeout: 5m
|
timeout: 5m
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -267,7 +267,7 @@ Then you just add a line to every git commit message:
|
|||||||
|
|
||||||
Signed-off-by: Joe Smith <joe.smith@email.com>
|
Signed-off-by: Joe Smith <joe.smith@email.com>
|
||||||
|
|
||||||
Use your real name (sorry, no pseudonyms or anonymous contributions.)
|
Use a known identity (sorry, no anonymous contributions.)
|
||||||
|
|
||||||
If you set your `user.name` and `user.email` git configs, you can sign your
|
If you set your `user.name` and `user.email` git configs, you can sign your
|
||||||
commit automatically with `git commit -s`.
|
commit automatically with `git commit -s`.
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ ARG BASE_VARIANT=alpine
|
|||||||
ARG ALPINE_VERSION=3.23
|
ARG ALPINE_VERSION=3.23
|
||||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||||
|
|
||||||
ARG GO_VERSION=1.26.5
|
ARG GO_VERSION=1.26.7
|
||||||
|
|
||||||
# XX_VERSION specifies the version of the xx utility to use.
|
# XX_VERSION specifies the version of the xx utility to use.
|
||||||
# It must be a valid tag in the docker.io/tonistiigi/xx image repository.
|
# It must be a valid tag in the docker.io/tonistiigi/xx image repository.
|
||||||
|
|||||||
@@ -458,7 +458,7 @@ func comparePorts(i, j container.PortSummary) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if i.IP != j.IP {
|
if i.IP != j.IP {
|
||||||
return i.IP.String() < j.IP.String()
|
return i.IP.Less(j.IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
if i.PublicPort != j.PublicPort {
|
if i.PublicPort != j.PublicPort {
|
||||||
|
|||||||
@@ -946,6 +946,24 @@ func TestDisplayablePorts(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expected: "80/tcp, 80/udp, 1024/tcp, 1024/udp, 12345/sctp, 1.1.1.1:1024->80/tcp, 1.1.1.1:1024->80/udp, 2.1.1.1:1024->80/tcp, 2.1.1.1:1024->80/udp, 1.1.1.1:80->1024/tcp, 1.1.1.1:80->1024/udp, 2.1.1.1:80->1024/tcp, 2.1.1.1:80->1024/udp", //nolint:revive // ignore line-length-limit (revive)
|
expected: "80/tcp, 80/udp, 1024/tcp, 1024/udp, 12345/sctp, 1.1.1.1:1024->80/tcp, 1.1.1.1:1024->80/udp, 2.1.1.1:1024->80/tcp, 2.1.1.1:1024->80/udp, 1.1.1.1:80->1024/tcp, 1.1.1.1:80->1024/udp, 2.1.1.1:80->1024/tcp, 2.1.1.1:80->1024/udp", //nolint:revive // ignore line-length-limit (revive)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// host IPs are ordered numerically, not lexicographically:
|
||||||
|
// "10.0.0.2" sorts as a string before "9.0.0.1".
|
||||||
|
ports: []container.PortSummary{
|
||||||
|
{
|
||||||
|
IP: netip.MustParseAddr("10.0.0.2"),
|
||||||
|
PublicPort: 8080,
|
||||||
|
PrivatePort: 80,
|
||||||
|
Type: "tcp",
|
||||||
|
}, {
|
||||||
|
IP: netip.MustParseAddr("9.0.0.1"),
|
||||||
|
PublicPort: 8081,
|
||||||
|
PrivatePort: 80,
|
||||||
|
Type: "tcp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "9.0.0.1:8081->80/tcp, 10.0.0.2:8080->80/tcp",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, port := range cases {
|
for _, port := range cases {
|
||||||
|
|||||||
@@ -48,27 +48,6 @@ func newManifestStore(dockerCLI command.Cli) store.Store {
|
|||||||
return store.NewStore(filepath.Join(config.Dir(), "manifests"))
|
return store.NewStore(filepath.Join(config.Dir(), "manifests"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// authConfigKey is the key used to store credentials for Docker Hub. It is
|
|
||||||
// a copy of [registry.IndexServer].
|
|
||||||
//
|
|
||||||
// [registry.IndexServer]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#IndexServer
|
|
||||||
const authConfigKey = "https://index.docker.io/v1/"
|
|
||||||
|
|
||||||
// getAuthConfigKey special-cases using the full index address of the official
|
|
||||||
// index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
|
|
||||||
//
|
|
||||||
// It is similar to [registry.GetAuthConfigKey], but does not require on
|
|
||||||
// [registrytypes.IndexInfo] as intermediate.
|
|
||||||
//
|
|
||||||
// [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/registry#GetAuthConfigKey
|
|
||||||
// [registrytypes.IndexInfo]: https://pkg.go.dev/github.com/docker/docker@v28.3.3+incompatible/api/types/registry#IndexInfo
|
|
||||||
func getAuthConfigKey(domainName string) string {
|
|
||||||
if domainName == "docker.io" || domainName == "index.docker.io" {
|
|
||||||
return authConfigKey
|
|
||||||
}
|
|
||||||
return domainName
|
|
||||||
}
|
|
||||||
|
|
||||||
// newRegistryClient returns a client for communicating with a Docker distribution
|
// newRegistryClient returns a client for communicating with a Docker distribution
|
||||||
// registry
|
// registry
|
||||||
func newRegistryClient(dockerCLI command.Cli, allowInsecure bool) registryclient.RegistryClient {
|
func newRegistryClient(dockerCLI command.Cli, allowInsecure bool) registryclient.RegistryClient {
|
||||||
@@ -78,8 +57,7 @@ func newRegistryClient(dockerCLI command.Cli, allowInsecure bool) registryclient
|
|||||||
}
|
}
|
||||||
cfg := dockerCLI.ConfigFile()
|
cfg := dockerCLI.ConfigFile()
|
||||||
resolver := func(ctx context.Context, domainName string) registry.AuthConfig {
|
resolver := func(ctx context.Context, domainName string) registry.AuthConfig {
|
||||||
configKey := getAuthConfigKey(domainName)
|
a, _ := cfg.GetAuthConfig(domainName)
|
||||||
a, _ := cfg.GetAuthConfig(configKey)
|
|
||||||
return registry.AuthConfig{
|
return registry.AuthConfig{
|
||||||
Username: a.Username,
|
Username: a.Username,
|
||||||
Password: a.Password,
|
Password: a.Password,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -687,15 +688,13 @@ func (options *serviceOptions) makeEnv() ([]string, error) {
|
|||||||
}
|
}
|
||||||
currentEnv := make([]string, 0, len(envVariables))
|
currentEnv := make([]string, 0, len(envVariables))
|
||||||
for _, env := range envVariables { // need to process each var, in order
|
for _, env := range envVariables { // need to process each var, in order
|
||||||
k, _, _ := strings.Cut(env, "=")
|
if slices.Contains(currentEnv, env) {
|
||||||
for i, current := range currentEnv { // remove duplicates
|
continue // no update required, may hide this behind flag to preserve order of envVariables
|
||||||
if current == env {
|
|
||||||
continue // no update required, may hide this behind flag to preserve order of envVariables
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(current, k+"=") {
|
|
||||||
currentEnv = append(currentEnv[:i], currentEnv[i+1:]...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
k, _, _ := strings.Cut(env, "=")
|
||||||
|
currentEnv = slices.DeleteFunc(currentEnv, func(current string) bool { // remove duplicates
|
||||||
|
return strings.HasPrefix(current, k+"=")
|
||||||
|
})
|
||||||
currentEnv = append(currentEnv, env)
|
currentEnv = append(currentEnv, env)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -373,3 +373,49 @@ func TestToServiceSysCtls(t *testing.T) {
|
|||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Sysctls, expected))
|
assert.Check(t, is.DeepEqual(service.TaskTemplate.ContainerSpec.Sysctls, expected))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMakeEnv(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
doc string
|
||||||
|
env []string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
doc: "no duplicates",
|
||||||
|
env: []string{"one=1", "two=2"},
|
||||||
|
expected: []string{"one=1", "two=2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "same variable repeated",
|
||||||
|
env: []string{"one=1", "one=1"},
|
||||||
|
expected: []string{"one=1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "same variable repeated, then overridden",
|
||||||
|
env: []string{"one=1", "one=1", "one=2"},
|
||||||
|
expected: []string{"one=2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "repeated variable last",
|
||||||
|
env: []string{"one=1", "two=2", "two=2"},
|
||||||
|
expected: []string{"one=1", "two=2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "last value wins",
|
||||||
|
env: []string{"one=1", "two=2", "one=3"},
|
||||||
|
expected: []string{"two=2", "one=3"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
|
o := newServiceOptions()
|
||||||
|
for _, env := range tc.env {
|
||||||
|
assert.NilError(t, o.env.Set(env))
|
||||||
|
}
|
||||||
|
actual, err := o.makeEnv()
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Check(t, is.DeepEqual(tc.expected, actual))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1213,11 +1213,9 @@ func updateHosts(flags *pflag.FlagSet, hosts *[]string) error {
|
|||||||
if rm.IPAddr != "" && rm.IPAddr != ip {
|
if rm.IPAddr != "" && rm.IPAddr != ip {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for i, h := range hostNames {
|
hostNames = slices.DeleteFunc(hostNames, func(h string) bool {
|
||||||
if h == rm.Host {
|
return h == rm.Host
|
||||||
hostNames = append(hostNames[:i], hostNames[i+1:]...)
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(hostNames) > 0 {
|
if len(hostNames) > 0 {
|
||||||
newHosts = append(newHosts, fmt.Sprintf("%s %s", ip, strings.Join(hostNames, " ")))
|
newHosts = append(newHosts, fmt.Sprintf("%s %s", ip, strings.Join(hostNames, " ")))
|
||||||
|
|||||||
@@ -1727,3 +1727,18 @@ func TestUpdateUlimits(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpdateHostsRemoveRepeatedHost(t *testing.T) {
|
||||||
|
flags := newUpdateCommand(nil).Flags()
|
||||||
|
flags.Set("host-rm", "host1")
|
||||||
|
|
||||||
|
//nolint:dupword // ignore "Duplicate words (host1) found"
|
||||||
|
hosts := []string{"127.0.0.1 host1 host1 host2", "127.0.0.2 host2 host1 host1"}
|
||||||
|
|
||||||
|
err := updateHosts(flags, &hosts)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
// All occurrences of `host1` should be removed, also if the same host
|
||||||
|
// is listed multiple times in the same entry.
|
||||||
|
assert.Check(t, is.DeepEqual([]string{"127.0.0.1 host2", "127.0.0.2 host2"}, hosts))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.26.5
|
ARG GO_VERSION=1.26.7
|
||||||
|
|
||||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.26.5
|
ARG GO_VERSION=1.26.7
|
||||||
|
|
||||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.26.5
|
ARG GO_VERSION=1.26.7
|
||||||
|
|
||||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||||
|
|||||||
+2
-2
@@ -31,7 +31,7 @@ require (
|
|||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/mattn/go-runewidth v0.0.24
|
github.com/mattn/go-runewidth v0.0.24
|
||||||
github.com/moby/go-archive v0.3.2
|
github.com/moby/go-archive v0.3.3
|
||||||
github.com/moby/moby/api v1.55.0
|
github.com/moby/moby/api v1.55.0
|
||||||
github.com/moby/moby/client v0.5.1
|
github.com/moby/moby/client v0.5.1
|
||||||
github.com/moby/patternmatcher v0.6.1
|
github.com/moby/patternmatcher v0.6.1
|
||||||
@@ -46,7 +46,7 @@ require (
|
|||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.1.1
|
github.com/opencontainers/image-spec v1.1.1
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
|
||||||
github.com/sirupsen/logrus v1.9.4
|
github.com/sirupsen/logrus v1.10.1
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/spf13/pflag v1.0.10
|
github.com/spf13/pflag v1.0.10
|
||||||
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346
|
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346
|
||||||
|
|||||||
+10
-8
@@ -32,7 +32,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
|
|||||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
@@ -107,8 +106,8 @@ github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhg
|
|||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
github.com/moby/go-archive v0.3.2 h1:x893kC3zRygv2C+k4Y9kMxYRPLCj4XEJB0srbAP06Hw=
|
github.com/moby/go-archive v0.3.3 h1:OxxR9paxsluYi+zDUEXTTaIxtkK3viymW+Ka7vRhhME=
|
||||||
github.com/moby/go-archive v0.3.2/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE=
|
github.com/moby/go-archive v0.3.3/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE=
|
||||||
github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=
|
github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=
|
||||||
github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||||
github.com/moby/moby/client v0.5.1 h1:tYNaJno4c0HXz12y5BiqEDy0rVTYkWzI26lGvnTMiJw=
|
github.com/moby/moby/client v0.5.1 h1:tYNaJno4c0HXz12y5BiqEDy0rVTYkWzI26lGvnTMiJw=
|
||||||
@@ -121,6 +120,10 @@ github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w
|
|||||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||||
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
|
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
|
||||||
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
|
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
|
||||||
|
github.com/moby/sys/mount v0.3.5 h1:eS3fsZTjHaBihwjp4/+5Z3jxqLXYsbwxqpVSfFv3M00=
|
||||||
|
github.com/moby/sys/mount v0.3.5/go.mod h1:WUQDO+/uCiCIkIztx8SrwIDVn2dtMFRBebRhpDFT71M=
|
||||||
|
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
||||||
|
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||||
github.com/moby/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8=
|
github.com/moby/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8=
|
||||||
github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o=
|
github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o=
|
||||||
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
|
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
|
||||||
@@ -149,7 +152,6 @@ github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgr
|
|||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
@@ -172,8 +174,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG
|
|||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
github.com/sirupsen/logrus v1.10.1 h1:xi4336Zh11WpU14fXR6I67V3yaTPQYwRx2WEtHbRg4Q=
|
||||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
github.com/sirupsen/logrus v1.10.1/go.mod h1:vsQHnG7xzNsxk3NrwboUiWPnIC3dmbjcGPykD7+tiHk=
|
||||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
@@ -183,8 +185,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw=
|
||||||
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346 h1:TvtdmeYsYEij78hS4oxnwikoiLdIrgav3BA+CbhaDAI=
|
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346 h1:TvtdmeYsYEij78hS4oxnwikoiLdIrgav3BA+CbhaDAI=
|
||||||
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346/go.mod h1:xKQhd7snlzKFuUi1taTGWjpRE8iFTA06DeacYi3CVFQ=
|
github.com/tonistiigi/go-rosetta v0.0.0-20220804170347-3f4430f2d346/go.mod h1:xKQhd7snlzKFuUi1taTGWjpRE8iFTA06DeacYi3CVFQ=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
|
|||||||
+25
-1
@@ -17,6 +17,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/log"
|
"github.com/containerd/log"
|
||||||
|
"github.com/moby/go-archive/internal/archiveoptions"
|
||||||
"github.com/moby/patternmatcher"
|
"github.com/moby/patternmatcher"
|
||||||
"github.com/moby/sys/sequential"
|
"github.com/moby/sys/sequential"
|
||||||
"github.com/moby/sys/user"
|
"github.com/moby/sys/user"
|
||||||
@@ -81,9 +82,22 @@ type (
|
|||||||
// were probably in the archive for a reason, so set this option at
|
// were probably in the archive for a reason, so set this option at
|
||||||
// your own peril.
|
// your own peril.
|
||||||
BestEffortXattrs bool
|
BestEffortXattrs bool
|
||||||
|
|
||||||
|
// internalOptions contains options for use by packages within this module.
|
||||||
|
internalOptions *archiveoptions.Options
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// WithProcSelfFD returns a copy of opts prepared for extraction in a
|
||||||
|
// filesystem context where /proc/self/fd may not be accessible by path.
|
||||||
|
//
|
||||||
|
// The caller must invoke the returned cleanup function after extraction
|
||||||
|
// completes. On platforms that do not use /proc/self/fd for extraction,
|
||||||
|
// the returned cleanup function is a no-op.
|
||||||
|
func WithProcSelfFD(opts *TarOptions) (*TarOptions, func(), error) {
|
||||||
|
return withProcSelfFD(opts)
|
||||||
|
}
|
||||||
|
|
||||||
// Archiver implements the Archiver interface and allows the reuse of most utility functions of
|
// Archiver implements the Archiver interface and allows the reuse of most utility functions of
|
||||||
// this package with a pluggable Untar function. Also, to facilitate the passing of specific id
|
// this package with a pluggable Untar function. Also, to facilitate the passing of specific id
|
||||||
// mappings for untar, an Archiver can be created with maps which will then be passed to Untar operations.
|
// mappings for untar, an Archiver can be created with maps which will then be passed to Untar operations.
|
||||||
@@ -509,6 +523,14 @@ func resolveArchivePath(root *os.Root, name string) (string, error) {
|
|||||||
// the native, root-relative filesystem path used for extraction.
|
// the native, root-relative filesystem path used for extraction.
|
||||||
func resolveHardlinkTarget(root *os.Root, linkname string) (string, error) {
|
func resolveHardlinkTarget(root *os.Root, linkname string) (string, error) {
|
||||||
cleaned := path.Clean(linkname)
|
cleaned := path.Clean(linkname)
|
||||||
|
if strings.HasPrefix(cleaned, "/") {
|
||||||
|
// Some image builders (e.g. kaniko) write hardlink targets as absolute
|
||||||
|
// paths. Resolve those relative to the extraction root, with chroot-like
|
||||||
|
// semantics matching absolute symlink targets. Strip the root from the
|
||||||
|
// original linkname rather than the cleaned one so that ".." components
|
||||||
|
// are not collapsed against "/" but instead rejected below.
|
||||||
|
cleaned = path.Clean(strings.TrimLeft(linkname, "/"))
|
||||||
|
}
|
||||||
if cleaned == "." || !filepath.IsLocal(cleaned) {
|
if cleaned == "." || !filepath.IsLocal(cleaned) {
|
||||||
return "", breakoutError(fmt.Errorf("invalid hardlink target %q", linkname))
|
return "", breakoutError(fmt.Errorf("invalid hardlink target %q", linkname))
|
||||||
}
|
}
|
||||||
@@ -523,6 +545,7 @@ func createTarFile(root *os.Root, dstPath string, hdr *tar.Header, reader io.Rea
|
|||||||
Lchown = true
|
Lchown = true
|
||||||
inUserns, bestEffortXattrs bool
|
inUserns, bestEffortXattrs bool
|
||||||
chownOpts *ChownOpts
|
chownOpts *ChownOpts
|
||||||
|
internalOpts *archiveoptions.Options
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(thaJeztah): make opts a required argument.
|
// TODO(thaJeztah): make opts a required argument.
|
||||||
@@ -531,6 +554,7 @@ func createTarFile(root *os.Root, dstPath string, hdr *tar.Header, reader io.Rea
|
|||||||
inUserns = opts.InUserNS // TODO(thaJeztah): consider deprecating opts.InUserNS and detect locally.
|
inUserns = opts.InUserNS // TODO(thaJeztah): consider deprecating opts.InUserNS and detect locally.
|
||||||
chownOpts = opts.ChownOpts
|
chownOpts = opts.ChownOpts
|
||||||
bestEffortXattrs = opts.BestEffortXattrs
|
bestEffortXattrs = opts.BestEffortXattrs
|
||||||
|
internalOpts = opts.internalOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// hdr.Mode is in linux format, which we can use for sycalls,
|
// hdr.Mode is in linux format, which we can use for sycalls,
|
||||||
@@ -672,7 +696,7 @@ func createTarFile(root *os.Root, dstPath string, hdr *tar.Header, reader io.Rea
|
|||||||
|
|
||||||
// There is no LChmod, so ignore mode for symlink. Also, this
|
// There is no LChmod, so ignore mode for symlink. Also, this
|
||||||
// must happen after chown, as that can modify the file mode
|
// must happen after chown, as that can modify the file mode
|
||||||
if err := handleLChmod(root, dstPath, hardlinkTarget, hdr, hdrInfo); err != nil {
|
if err := handleLChmod(root, dstPath, hardlinkTarget, hdr, hdrInfo, internalOpts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
@@ -8,10 +8,28 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/moby/go-archive/internal/archiveoptions"
|
||||||
"github.com/moby/sys/userns"
|
"github.com/moby/sys/userns"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func withProcSelfFD(opts *TarOptions) (*TarOptions, func(), error) {
|
||||||
|
procSelfFD, err := os.Open("/proc/self/fd")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var prepared TarOptions
|
||||||
|
if opts != nil {
|
||||||
|
prepared = *opts
|
||||||
|
}
|
||||||
|
prepared.internalOptions = &archiveoptions.Options{
|
||||||
|
ProcSelfFD: procSelfFD,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &prepared, func() { _ = procSelfFD.Close() }, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
|
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
|
||||||
if format == OverlayWhiteoutFormat {
|
if format == OverlayWhiteoutFormat {
|
||||||
return newOverlayWhiteoutConverter()
|
return newOverlayWhiteoutConverter()
|
||||||
|
|||||||
+8
@@ -2,6 +2,14 @@
|
|||||||
|
|
||||||
package archive
|
package archive
|
||||||
|
|
||||||
|
func withProcSelfFD(opts *TarOptions) (*TarOptions, func(), error) {
|
||||||
|
var prepared TarOptions
|
||||||
|
if opts != nil {
|
||||||
|
prepared = *opts
|
||||||
|
}
|
||||||
|
return &prepared, func() {}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
|
func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-17
@@ -12,6 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/moby/go-archive/internal/archiveoptions"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -87,7 +88,7 @@ func handleTarTypeBlockCharFifo(root *os.Root, hdr *tar.Header, dstPath string)
|
|||||||
// handleLChmod applies the mode from hdrInfo to dstPath within root, skipping
|
// handleLChmod applies the mode from hdrInfo to dstPath within root, skipping
|
||||||
// symlinks (there is no lchmod). For hardlinks, the mode is applied only when
|
// symlinks (there is no lchmod). For hardlinks, the mode is applied only when
|
||||||
// the link target is itself not a symlink.
|
// the link target is itself not a symlink.
|
||||||
func handleLChmod(root *os.Root, dstPath string, hardlinkTarget string, hdr *tar.Header, hdrInfo os.FileInfo) error {
|
func handleLChmod(root *os.Root, dstPath string, hardlinkTarget string, hdr *tar.Header, hdrInfo os.FileInfo, opts *archiveoptions.Options) error {
|
||||||
switch hdr.Typeflag {
|
switch hdr.Typeflag {
|
||||||
case tar.TypeSymlink:
|
case tar.TypeSymlink:
|
||||||
return nil
|
return nil
|
||||||
@@ -99,17 +100,17 @@ func handleLChmod(root *os.Root, dstPath string, hardlinkTarget string, hdr *tar
|
|||||||
if err != nil || fi.Mode()&os.ModeSymlink != 0 {
|
if err != nil || fi.Mode()&os.ModeSymlink != 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return chmodNoSymlink(root, dstPath, hdrInfo.Mode())
|
return chmodNoSymlink(root, dstPath, hdrInfo.Mode(), opts)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return chmodNoSymlink(root, dstPath, hdrInfo.Mode())
|
return chmodNoSymlink(root, dstPath, hdrInfo.Mode(), opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// chmodNoSymlink applies mode to a non-symlink entry.
|
// chmodNoSymlink applies mode to a non-symlink entry.
|
||||||
//
|
//
|
||||||
// Callers must have already excluded symlink entries.
|
// Callers must have already excluded symlink entries.
|
||||||
func chmodNoSymlink(root *os.Root, name string, mode os.FileMode) error {
|
func chmodNoSymlink(root *os.Root, name string, mode os.FileMode, opts *archiveoptions.Options) error {
|
||||||
parent, err := root.OpenFile(filepath.Dir(name), os.O_RDONLY, 0)
|
parent, err := root.OpenFile(filepath.Dir(name), os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -126,19 +127,7 @@ func chmodNoSymlink(root *os.Root, name string, mode os.FileMode) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback for systems that cannot perform fchmodat with AT_SYMLINK_NOFOLLOW.
|
// Fallback for systems that cannot perform fchmodat with AT_SYMLINK_NOFOLLOW.
|
||||||
// Open the entry without following symlinks and apply the mode through the
|
return chmodNoSymlinkFallback(int(parent.Fd()), base, name, perm, opts) // #nosec G115 -- ignore integer overflow conversion for parent.Fd
|
||||||
// resulting file descriptor.
|
|
||||||
// #nosec G115 -- ignore integer overflow conversion for parent.Fd
|
|
||||||
fd, err := unix.Openat(int(parent.Fd()), base, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK, 0)
|
|
||||||
if err != nil {
|
|
||||||
return &os.PathError{Op: "openat", Path: name, Err: err}
|
|
||||||
}
|
|
||||||
defer unix.Close(fd)
|
|
||||||
|
|
||||||
if err := unix.Fchmod(fd, perm); err != nil {
|
|
||||||
return &os.PathError{Op: "fchmod", Path: name, Err: err}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileModeToPerm returns the subset of an os.FileMode that can be applied
|
// fileModeToPerm returns the subset of an os.FileMode that can be applied
|
||||||
|
|||||||
+1
-1
@@ -53,7 +53,7 @@ func handleTarTypeBlockCharFifo(root *os.Root, hdr *tar.Header, path string) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleLChmod is a no-op on Windows because chmod is not supported.
|
// handleLChmod is a no-op on Windows because chmod is not supported.
|
||||||
func handleLChmod(root *os.Root, dstPath string, hardlinkTarget string, hdr *tar.Header, hdrInfo os.FileInfo) error {
|
func handleLChmod(root *os.Root, dstPath string, hardlinkTarget string, hdr *tar.Header, hdrInfo os.FileInfo, opts any) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
package archive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/moby/go-archive/internal/archiveoptions"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// chmodNoSymlinkFallback applies mode without following the final path
|
||||||
|
// component on systems without fchmodat2 support.
|
||||||
|
//
|
||||||
|
// Callers must have already excluded symlink entries.
|
||||||
|
func chmodNoSymlinkFallback(parentFD int, base, name string, perm uint32, opts *archiveoptions.Options) error {
|
||||||
|
fd, err := unix.Openat(parentFD, base, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||||
|
if err != nil {
|
||||||
|
return &os.PathError{Op: "openat", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
defer unix.Close(fd)
|
||||||
|
|
||||||
|
if opts != nil && opts.ProcSelfFD != nil {
|
||||||
|
err := unix.Fchmodat(int(opts.ProcSelfFD.Fd()), strconv.Itoa(fd), perm, 0)
|
||||||
|
// Keep the os.File alive until fchmodat has finished using its descriptor.
|
||||||
|
runtime.KeepAlive(opts.ProcSelfFD)
|
||||||
|
if err != nil {
|
||||||
|
return &os.PathError{
|
||||||
|
Op: "fchmodat",
|
||||||
|
Path: name,
|
||||||
|
Err: fmt.Errorf("via pre-opened /proc/self/fd/%d: %w", fd, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
procPath := "/proc/self/fd/" + strconv.Itoa(fd)
|
||||||
|
if err := unix.Chmod(procPath, perm); err != nil {
|
||||||
|
return &os.PathError{
|
||||||
|
Op: "chmod",
|
||||||
|
Path: name,
|
||||||
|
Err: fmt.Errorf("via %s: %w", procPath, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
//go:build !linux && !windows
|
||||||
|
|
||||||
|
package archive
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/moby/go-archive/internal/archiveoptions"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// chmodNoSymlinkFallback applies mode without following the final path
|
||||||
|
// component on systems without fchmodat2 support.
|
||||||
|
//
|
||||||
|
// Callers must have already excluded symlink entries.
|
||||||
|
func chmodNoSymlinkFallback(parentFD int, base, name string, perm uint32, _ *archiveoptions.Options) error {
|
||||||
|
fd, err := unix.Openat(parentFD, base, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK|unix.O_CLOEXEC, 0)
|
||||||
|
if err != nil {
|
||||||
|
return &os.PathError{Op: "openat", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
defer unix.Close(fd)
|
||||||
|
|
||||||
|
if err := unix.Fchmod(fd, perm); err != nil {
|
||||||
|
return &os.PathError{Op: "fchmod", Path: name, Err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
// Package archiveoptions defines internal options shared between archive and
|
||||||
|
// chrootarchive.
|
||||||
|
package archiveoptions
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// Options contains extraction resources supplied by internal callers.
|
||||||
|
type Options struct {
|
||||||
|
// ProcSelfFD references /proc/self/fd as opened before entering a chroot.
|
||||||
|
// The caller retains ownership of the file.
|
||||||
|
ProcSelfFD *os.File
|
||||||
|
}
|
||||||
+10
-38
@@ -1,12 +1,9 @@
|
|||||||
version: "2"
|
version: "2"
|
||||||
run:
|
|
||||||
tests: false
|
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- asasalint
|
- asasalint
|
||||||
- asciicheck
|
- asciicheck
|
||||||
- bidichk
|
- bidichk
|
||||||
- bodyclose
|
|
||||||
- contextcheck
|
- contextcheck
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- errchkjson
|
- errchkjson
|
||||||
@@ -22,46 +19,21 @@ linters:
|
|||||||
- nilerr
|
- nilerr
|
||||||
- nilnesserr
|
- nilnesserr
|
||||||
- noctx
|
- noctx
|
||||||
- protogetter
|
|
||||||
- reassign
|
- reassign
|
||||||
- recvcheck
|
- recvcheck
|
||||||
- rowserrcheck
|
|
||||||
- spancheck
|
|
||||||
- sqlclosecheck
|
|
||||||
- testifylint
|
- testifylint
|
||||||
- unparam
|
- unparam
|
||||||
- zerologlint
|
|
||||||
disable:
|
|
||||||
- prealloc
|
|
||||||
settings:
|
|
||||||
errcheck:
|
|
||||||
check-type-assertions: false
|
|
||||||
check-blank: false
|
|
||||||
lll:
|
|
||||||
line-length: 100
|
|
||||||
tab-width: 4
|
|
||||||
prealloc:
|
|
||||||
simple: false
|
|
||||||
range-loops: false
|
|
||||||
for-loops: false
|
|
||||||
whitespace:
|
|
||||||
multi-if: false
|
|
||||||
multi-func: false
|
|
||||||
exclusions:
|
exclusions:
|
||||||
generated: lax
|
|
||||||
presets:
|
presets:
|
||||||
- comments
|
|
||||||
- common-false-positives
|
|
||||||
- legacy
|
- legacy
|
||||||
- std-error-handling
|
- std-error-handling
|
||||||
paths:
|
rules:
|
||||||
- third_party$
|
# Exclude some linters from running on tests files.
|
||||||
- builtin$
|
- path: _test\.go
|
||||||
- examples$
|
linters:
|
||||||
formatters:
|
- gosec
|
||||||
exclusions:
|
- musttag
|
||||||
generated: lax
|
- noctx # TODO: enable once we switch to Go 1.24+.
|
||||||
paths:
|
- linters: # TODO: remove once golangci-lint is updated with https://github.com/golangci/golangci-lint/pull/6584
|
||||||
- third_party$
|
- gocheckcompilerdirectives
|
||||||
- builtin$
|
text: 'compiler directive unrecognized: //go:fix'
|
||||||
- examples$
|
|
||||||
|
|||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
language: go
|
|
||||||
go_import_path: github.com/sirupsen/logrus
|
|
||||||
git:
|
|
||||||
depth: 1
|
|
||||||
env:
|
|
||||||
- GO111MODULE=on
|
|
||||||
go: 1.15.x
|
|
||||||
os: linux
|
|
||||||
install:
|
|
||||||
- ./travis/install.sh
|
|
||||||
script:
|
|
||||||
- cd ci
|
|
||||||
- go run mage.go -v -w ../ crossBuild
|
|
||||||
- go run mage.go -v -w ../ lint
|
|
||||||
- go run mage.go -v -w ../ test
|
|
||||||
+201
-38
@@ -1,90 +1,246 @@
|
|||||||
# 1.8.1
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## 1.10.1
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
* Fix a regression introduced in v1.10.0 where `TextFormatter` could panic
|
||||||
|
when formatting nil or panicking `error` and `fmt.Stringer` values.
|
||||||
|
* Allow function-backed implementations of `error` as field values.
|
||||||
|
|
||||||
|
## 1.10.0
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
* Fix reentrant logging deadlocks in formatter paths.
|
||||||
|
* Fix race conditions in formatter and entry handling.
|
||||||
|
* Fix generic `Log`, `Logf`, `Logln`, and `LogFn` methods unexpectedly
|
||||||
|
panicking when called with `PanicLevel`. Use the corresponding `Panic`
|
||||||
|
methods when panic behavior is desired.
|
||||||
|
* Improve concurrency safety around formatter and hook access.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* Add `slog` hook for forwarding Logrus entries to `log/slog`.
|
||||||
|
* Add `slog.Handler` for forwarding `log/slog` records to a Logrus logger,
|
||||||
|
including levels, fields, groups, context, time, and optional caller
|
||||||
|
reporting. The hook and handler can also be combined to help migrate
|
||||||
|
between Logrus and `log/slog`.
|
||||||
|
* Add minimal, composable logging interfaces for each log level. This enables
|
||||||
|
consumers to depend on narrower interfaces, making it easier to substitute
|
||||||
|
or adapt logging implementations.
|
||||||
|
* Allow `Entry.Caller` to be set explicitly and preserve it across derived
|
||||||
|
entries, enabling custom caller detection without Logrus overwriting
|
||||||
|
caller information when `ReportCaller` is enabled.
|
||||||
|
|
||||||
|
Changed:
|
||||||
|
|
||||||
|
* Raise minimum supported Go version to 1.23.
|
||||||
|
* TextFormatter now renders `[]byte` values as raw/quoted strings instead of slice-of-ints.
|
||||||
|
* TextFormatter now uses distinct dimmed colors for debug and trace output.
|
||||||
|
* TextFormatter now automatically enables colors on Windows terminals with ANSI support,
|
||||||
|
matching the behavior on other platforms.
|
||||||
|
* `Entry.HasCaller` is now deprecated in favor of checking `Entry.Caller` directly.
|
||||||
|
* Deprecated `MutexWrap`, which was unintentionally exposed as public API.
|
||||||
|
It remains available as an alias for compatibility but should not be used
|
||||||
|
directly.
|
||||||
|
|
||||||
|
Performance:
|
||||||
|
|
||||||
|
* Significantly improve TextFormatter performance and reduce allocations.
|
||||||
|
* Optimize common Entry and Logger hot paths.
|
||||||
|
* Reduce allocations in caller reporting.
|
||||||
|
* ~17% lower geomean runtime and ~27% higher formatter throughput overall.
|
||||||
|
* Common enabled logging paths are ~30–44% faster.
|
||||||
|
* TextFormatter paths are up to ~40% faster, with allocation counts reduced
|
||||||
|
by 25–74% across the measured formatter cases.
|
||||||
|
|
||||||
|
|
||||||
|
## 1.9.4
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
* Remove uses of deprecated `ioutil` package
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* Add GNU/Hurd support
|
||||||
|
* Add WASI wasip1 support
|
||||||
|
|
||||||
Code quality:
|
Code quality:
|
||||||
|
|
||||||
|
* Update minimum supported Go version to 1.17
|
||||||
|
* Documentation updates
|
||||||
|
|
||||||
|
|
||||||
|
## 1.9.3
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
* Re-apply fix for potential denial of service in logrus.Writer() when logging >64KB single-line payloads without newlines (#1376)
|
||||||
|
* Fix panic in Writer
|
||||||
|
|
||||||
|
|
||||||
|
## 1.9.2
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
* Revert Writer DoS fix (#1376) due to regression
|
||||||
|
|
||||||
|
|
||||||
|
## 1.9.1
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
* Fix potential denial of service in logrus.Writer() when logging >64KB single-line payloads without newlines (#1376)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.9.0
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
* Multiple concurrency and race condition fixes
|
||||||
|
* Improve Windows terminal and ANSI handling
|
||||||
|
|
||||||
|
Code quality:
|
||||||
|
|
||||||
|
* Internal cleanups and modernization
|
||||||
|
|
||||||
|
|
||||||
|
## 1.8.3
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
* Fix potential denial of service in logrus.Writer() when logging >64KB single-line payloads without newlines (#1376)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.8.2
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* Add support for the logger private buffer pool (#1253)
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
|
||||||
|
* Fix race condition for SetFormatter and SetReportCaller
|
||||||
|
* Fix data race in hooks test package
|
||||||
|
|
||||||
|
## 1.8.1
|
||||||
|
|
||||||
|
Code quality:
|
||||||
|
|
||||||
* move magefile in its own subdir/submodule to remove magefile dependency on logrus consumer
|
* move magefile in its own subdir/submodule to remove magefile dependency on logrus consumer
|
||||||
* improve timestamp format documentation
|
* improve timestamp format documentation
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
|
|
||||||
* fix race condition on logger hooks
|
* fix race condition on logger hooks
|
||||||
|
|
||||||
|
|
||||||
# 1.8.0
|
## 1.8.0
|
||||||
|
|
||||||
Correct versioning number replacing v1.7.1.
|
Correct versioning number replacing v1.7.1.
|
||||||
|
|
||||||
# 1.7.1
|
## 1.7.1
|
||||||
|
|
||||||
Beware this release has introduced a new public API and its semver is therefore incorrect.
|
Beware this release has introduced a new public API and its semver is therefore incorrect.
|
||||||
|
|
||||||
Code quality:
|
Code quality:
|
||||||
|
|
||||||
* use go 1.15 in travis
|
* use go 1.15 in travis
|
||||||
* use magefile as task runner
|
* use magefile as task runner
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
|
|
||||||
* small fixes about new go 1.13 error formatting system
|
* small fixes about new go 1.13 error formatting system
|
||||||
* Fix for long time race condiction with mutating data hooks
|
* Fix for long time race condiction with mutating data hooks
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|
||||||
* build support for zos
|
* build support for zos
|
||||||
|
|
||||||
# 1.7.0
|
## 1.7.0
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
|
|
||||||
* the dependency toward a windows terminal library has been removed
|
* the dependency toward a windows terminal library has been removed
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|
||||||
* a new buffer pool management API has been added
|
* a new buffer pool management API has been added
|
||||||
* a set of `<LogLevel>Fn()` functions have been added
|
* a set of `<LogLevel>Fn()` functions have been added
|
||||||
|
|
||||||
# 1.6.0
|
## 1.6.0
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
|
|
||||||
* end of line cleanup
|
* end of line cleanup
|
||||||
* revert the entry concurrency bug fix which leads to deadlock under some circumstances
|
* revert the entry concurrency bug fix which leads to deadlock under some circumstances
|
||||||
* update dependency on go-windows-terminal-sequences to fix a crash with go 1.14
|
* update dependency on go-windows-terminal-sequences to fix a crash with go 1.14
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|
||||||
* add an option to the `TextFormatter` to completely disable fields quoting
|
* add an option to the `TextFormatter` to completely disable fields quoting
|
||||||
|
|
||||||
# 1.5.0
|
## 1.5.0
|
||||||
|
|
||||||
Code quality:
|
Code quality:
|
||||||
|
|
||||||
* add golangci linter run on travis
|
* add golangci linter run on travis
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
|
|
||||||
* add mutex for hooks concurrent access on `Entry` data
|
* add mutex for hooks concurrent access on `Entry` data
|
||||||
* caller function field for go1.14
|
* caller function field for go1.14
|
||||||
* fix build issue for gopherjs target
|
* fix build issue for gopherjs target
|
||||||
|
|
||||||
Feature:
|
Feature:
|
||||||
|
|
||||||
* add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level
|
* add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level
|
||||||
* add a `DisableHTMLEscape` option in the `JSONFormatter`
|
* add a `DisableHTMLEscape` option in the `JSONFormatter`
|
||||||
* add `ForceQuote` and `PadLevelText` options in the `TextFormatter`
|
* add `ForceQuote` and `PadLevelText` options in the `TextFormatter`
|
||||||
|
|
||||||
# 1.4.2
|
## 1.4.2
|
||||||
|
|
||||||
* Fixes build break for plan9, nacl, solaris
|
* Fixes build break for plan9, nacl, solaris
|
||||||
# 1.4.1
|
|
||||||
|
## 1.4.1
|
||||||
|
|
||||||
This new release introduces:
|
This new release introduces:
|
||||||
|
|
||||||
* Enhance TextFormatter to not print caller information when they are empty (#944)
|
* Enhance TextFormatter to not print caller information when they are empty (#944)
|
||||||
* Remove dependency on golang.org/x/crypto (#932, #943)
|
* Remove dependency on golang.org/x/crypto (#932, #943)
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
|
|
||||||
* Fix Entry.WithContext method to return a copy of the initial entry (#941)
|
* Fix Entry.WithContext method to return a copy of the initial entry (#941)
|
||||||
|
|
||||||
# 1.4.0
|
## 1.4.0
|
||||||
|
|
||||||
This new release introduces:
|
This new release introduces:
|
||||||
|
|
||||||
* Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848).
|
* Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848).
|
||||||
* Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911)
|
* Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911)
|
||||||
* Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919).
|
* Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919).
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
|
|
||||||
* Fix wrong method calls `Logger.Print` and `Logger.Warningln` (#893).
|
* Fix wrong method calls `Logger.Print` and `Logger.Warningln` (#893).
|
||||||
* Update `Entry.Logf` to not do string formatting unless the log level is enabled (#903)
|
* Update `Entry.Logf` to not do string formatting unless the log level is enabled (#903)
|
||||||
* Fix infinite recursion on unknown `Level.String()` (#907)
|
* Fix infinite recursion on unknown `Level.String()` (#907)
|
||||||
* Fix race condition in `getCaller` (#916).
|
* Fix race condition in `getCaller` (#916).
|
||||||
|
|
||||||
|
|
||||||
# 1.3.0
|
## 1.3.0
|
||||||
|
|
||||||
This new release introduces:
|
This new release introduces:
|
||||||
|
|
||||||
* Log, Logf, Logln functions for Logger and Entry that take a Level
|
* Log, Logf, Logln functions for Logger and Entry that take a Level
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
|
|
||||||
* Building prometheus node_exporter on AIX (#840)
|
* Building prometheus node_exporter on AIX (#840)
|
||||||
* Race condition in TextFormatter (#468)
|
* Race condition in TextFormatter (#468)
|
||||||
* Travis CI import path (#868)
|
* Travis CI import path (#868)
|
||||||
@@ -92,20 +248,26 @@ Fixes:
|
|||||||
* Pointer to func as field in JSONFormatter (#870)
|
* Pointer to func as field in JSONFormatter (#870)
|
||||||
* Properly marshal Levels (#873)
|
* Properly marshal Levels (#873)
|
||||||
|
|
||||||
# 1.2.0
|
## 1.2.0
|
||||||
|
|
||||||
This new release introduces:
|
This new release introduces:
|
||||||
|
|
||||||
* A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued
|
* A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued
|
||||||
* A new trace level named `Trace` whose level is below `Debug`
|
* A new trace level named `Trace` whose level is below `Debug`
|
||||||
* A configurable exit function to be called upon a Fatal trace
|
* A configurable exit function to be called upon a Fatal trace
|
||||||
* The `Level` object now implements `encoding.TextUnmarshaler` interface
|
* The `Level` object now implements `encoding.TextUnmarshaler` interface
|
||||||
|
|
||||||
# 1.1.1
|
## 1.1.1
|
||||||
|
|
||||||
This is a bug fix release.
|
This is a bug fix release.
|
||||||
|
|
||||||
* fix the build break on Solaris
|
* fix the build break on Solaris
|
||||||
* don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized
|
* don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized
|
||||||
|
|
||||||
# 1.1.0
|
## 1.1.0
|
||||||
|
|
||||||
This new release introduces:
|
This new release introduces:
|
||||||
|
|
||||||
* several fixes:
|
* several fixes:
|
||||||
* a fix for a race condition on entry formatting
|
* a fix for a race condition on entry formatting
|
||||||
* proper cleanup of previously used entries before putting them back in the pool
|
* proper cleanup of previously used entries before putting them back in the pool
|
||||||
@@ -122,9 +284,10 @@ This new release introduces:
|
|||||||
* the field sort function is now configurable for text formatter
|
* the field sort function is now configurable for text formatter
|
||||||
* the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater
|
* the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater
|
||||||
|
|
||||||
# 1.0.6
|
## 1.0.6
|
||||||
|
|
||||||
This new release introduces:
|
This new release introduces:
|
||||||
|
|
||||||
* a new api WithTime which allows to easily force the time of the log entry
|
* a new api WithTime which allows to easily force the time of the log entry
|
||||||
which is mostly useful for logger wrapper
|
which is mostly useful for logger wrapper
|
||||||
* a fix reverting the immutability of the entry given as parameter to the hooks
|
* a fix reverting the immutability of the entry given as parameter to the hooks
|
||||||
@@ -134,71 +297,71 @@ This new release introduces:
|
|||||||
* a new configuration of the textformatter to configure the name of the default keys
|
* a new configuration of the textformatter to configure the name of the default keys
|
||||||
* a new configuration of the text formatter to disable the level truncation
|
* a new configuration of the text formatter to disable the level truncation
|
||||||
|
|
||||||
# 1.0.5
|
## 1.0.5
|
||||||
|
|
||||||
* Fix hooks race (#707)
|
* Fix hooks race (#707)
|
||||||
* Fix panic deadlock (#695)
|
* Fix panic deadlock (#695)
|
||||||
|
|
||||||
# 1.0.4
|
## 1.0.4
|
||||||
|
|
||||||
* Fix race when adding hooks (#612)
|
* Fix race when adding hooks (#612)
|
||||||
* Fix terminal check in AppEngine (#635)
|
* Fix terminal check in AppEngine (#635)
|
||||||
|
|
||||||
# 1.0.3
|
## 1.0.3
|
||||||
|
|
||||||
* Replace example files with testable examples
|
* Replace example files with testable examples
|
||||||
|
|
||||||
# 1.0.2
|
## 1.0.2
|
||||||
|
|
||||||
* bug: quote non-string values in text formatter (#583)
|
* bug: quote non-string values in text formatter (#583)
|
||||||
* Make (*Logger) SetLevel a public method
|
* Make (*Logger) SetLevel a public method
|
||||||
|
|
||||||
# 1.0.1
|
## 1.0.1
|
||||||
|
|
||||||
* bug: fix escaping in text formatter (#575)
|
* bug: fix escaping in text formatter (#575)
|
||||||
|
|
||||||
# 1.0.0
|
## 1.0.0
|
||||||
|
|
||||||
* Officially changed name to lower-case
|
* Officially changed name to lower-case
|
||||||
* bug: colors on Windows 10 (#541)
|
* bug: colors on Windows 10 (#541)
|
||||||
* bug: fix race in accessing level (#512)
|
* bug: fix race in accessing level (#512)
|
||||||
|
|
||||||
# 0.11.5
|
## 0.11.5
|
||||||
|
|
||||||
* feature: add writer and writerlevel to entry (#372)
|
* feature: add writer and writerlevel to entry (#372)
|
||||||
|
|
||||||
# 0.11.4
|
## 0.11.4
|
||||||
|
|
||||||
* bug: fix undefined variable on solaris (#493)
|
* bug: fix undefined variable on solaris (#493)
|
||||||
|
|
||||||
# 0.11.3
|
## 0.11.3
|
||||||
|
|
||||||
* formatter: configure quoting of empty values (#484)
|
* formatter: configure quoting of empty values (#484)
|
||||||
* formatter: configure quoting character (default is `"`) (#484)
|
* formatter: configure quoting character (default is `"`) (#484)
|
||||||
* bug: fix not importing io correctly in non-linux environments (#481)
|
* bug: fix not importing io correctly in non-linux environments (#481)
|
||||||
|
|
||||||
# 0.11.2
|
## 0.11.2
|
||||||
|
|
||||||
* bug: fix windows terminal detection (#476)
|
* bug: fix windows terminal detection (#476)
|
||||||
|
|
||||||
# 0.11.1
|
## 0.11.1
|
||||||
|
|
||||||
* bug: fix tty detection with custom out (#471)
|
* bug: fix tty detection with custom out (#471)
|
||||||
|
|
||||||
# 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
* performance: Use bufferpool to allocate (#370)
|
* performance: Use bufferpool to allocate (#370)
|
||||||
* terminal: terminal detection for app-engine (#343)
|
* terminal: terminal detection for app-engine (#343)
|
||||||
* feature: exit handler (#375)
|
* feature: exit handler (#375)
|
||||||
|
|
||||||
# 0.10.0
|
## 0.10.0
|
||||||
|
|
||||||
* feature: Add a test hook (#180)
|
* feature: Add a test hook (#180)
|
||||||
* feature: `ParseLevel` is now case-insensitive (#326)
|
* feature: `ParseLevel` is now case-insensitive (#326)
|
||||||
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
||||||
* performance: avoid re-allocations on `WithFields` (#335)
|
* performance: avoid re-allocations on `WithFields` (#335)
|
||||||
|
|
||||||
# 0.9.0
|
## 0.9.0
|
||||||
|
|
||||||
* logrus/text_formatter: don't emit empty msg
|
* logrus/text_formatter: don't emit empty msg
|
||||||
* logrus/hooks/airbrake: move out of main repository
|
* logrus/hooks/airbrake: move out of main repository
|
||||||
@@ -210,25 +373,25 @@ This new release introduces:
|
|||||||
* logrus/core: support `WithError` on logger
|
* logrus/core: support `WithError` on logger
|
||||||
* logrus/core: Solaris support
|
* logrus/core: Solaris support
|
||||||
|
|
||||||
# 0.8.7
|
## 0.8.7
|
||||||
|
|
||||||
* logrus/core: fix possible race (#216)
|
* logrus/core: fix possible race (#216)
|
||||||
* logrus/doc: small typo fixes and doc improvements
|
* logrus/doc: small typo fixes and doc improvements
|
||||||
|
|
||||||
|
|
||||||
# 0.8.6
|
## 0.8.6
|
||||||
|
|
||||||
* hooks/raven: allow passing an initialized client
|
* hooks/raven: allow passing an initialized client
|
||||||
|
|
||||||
# 0.8.5
|
## 0.8.5
|
||||||
|
|
||||||
* logrus/core: revert #208
|
* logrus/core: revert #208
|
||||||
|
|
||||||
# 0.8.4
|
## 0.8.4
|
||||||
|
|
||||||
* formatter/text: fix data race (#218)
|
* formatter/text: fix data race (#218)
|
||||||
|
|
||||||
# 0.8.3
|
## 0.8.3
|
||||||
|
|
||||||
* logrus/core: fix entry log level (#208)
|
* logrus/core: fix entry log level (#208)
|
||||||
* logrus/core: improve performance of text formatter by 40%
|
* logrus/core: improve performance of text formatter by 40%
|
||||||
@@ -236,24 +399,24 @@ This new release introduces:
|
|||||||
* logrus/core: add support for DragonflyBSD and NetBSD
|
* logrus/core: add support for DragonflyBSD and NetBSD
|
||||||
* formatter/text: print structs more verbosely
|
* formatter/text: print structs more verbosely
|
||||||
|
|
||||||
# 0.8.2
|
## 0.8.2
|
||||||
|
|
||||||
* logrus: fix more Fatal family functions
|
* logrus: fix more Fatal family functions
|
||||||
|
|
||||||
# 0.8.1
|
## 0.8.1
|
||||||
|
|
||||||
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
||||||
|
|
||||||
# 0.8.0
|
## 0.8.0
|
||||||
|
|
||||||
* logrus: defaults to stderr instead of stdout
|
* logrus: defaults to stderr instead of stdout
|
||||||
* hooks/sentry: add special field for `*http.Request`
|
* hooks/sentry: add special field for `*http.Request`
|
||||||
* formatter/text: ignore Windows for colors
|
* formatter/text: ignore Windows for colors
|
||||||
|
|
||||||
# 0.7.3
|
## 0.7.3
|
||||||
|
|
||||||
* formatter/\*: allow configuration of timestamp layout
|
* formatter/\*: allow configuration of timestamp layout
|
||||||
|
|
||||||
# 0.7.2
|
## 0.7.2
|
||||||
|
|
||||||
* formatter/text: Add configuration option for time format (#158)
|
* formatter/text: Add configuration option for time format (#158)
|
||||||
|
|||||||
+39
-53
@@ -3,13 +3,10 @@
|
|||||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||||
the standard library logger.
|
the standard library logger.
|
||||||
|
|
||||||
**Logrus is in maintenance-mode.** We will not be introducing new features. It's
|
**Logrus is in maintenance mode.** The project focuses on security, bug fixes,
|
||||||
simply too hard to do in a way that won't break many people's projects, which is
|
and performance improvements. New features are not planned, aside from changes
|
||||||
the last thing you want from your Logging library (again...).
|
required to provide interoperability with other logging ecosystems (e.g., Go's
|
||||||
|
[log/slog](https://pkg.go.dev/log/slog)).
|
||||||
This does not mean Logrus is dead. Logrus will continue to be maintained for
|
|
||||||
security, (backwards compatible) bug fixes, and performance (where we are
|
|
||||||
limited by the interface).
|
|
||||||
|
|
||||||
I believe Logrus' biggest contribution is to have played a part in today's
|
I believe Logrus' biggest contribution is to have played a part in today's
|
||||||
widespread use of structured logging in Golang. There doesn't seem to be a
|
widespread use of structured logging in Golang. There doesn't seem to be a
|
||||||
@@ -23,18 +20,6 @@ about structured logging in Go today. Check out, for example,
|
|||||||
[zap]: https://github.com/uber-go/zap
|
[zap]: https://github.com/uber-go/zap
|
||||||
[apex]: https://github.com/apex/log
|
[apex]: https://github.com/apex/log
|
||||||
|
|
||||||
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
|
||||||
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
|
||||||
this caused issues in the community and we needed a standard. Some environments
|
|
||||||
experienced problems with the upper-case variant, so the lower-case was decided.
|
|
||||||
Everything using `logrus` will need to use the lower-case:
|
|
||||||
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
|
||||||
|
|
||||||
To fix Glide, see [these
|
|
||||||
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
|
||||||
For an in-depth explanation of the casing issue, see [this
|
|
||||||
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
|
||||||
|
|
||||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||||
plain text):
|
plain text):
|
||||||
|
|
||||||
@@ -43,35 +28,27 @@ plain text):
|
|||||||
With `logrus.SetFormatter(&logrus.JSONFormatter{})`, for easy parsing by logstash
|
With `logrus.SetFormatter(&logrus.JSONFormatter{})`, for easy parsing by logstash
|
||||||
or Splunk:
|
or Splunk:
|
||||||
|
|
||||||
```text
|
```json lines
|
||||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
{"level":"warning","msg":"The group's number increased tremendously!","number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||||
|
{"animal":"walrus","level":"info","msg":"A giant walrus appears!","size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.","size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||||
|
|
||||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
|
||||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
|
||||||
|
|
||||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
|
||||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
|
||||||
|
|
||||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
|
||||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
With the default `logrus.SetFormatter(&logrus.TextFormatter{})` when a TTY is not
|
With the default `logrus.SetFormatter(&logrus.TextFormatter{})` when a TTY is not
|
||||||
attached, the output is compatible with the
|
attached, the output is compatible with the
|
||||||
[logfmt](https://pkg.go.dev/github.com/kr/logfmt) format:
|
[logfmt](https://pkg.go.dev/github.com/kr/logfmt) format:
|
||||||
|
|
||||||
```text
|
```bash
|
||||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
||||||
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||||
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
||||||
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
||||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" animal=orca err="It's over 9000!" number=100 omg=true size=9009
|
||||||
```
|
```
|
||||||
|
|
||||||
To ensure this behaviour even if a TTY is attached, set your formatter as follows:
|
To ensure this behaviour even if a TTY is attached, set your formatter as follows:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -88,29 +65,32 @@ If you wish to add the calling method as a field, instruct the logger via:
|
|||||||
```go
|
```go
|
||||||
logrus.SetReportCaller(true)
|
logrus.SetReportCaller(true)
|
||||||
```
|
```
|
||||||
|
|
||||||
This adds the caller as 'method' like so:
|
This adds the caller as 'method' like so:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by",
|
{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by","time":"2014-03-10 19:57:38.562543129 -0400 EDT"}
|
||||||
"time":"2014-03-10 19:57:38.562543129 -0400 EDT"}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```text
|
```bash
|
||||||
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
|
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that this does add measurable overhead - the cost will depend on the version of Go, but is
|
Note that this does add measurable overhead - the cost will depend on the version of Go, but is
|
||||||
between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your
|
between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your
|
||||||
environment via benchmarks:
|
environment via benchmarks:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go test -bench=.*CallerTracing
|
go test -bench=ReportCaller
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Case-sensitivity
|
#### Case-sensitivity
|
||||||
|
|
||||||
The organization's name was changed to lower-case--and this will not be changed
|
The organization's name was [changed to lower-case][1]. If you are getting import
|
||||||
back. If you are getting import conflicts due to case sensitivity, please use
|
conflicts due to case sensitivity, please use the lower-case import:
|
||||||
the lower-case import: `github.com/sirupsen/logrus`.
|
`github.com/sirupsen/logrus`.
|
||||||
|
|
||||||
|
[1]: https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
||||||
@@ -289,6 +269,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Syslog hooks also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
Note: Syslog hooks also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
||||||
|
|
||||||
A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
|
A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
|
||||||
@@ -367,18 +348,21 @@ Splunk or Logstash.
|
|||||||
|
|
||||||
The built-in logging formatters are:
|
The built-in logging formatters are:
|
||||||
|
|
||||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
* [`logrus.TextFormatter`](https://pkg.go.dev/github.com/sirupsen/logrus#TextFormatter)
|
||||||
without colors.
|
logs the event in colors if the logger output is a TTY, otherwise without colors.
|
||||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
* To force colored output when there is no TTY, set the `ForceColors`
|
||||||
field to `true`. To force no colored output even if there is a TTY set the
|
field to `true`. To force no colored output even if there is a TTY set the
|
||||||
`DisableColors` field to `true`. For Windows, see
|
`DisableColors` field to `true`.
|
||||||
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
* On modern Windows terminals with ANSI (Virtual Terminal) support, TextFormatter
|
||||||
|
automatically enables colored output.
|
||||||
|
* If your environment does not support ANSI escape sequences, wrap the logger output
|
||||||
|
using [github.com/mattn/go-colorable](https://github.com/mattn/go-colorable)
|
||||||
|
and set `ForceColors` (or `CLICOLOR_FORCE=1`) to enable colors through the wrapper.
|
||||||
* When colors are enabled, levels are truncated to 4 characters by default. To disable
|
* When colors are enabled, levels are truncated to 4 characters by default. To disable
|
||||||
truncation set the `DisableLevelTruncation` field to `true`.
|
truncation set the `DisableLevelTruncation` field to `true`.
|
||||||
* When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text.
|
* When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text.
|
||||||
* All options are listed in the [generated docs](https://pkg.go.dev/github.com/sirupsen/logrus#TextFormatter).
|
* [`logrus.JSONFormatter`](https://pkg.go.dev/github.com/sirupsen/logrus#JSONFormatter)
|
||||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
logs fields as JSON.
|
||||||
* All options are listed in the [generated docs](https://pkg.go.dev/github.com/sirupsen/logrus#JSONFormatter).
|
|
||||||
|
|
||||||
Third-party logging formatters:
|
Third-party logging formatters:
|
||||||
|
|
||||||
@@ -390,10 +374,12 @@ Third-party logging formatters:
|
|||||||
* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure.
|
* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure.
|
||||||
* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Save log to files.
|
* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Save log to files.
|
||||||
* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added.
|
* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added.
|
||||||
|
* [`easy-logrus-formatter`](https://github.com/WeiZhixiong/easy-logrus-formatter). Provide a user-friendly formatter for logrus.
|
||||||
|
* [`redactrus`](https://github.com/ibreakthecloud/redactrus). Redacts sensitive information like password, apikeys, email, etc. from logs.
|
||||||
|
|
||||||
You can define your formatter by implementing the `Formatter` interface,
|
You can define your formatter by implementing the `Formatter` interface,
|
||||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
`Fields` type (`map[string]any`) with all your fields as well as the
|
||||||
default ones (see Entries section above):
|
default ones (see Entries section above):
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -516,4 +502,4 @@ Situations when locking is not needed include:
|
|||||||
|
|
||||||
2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing)
|
2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing)
|
||||||
|
|
||||||
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
(Refer to <http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/>)
|
||||||
|
|||||||
+2
-2
@@ -57,7 +57,7 @@ func Exit(code int) {
|
|||||||
//
|
//
|
||||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||||
// message but also needs to gracefully shutdown. An example usecase could be
|
// message but also needs to gracefully shutdown. An example usecase could be
|
||||||
// closing database connections, or sending a alert that the application is
|
// closing database connections, or sending an alert that the application is
|
||||||
// closing.
|
// closing.
|
||||||
func RegisterExitHandler(handler func()) {
|
func RegisterExitHandler(handler func()) {
|
||||||
handlers = append(handlers, handler)
|
handlers = append(handlers, handler)
|
||||||
@@ -69,7 +69,7 @@ func RegisterExitHandler(handler func()) {
|
|||||||
//
|
//
|
||||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||||
// message but also needs to gracefully shutdown. An example usecase could be
|
// message but also needs to gracefully shutdown. An example usecase could be
|
||||||
// closing database connections, or sending a alert that the application is
|
// closing database connections, or sending an alert that the application is
|
||||||
// closing.
|
// closing.
|
||||||
func DeferExitHandler(handler func()) {
|
func DeferExitHandler(handler func()) {
|
||||||
handlers = append([]func(){handler}, handlers...)
|
handlers = append([]func(){handler}, handlers...)
|
||||||
|
|||||||
+8
-14
@@ -5,9 +5,13 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var bufferPool BufferPool = &defaultPool{
|
||||||
bufferPool BufferPool
|
pool: &sync.Pool{
|
||||||
)
|
New: func() any {
|
||||||
|
return new(bytes.Buffer)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
type BufferPool interface {
|
type BufferPool interface {
|
||||||
Put(*bytes.Buffer)
|
Put(*bytes.Buffer)
|
||||||
@@ -27,17 +31,7 @@ func (p *defaultPool) Get() *bytes.Buffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetBufferPool allows to replace the default logrus buffer pool
|
// SetBufferPool allows to replace the default logrus buffer pool
|
||||||
// to better meets the specific needs of an application.
|
// to better meet the specific needs of an application.
|
||||||
func SetBufferPool(bp BufferPool) {
|
func SetBufferPool(bp BufferPool) {
|
||||||
bufferPool = bp
|
bufferPool = bp
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
SetBufferPool(&defaultPool{
|
|
||||||
pool: &sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return new(bytes.Buffer)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
+13
-13
@@ -1,25 +1,25 @@
|
|||||||
/*
|
/*
|
||||||
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
||||||
|
|
||||||
|
|
||||||
The simplest way to use Logrus is simply the package-level exported logger:
|
The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"animal": "walrus",
|
"animal": "walrus",
|
||||||
"number": 1,
|
"number": 1,
|
||||||
"size": 10,
|
"size": 10,
|
||||||
}).Info("A walrus appears")
|
}).Info("A walrus appears")
|
||||||
}
|
}
|
||||||
|
|
||||||
Output:
|
Output:
|
||||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
|
||||||
|
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
||||||
|
|
||||||
For a full guide visit https://github.com/sirupsen/logrus
|
For a full guide visit https://github.com/sirupsen/logrus
|
||||||
*/
|
*/
|
||||||
|
|||||||
+268
-146
@@ -4,9 +4,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -17,8 +19,10 @@ var (
|
|||||||
// qualified package name, cached at first use
|
// qualified package name, cached at first use
|
||||||
logrusPackage string
|
logrusPackage string
|
||||||
|
|
||||||
// Positions in the call stack when tracing to report the calling method
|
// Positions in the call stack when tracing to report the calling method.
|
||||||
minimumCallerDepth int
|
//
|
||||||
|
// Start at the bottom of the stack before the package-name cache is primed.
|
||||||
|
minimumCallerDepth = 1
|
||||||
|
|
||||||
// Used for caller information initialisation
|
// Used for caller information initialisation
|
||||||
callerInitOnce sync.Once
|
callerInitOnce sync.Once
|
||||||
@@ -29,68 +33,116 @@ const (
|
|||||||
knownLogrusFrames int = 4
|
knownLogrusFrames int = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
// start at the bottom of the stack before the package-name cache is primed
|
|
||||||
minimumCallerDepth = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorKey defines the key when adding errors using [WithError], [Logger.WithError].
|
// ErrorKey defines the key when adding errors using [WithError], [Logger.WithError].
|
||||||
var ErrorKey = "error"
|
var ErrorKey = "error"
|
||||||
|
|
||||||
// Entry is the final or intermediate Logrus logging entry. It contains all
|
// Entry represents a single log event. It may be either an intermediate
|
||||||
// the fields passed with WithField{,s}. It's finally logged when Trace, Debug,
|
// entry (created via WithField(s), WithContext, etc.) or a final entry
|
||||||
// Info, Warn, Error, Fatal or Panic is called on it. These objects can be
|
// that is emitted when one of the level methods (Trace, Debug, Info,
|
||||||
// reused and passed around as much as you wish to avoid field duplication.
|
// Warn, Error, Fatal, Panic) is called.
|
||||||
//
|
//
|
||||||
//nolint:recvcheck // the methods of "Entry" use pointer receiver and non-pointer receiver.
|
// An Entry always belongs to a Logger. A nil Logger is invalid and will
|
||||||
|
// cause a panic when the entry is logged. Use [NewEntry] or Logger methods
|
||||||
|
// to construct entries.
|
||||||
|
//
|
||||||
|
// Entries are safe to reuse for adding fields and may be passed around
|
||||||
|
// to avoid field duplication. Each log operation operates on a copy
|
||||||
|
// of the Entry’s data to avoid mutation during formatting.
|
||||||
|
//
|
||||||
|
//nolint:recvcheck // Entry methods intentionally use both pointer and value receivers.
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
|
// Logger is the Logger that owns this entry and is responsible for
|
||||||
|
// formatting, hooks, and output. It must not be nil. An Entry without
|
||||||
|
// a Logger is invalid and will panic when logged.
|
||||||
Logger *Logger
|
Logger *Logger
|
||||||
|
|
||||||
// Contains all the fields set by the user.
|
// Data contains all user-defined fields attached to this entry.
|
||||||
Data Fields
|
Data Fields
|
||||||
|
|
||||||
// Time at which the log entry was created
|
// Time is the timestamp for the log event. If zero when the entry is
|
||||||
|
// logged, it defaults to the current time.
|
||||||
Time time.Time
|
Time time.Time
|
||||||
|
|
||||||
// Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic
|
// Level is the severity of the log entry. It is set when the entry
|
||||||
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
// is fired and reflects the level used for that log call.
|
||||||
Level Level
|
Level Level
|
||||||
|
|
||||||
// Calling method, with package name
|
// Caller contains the calling method information.
|
||||||
|
//
|
||||||
|
// When [Logger.ReportCaller] is enabled, Caller is populated automatically at
|
||||||
|
// log time if it is nil. Hooks and formatters may inspect Caller.
|
||||||
|
//
|
||||||
|
// Applications generally should not modify Caller unless they intentionally
|
||||||
|
// want to provide custom caller information.
|
||||||
Caller *runtime.Frame
|
Caller *runtime.Frame
|
||||||
|
|
||||||
// Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic
|
// Message is the log message supplied to one of the logging methods
|
||||||
|
// (Trace, Debug, Info, Warn, Error, Fatal, or Panic). It is set when
|
||||||
|
// the entry is logged.
|
||||||
Message string
|
Message string
|
||||||
|
|
||||||
// When formatter is called in entry.log(), a Buffer may be set to entry
|
// Buffer is a reusable buffer provided to the formatter. It is set
|
||||||
|
// before formatting in the normal log path; when nil, formatters
|
||||||
|
// allocate their own.
|
||||||
Buffer *bytes.Buffer
|
Buffer *bytes.Buffer
|
||||||
|
|
||||||
// Contains the context set by the user. Useful for hook processing etc.
|
// Context carries user-provided context for hooks and formatters.
|
||||||
Context context.Context
|
Context context.Context
|
||||||
|
|
||||||
// err may contain a field formatting error
|
// err contains internal field-formatting errors.
|
||||||
err string
|
err string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewEntry creates a new [Entry] associated with the provided Logger.
|
||||||
|
// The logger must not be nil. Passing a nil logger results in a
|
||||||
|
// panic when a logging method (e.g., [Entry.Info], [Entry.Error], etc.)
|
||||||
|
// is called.
|
||||||
func NewEntry(logger *Logger) *Entry {
|
func NewEntry(logger *Logger) *Entry {
|
||||||
return &Entry{
|
return &Entry{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
// Default is three fields, plus one optional. Give a little extra room.
|
// Reserve default predefined fields and a little extra room.
|
||||||
Data: make(Fields, 6),
|
Data: make(Fields, defaultFields+3),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dup creates a copy of the entry for further modification.
|
||||||
|
//
|
||||||
|
// Data is cloned to avoid mutating the original entry. Other fields
|
||||||
|
// (Logger, Time, Context, etc.) are copied by value.
|
||||||
func (entry *Entry) Dup() *Entry {
|
func (entry *Entry) Dup() *Entry {
|
||||||
data := make(Fields, len(entry.Data))
|
dup := entry.dup()
|
||||||
for k, v := range entry.Data {
|
dup.Data = maps.Clone(entry.Data)
|
||||||
data[k] = v
|
return dup
|
||||||
|
}
|
||||||
|
|
||||||
|
// dup copies the entry fields shared by derived entries except Data, which
|
||||||
|
// callers must copy or initialize as appropriate for their use.
|
||||||
|
func (entry *Entry) dup() *Entry {
|
||||||
|
return &Entry{
|
||||||
|
Logger: entry.Logger,
|
||||||
|
Time: entry.Time,
|
||||||
|
Caller: entry.Caller,
|
||||||
|
Context: entry.Context,
|
||||||
|
err: entry.err,
|
||||||
}
|
}
|
||||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes returns the bytes representation of this entry from the formatter.
|
// Bytes returns the bytes representation of this entry from the formatter.
|
||||||
func (entry *Entry) Bytes() ([]byte, error) {
|
func (entry *Entry) Bytes() ([]byte, error) {
|
||||||
return entry.Logger.Formatter.Format(entry)
|
// Snapshot the formatter under the lock to protect against concurrent
|
||||||
|
// SetFormatter calls, then release the lock before formatting.
|
||||||
|
// This avoids a data race and prevents a deadlock if Format() triggers
|
||||||
|
// reentrant logging (e.g., a field's MarshalJSON calls logrus).
|
||||||
|
//
|
||||||
|
// See:
|
||||||
|
//
|
||||||
|
// - https://github.com/sirupsen/logrus/issues/1440
|
||||||
|
// - https://github.com/sirupsen/logrus/issues/1448
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
formatter := entry.Logger.Formatter
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
|
||||||
|
return formatter.Format(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the string representation from the reader and ultimately the
|
// String returns the string representation from the reader and ultimately the
|
||||||
@@ -112,58 +164,61 @@ func (entry *Entry) WithError(err error) *Entry {
|
|||||||
|
|
||||||
// WithContext adds a context to the Entry.
|
// WithContext adds a context to the Entry.
|
||||||
func (entry *Entry) WithContext(ctx context.Context) *Entry {
|
func (entry *Entry) WithContext(ctx context.Context) *Entry {
|
||||||
dataCopy := make(Fields, len(entry.Data))
|
dup := entry.dup()
|
||||||
for k, v := range entry.Data {
|
dup.Data = maps.Clone(entry.Data)
|
||||||
dataCopy[k] = v
|
dup.Context = ctx
|
||||||
}
|
return dup
|
||||||
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithField adds a single field to the Entry.
|
// WithField adds a single field to the Entry.
|
||||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
func (entry *Entry) WithField(key string, value any) *Entry {
|
||||||
return entry.WithFields(Fields{key: value})
|
dup := entry.dup()
|
||||||
|
dup.Data = maps.Clone(entry.Data)
|
||||||
|
dup.addField(key, value)
|
||||||
|
return dup
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFields adds a map of fields to the Entry.
|
// WithFields adds a map of fields to the Entry.
|
||||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||||
data := make(Fields, len(entry.Data)+len(fields))
|
dup := entry.dup()
|
||||||
for k, v := range entry.Data {
|
dup.Data = make(Fields, len(entry.Data)+len(fields))
|
||||||
data[k] = v
|
maps.Copy(dup.Data, entry.Data)
|
||||||
|
|
||||||
|
for key, value := range fields {
|
||||||
|
dup.addField(key, value)
|
||||||
}
|
}
|
||||||
fieldErr := entry.err
|
return dup
|
||||||
for k, v := range fields {
|
|
||||||
isErrField := false
|
|
||||||
if t := reflect.TypeOf(v); t != nil {
|
|
||||||
switch {
|
|
||||||
case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func:
|
|
||||||
isErrField = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isErrField {
|
|
||||||
tmp := fmt.Sprintf("can not add field %q", k)
|
|
||||||
if fieldErr != "" {
|
|
||||||
fieldErr = entry.err + ", " + tmp
|
|
||||||
} else {
|
|
||||||
fieldErr = tmp
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTime overrides the time of the Entry.
|
// WithTime overrides the time of the Entry.
|
||||||
func (entry *Entry) WithTime(t time.Time) *Entry {
|
func (entry *Entry) WithTime(t time.Time) *Entry {
|
||||||
dataCopy := make(Fields, len(entry.Data))
|
dup := entry.dup()
|
||||||
for k, v := range entry.Data {
|
dup.Data = maps.Clone(entry.Data)
|
||||||
dataCopy[k] = v
|
dup.Time = t
|
||||||
|
return dup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) addField(key string, value any) {
|
||||||
|
if _, ok := value.(error); !ok {
|
||||||
|
t := reflect.TypeOf(value)
|
||||||
|
if t != nil && (t.Kind() == reflect.Func || t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Func) {
|
||||||
|
if entry.err != "" {
|
||||||
|
entry.err += ", skipping unsupported field " + strconv.Quote(key)
|
||||||
|
} else {
|
||||||
|
entry.err = "skipping unsupported field " + strconv.Quote(key)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context}
|
|
||||||
|
if entry.Data == nil {
|
||||||
|
entry.Data = make(Fields, 1)
|
||||||
|
}
|
||||||
|
entry.Data[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPackageName reduces a fully qualified function name to the package name
|
// getPackageName reduces a fully qualified function name to the package name
|
||||||
// There really ought to be to be a better way...
|
// There really ought to be a better way...
|
||||||
func getPackageName(f string) string {
|
func getPackageName(f string) string {
|
||||||
for {
|
for {
|
||||||
lastPeriod := strings.LastIndex(f, ".")
|
lastPeriod := strings.LastIndex(f, ".")
|
||||||
@@ -186,7 +241,7 @@ func getCaller() *runtime.Frame {
|
|||||||
_ = runtime.Callers(0, pcs)
|
_ = runtime.Callers(0, pcs)
|
||||||
|
|
||||||
// dynamic get the package name and the minimum caller depth
|
// dynamic get the package name and the minimum caller depth
|
||||||
for i := 0; i < maximumCallerDepth; i++ {
|
for i := range maximumCallerDepth {
|
||||||
funcName := runtime.FuncForPC(pcs[i]).Name()
|
funcName := runtime.FuncForPC(pcs[i]).Name()
|
||||||
if strings.Contains(funcName, "getCaller") {
|
if strings.Contains(funcName, "getCaller") {
|
||||||
logrusPackage = getPackageName(funcName)
|
logrusPackage = getPackageName(funcName)
|
||||||
@@ -215,16 +270,47 @@ func getCaller() *runtime.Frame {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry Entry) HasCaller() (has bool) {
|
// HasCaller reports whether this Entry contains caller information.
|
||||||
return entry.Logger != nil &&
|
//
|
||||||
entry.Logger.ReportCaller &&
|
// Caller may be set explicitly, or populated at log time when
|
||||||
entry.Caller != nil
|
// [Logger.ReportCaller] is enabled.
|
||||||
|
//
|
||||||
|
// Deprecated: use [Entry.Caller] != nil instead.
|
||||||
|
//
|
||||||
|
//go:fix inline
|
||||||
|
func (entry Entry) HasCaller() bool {
|
||||||
|
return entry.Caller != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) log(level Level, msg string) {
|
func (entry *Entry) logArgs(level Level, panicAfter bool, args ...any) {
|
||||||
var buffer *bytes.Buffer
|
entry.log(level, panicAfter, sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
newEntry := entry.Dup()
|
func (entry *Entry) logf(level Level, panicAfter bool, format string, args ...any) {
|
||||||
|
entry.log(level, panicAfter, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// logln uses Sprintln for multiple arguments to preserve Println-style
|
||||||
|
// spacing between args, then trims the trailing newline.
|
||||||
|
func (entry *Entry) logln(level Level, panicAfter bool, args ...any) {
|
||||||
|
if len(args) <= 1 {
|
||||||
|
entry.log(level, panicAfter, sprint(args...))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msg := fmt.Sprintln(args...)
|
||||||
|
msg = msg[:len(msg)-1] // Trim the newline added by Sprintln; logging adds its own.
|
||||||
|
entry.log(level, panicAfter, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// log writes msg at level. If panicAfter is true, it panics with the fully
|
||||||
|
// populated entry after hooks and output have completed.
|
||||||
|
//
|
||||||
|
// The explicit flag keeps panic behavior limited to Panic, Panicf, and
|
||||||
|
// Panicln while avoiding a return value used only as the panic value.
|
||||||
|
// See #1283 and commits f96066e and 5f8c666.
|
||||||
|
func (entry *Entry) log(level Level, panicAfter bool, msg string) {
|
||||||
|
newEntry := entry.dup()
|
||||||
|
newEntry.Data = maps.Clone(entry.Data)
|
||||||
|
|
||||||
if newEntry.Time.IsZero() {
|
if newEntry.Time.IsZero() {
|
||||||
newEntry.Time = time.Now()
|
newEntry.Time = time.Now()
|
||||||
@@ -233,17 +319,24 @@ func (entry *Entry) log(level Level, msg string) {
|
|||||||
newEntry.Level = level
|
newEntry.Level = level
|
||||||
newEntry.Message = msg
|
newEntry.Message = msg
|
||||||
|
|
||||||
newEntry.Logger.mu.Lock()
|
logger := newEntry.Logger
|
||||||
reportCaller := newEntry.Logger.ReportCaller
|
logger.mu.Lock()
|
||||||
|
reportCaller := logger.ReportCaller
|
||||||
bufPool := newEntry.getBufferPool()
|
bufPool := newEntry.getBufferPool()
|
||||||
newEntry.Logger.mu.Unlock()
|
logger.mu.Unlock()
|
||||||
|
|
||||||
if reportCaller {
|
// Preserve explicitly set caller information.
|
||||||
|
if reportCaller && newEntry.Caller == nil {
|
||||||
newEntry.Caller = getCaller()
|
newEntry.Caller = getCaller()
|
||||||
}
|
}
|
||||||
|
|
||||||
newEntry.fireHooks()
|
// Select hooks based on the level for this log call. Hooks receive the
|
||||||
buffer = bufPool.Get()
|
// Entry and may mutate it, but that does not affect which hooks are
|
||||||
|
// fired for this event.
|
||||||
|
hooks := logger.hooksForLevel(level)
|
||||||
|
newEntry.fireHooks(hooks)
|
||||||
|
|
||||||
|
buffer := bufPool.Get()
|
||||||
defer func() {
|
defer func() {
|
||||||
newEntry.Buffer = nil
|
newEntry.Buffer = nil
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
@@ -251,15 +344,12 @@ func (entry *Entry) log(level Level, msg string) {
|
|||||||
}()
|
}()
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
newEntry.Buffer = buffer
|
newEntry.Buffer = buffer
|
||||||
|
|
||||||
newEntry.write()
|
newEntry.write()
|
||||||
|
|
||||||
newEntry.Buffer = nil
|
newEntry.Buffer = nil
|
||||||
|
|
||||||
// To avoid Entry#log() returning a value that only would make sense for
|
// Panic here so the panic value contains the fully populated entry without
|
||||||
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
// requiring log to return it to the caller.
|
||||||
// directly here.
|
if panicAfter {
|
||||||
if level <= PanicLevel {
|
|
||||||
panic(newEntry)
|
panic(newEntry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,175 +361,207 @@ func (entry *Entry) getBufferPool() (pool BufferPool) {
|
|||||||
return bufferPool
|
return bufferPool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) fireHooks() {
|
func (entry *Entry) fireHooks(hooks []Hook) {
|
||||||
var tmpHooks LevelHooks
|
for _, hook := range hooks {
|
||||||
entry.Logger.mu.Lock()
|
if err := hook.Fire(entry); err != nil {
|
||||||
tmpHooks = make(LevelHooks, len(entry.Logger.Hooks))
|
_, _ = fmt.Fprintln(os.Stderr, "Failed to fire hook:", err)
|
||||||
for k, v := range entry.Logger.Hooks {
|
return
|
||||||
tmpHooks[k] = v
|
}
|
||||||
}
|
|
||||||
entry.Logger.mu.Unlock()
|
|
||||||
|
|
||||||
err := tmpHooks.Fire(entry.Level, entry)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) write() {
|
func (entry *Entry) write() {
|
||||||
|
// Snapshot the formatter under the lock to protect against concurrent
|
||||||
|
// SetFormatter calls, then release the lock before formatting.
|
||||||
|
// This avoids a deadlock when Format() triggers reentrant logging (e.g.,
|
||||||
|
// a field's MarshalJSON calls logrus). See #1448, #1440.
|
||||||
entry.Logger.mu.Lock()
|
entry.Logger.mu.Lock()
|
||||||
defer entry.Logger.mu.Unlock()
|
formatter := entry.Logger.Formatter
|
||||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
entry.Logger.mu.Unlock()
|
||||||
|
|
||||||
|
serialized, err := formatter.Format(entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
_, _ = fmt.Fprintln(os.Stderr, "Failed to format entry:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-acquire the lock to serialize writes to the underlying io.Writer.
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
defer entry.Logger.mu.Unlock()
|
||||||
if _, err := entry.Logger.Out.Write(serialized); err != nil {
|
if _, err := entry.Logger.Out.Write(serialized); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
_, _ = fmt.Fprintln(os.Stderr, "Failed to write to log:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log will log a message at the level given as parameter.
|
// Log logs a message at the specified level.
|
||||||
// Warning: using Log at Panic or Fatal level will not respectively Panic nor Exit.
|
//
|
||||||
// For this behaviour Entry.Panic or Entry.Fatal should be used instead.
|
// Using Log with [PanicLevel] or [FatalLevel] intentionally does not
|
||||||
func (entry *Entry) Log(level Level, args ...interface{}) {
|
// trigger a panic or exit. Log treats the level as logging severity only;
|
||||||
|
// use [Entry.Panic] or [Entry.Fatal] when those side effects are desired.
|
||||||
|
func (entry *Entry) Log(level Level, args ...any) {
|
||||||
|
const panicAfter = false
|
||||||
if entry.Logger.IsLevelEnabled(level) {
|
if entry.Logger.IsLevelEnabled(level) {
|
||||||
entry.log(level, fmt.Sprint(args...))
|
entry.logArgs(level, panicAfter, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Trace(args ...interface{}) {
|
func (entry *Entry) Trace(args ...any) {
|
||||||
entry.Log(TraceLevel, args...)
|
entry.Log(TraceLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Debug(args ...interface{}) {
|
func (entry *Entry) Debug(args ...any) {
|
||||||
entry.Log(DebugLevel, args...)
|
entry.Log(DebugLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Print(args ...interface{}) {
|
func (entry *Entry) Print(args ...any) {
|
||||||
entry.Info(args...)
|
entry.Info(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Info(args ...interface{}) {
|
func (entry *Entry) Info(args ...any) {
|
||||||
entry.Log(InfoLevel, args...)
|
entry.Log(InfoLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warn(args ...interface{}) {
|
func (entry *Entry) Warn(args ...any) {
|
||||||
entry.Log(WarnLevel, args...)
|
entry.Log(WarnLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warning(args ...interface{}) {
|
func (entry *Entry) Warning(args ...any) {
|
||||||
entry.Warn(args...)
|
entry.Warn(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Error(args ...interface{}) {
|
func (entry *Entry) Error(args ...any) {
|
||||||
entry.Log(ErrorLevel, args...)
|
entry.Log(ErrorLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Fatal(args ...interface{}) {
|
func (entry *Entry) Fatal(args ...any) {
|
||||||
entry.Log(FatalLevel, args...)
|
entry.Log(FatalLevel, args...)
|
||||||
entry.Logger.Exit(1)
|
entry.Logger.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Panic(args ...interface{}) {
|
func (entry *Entry) Panic(args ...any) {
|
||||||
entry.Log(PanicLevel, args...)
|
const panicAfter = true
|
||||||
|
if entry.Logger.IsLevelEnabled(PanicLevel) {
|
||||||
|
entry.logArgs(PanicLevel, panicAfter, args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entry Printf family functions
|
// Entry Printf family functions
|
||||||
|
|
||||||
func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
|
// Logf logs a formatted message at the specified level.
|
||||||
|
//
|
||||||
|
// Using Logf with [PanicLevel] or [FatalLevel] intentionally does not
|
||||||
|
// trigger a panic or exit. Logf treats the level as logging severity only;
|
||||||
|
// use [Entry.Panicf] or [Entry.Fatalf] when those side effects are desired.
|
||||||
|
func (entry *Entry) Logf(level Level, format string, args ...any) {
|
||||||
|
const panicAfter = false
|
||||||
if entry.Logger.IsLevelEnabled(level) {
|
if entry.Logger.IsLevelEnabled(level) {
|
||||||
entry.Log(level, fmt.Sprintf(format, args...))
|
entry.logf(level, panicAfter, format, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Tracef(format string, args ...interface{}) {
|
func (entry *Entry) Tracef(format string, args ...any) {
|
||||||
entry.Logf(TraceLevel, format, args...)
|
entry.Logf(TraceLevel, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
func (entry *Entry) Debugf(format string, args ...any) {
|
||||||
entry.Logf(DebugLevel, format, args...)
|
entry.Logf(DebugLevel, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
func (entry *Entry) Infof(format string, args ...any) {
|
||||||
entry.Logf(InfoLevel, format, args...)
|
entry.Logf(InfoLevel, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
func (entry *Entry) Printf(format string, args ...any) {
|
||||||
entry.Infof(format, args...)
|
entry.Infof(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
func (entry *Entry) Warnf(format string, args ...any) {
|
||||||
entry.Logf(WarnLevel, format, args...)
|
entry.Logf(WarnLevel, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
func (entry *Entry) Warningf(format string, args ...any) {
|
||||||
entry.Warnf(format, args...)
|
entry.Warnf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
func (entry *Entry) Errorf(format string, args ...any) {
|
||||||
entry.Logf(ErrorLevel, format, args...)
|
entry.Logf(ErrorLevel, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
func (entry *Entry) Fatalf(format string, args ...any) {
|
||||||
entry.Logf(FatalLevel, format, args...)
|
entry.Logf(FatalLevel, format, args...)
|
||||||
entry.Logger.Exit(1)
|
entry.Logger.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
func (entry *Entry) Panicf(format string, args ...any) {
|
||||||
entry.Logf(PanicLevel, format, args...)
|
const panicAfter = true
|
||||||
|
if entry.Logger.IsLevelEnabled(PanicLevel) {
|
||||||
|
entry.logf(PanicLevel, panicAfter, format, args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entry Println family functions
|
// Entry Println family functions
|
||||||
|
|
||||||
func (entry *Entry) Logln(level Level, args ...interface{}) {
|
// Logln logs a message at the specified level with Println-style spacing.
|
||||||
|
//
|
||||||
|
// Using Logln with [PanicLevel] or [FatalLevel] intentionally does not
|
||||||
|
// trigger a panic or exit. Logln treats the level as logging severity only;
|
||||||
|
// use [Entry.Panicln] or [Entry.Fatalln] when those side effects are desired.
|
||||||
|
func (entry *Entry) Logln(level Level, args ...any) {
|
||||||
|
const panicAfter = false
|
||||||
if entry.Logger.IsLevelEnabled(level) {
|
if entry.Logger.IsLevelEnabled(level) {
|
||||||
entry.Log(level, entry.sprintlnn(args...))
|
entry.logln(level, panicAfter, args...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Traceln(args ...interface{}) {
|
func (entry *Entry) Traceln(args ...any) {
|
||||||
entry.Logln(TraceLevel, args...)
|
entry.Logln(TraceLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Debugln(args ...interface{}) {
|
func (entry *Entry) Debugln(args ...any) {
|
||||||
entry.Logln(DebugLevel, args...)
|
entry.Logln(DebugLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Infoln(args ...interface{}) {
|
func (entry *Entry) Infoln(args ...any) {
|
||||||
entry.Logln(InfoLevel, args...)
|
entry.Logln(InfoLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Println(args ...interface{}) {
|
func (entry *Entry) Println(args ...any) {
|
||||||
entry.Infoln(args...)
|
entry.Infoln(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warnln(args ...interface{}) {
|
func (entry *Entry) Warnln(args ...any) {
|
||||||
entry.Logln(WarnLevel, args...)
|
entry.Logln(WarnLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Warningln(args ...interface{}) {
|
func (entry *Entry) Warningln(args ...any) {
|
||||||
entry.Warnln(args...)
|
entry.Warnln(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Errorln(args ...interface{}) {
|
func (entry *Entry) Errorln(args ...any) {
|
||||||
entry.Logln(ErrorLevel, args...)
|
entry.Logln(ErrorLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
func (entry *Entry) Fatalln(args ...any) {
|
||||||
entry.Logln(FatalLevel, args...)
|
entry.Logln(FatalLevel, args...)
|
||||||
entry.Logger.Exit(1)
|
entry.Logger.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Panicln(args ...interface{}) {
|
func (entry *Entry) Panicln(args ...any) {
|
||||||
entry.Logln(PanicLevel, args...)
|
const panicAfter = true
|
||||||
|
if entry.Logger.IsLevelEnabled(PanicLevel) {
|
||||||
|
entry.logln(PanicLevel, panicAfter, args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sprintlnn => Sprint no newline. This is to get the behavior of how
|
// sprint is fmt.Sprint with fast paths for zero or one string argument.
|
||||||
// fmt.Sprintln where spaces are always added between operands, regardless of
|
func sprint(args ...any) string {
|
||||||
// their type. Instead of vendoring the Sprintln implementation to spare a
|
switch len(args) {
|
||||||
// string allocation, we do the simplest thing.
|
case 0:
|
||||||
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
return ""
|
||||||
msg := fmt.Sprintln(args...)
|
case 1:
|
||||||
return msg[:len(msg)-1]
|
if msg, ok := args[0].(string); ok {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprint(args...)
|
||||||
}
|
}
|
||||||
|
|||||||
+82
-87
@@ -6,11 +6,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// std is the package-level standard logger, similar to the default logger
|
||||||
// std is the name of the standard logger in stdlib `log`
|
// in the stdlib [log] package.
|
||||||
std = New()
|
var std = New()
|
||||||
)
|
|
||||||
|
|
||||||
|
// StandardLogger returns the package-level standard logger used by
|
||||||
|
// the top-level logging functions.
|
||||||
func StandardLogger() *Logger {
|
func StandardLogger() *Logger {
|
||||||
return std
|
return std
|
||||||
}
|
}
|
||||||
@@ -41,7 +42,7 @@ func GetLevel() Level {
|
|||||||
return std.GetLevel()
|
return std.GetLevel()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsLevelEnabled checks if the log level of the standard logger is greater than the level param
|
// IsLevelEnabled checks if logging for the given level is enabled for the standard logger.
|
||||||
func IsLevelEnabled(level Level) bool {
|
func IsLevelEnabled(level Level) bool {
|
||||||
return std.IsLevelEnabled(level)
|
return std.IsLevelEnabled(level)
|
||||||
}
|
}
|
||||||
@@ -51,9 +52,10 @@ func AddHook(hook Hook) {
|
|||||||
std.AddHook(hook)
|
std.AddHook(hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
// WithError creates an entry from the standard logger and adds an error to it,
|
||||||
|
// using the value defined in [ErrorKey] as key.
|
||||||
func WithError(err error) *Entry {
|
func WithError(err error) *Entry {
|
||||||
return std.WithField(ErrorKey, err)
|
return std.WithError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithContext creates an entry from the standard logger and adds a context to it.
|
// WithContext creates an entry from the standard logger and adds a context to it.
|
||||||
@@ -61,210 +63,203 @@ func WithContext(ctx context.Context) *Entry {
|
|||||||
return std.WithContext(ctx)
|
return std.WithContext(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithField creates an entry from the standard logger and adds a field to
|
// WithField creates an entry from the standard logger and adds a single field.
|
||||||
// it. If you want multiple fields, use `WithFields`.
|
// For multiple fields, prefer [WithFields] over chaining WithField calls.
|
||||||
//
|
func WithField(key string, value any) *Entry {
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithField(key string, value interface{}) *Entry {
|
|
||||||
return std.WithField(key, value)
|
return std.WithField(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFields creates an entry from the standard logger and adds multiple
|
// WithFields creates an entry from the standard logger and adds the fields to it.
|
||||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
|
||||||
// once for each field.
|
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithFields(fields Fields) *Entry {
|
func WithFields(fields Fields) *Entry {
|
||||||
return std.WithFields(fields)
|
return std.WithFields(fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithTime creates an entry from the standard logger and overrides the time of
|
// WithTime creates an entry from the standard logger and overrides the time
|
||||||
// logs generated with it.
|
// used for logs generated with it.
|
||||||
//
|
|
||||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
|
||||||
// or Panic on the Entry it returns.
|
|
||||||
func WithTime(t time.Time) *Entry {
|
func WithTime(t time.Time) *Entry {
|
||||||
return std.WithTime(t)
|
return std.WithTime(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trace logs a message at level Trace on the standard logger.
|
// Trace logs a message at level [TraceLevel] on the standard logger.
|
||||||
func Trace(args ...interface{}) {
|
func Trace(args ...any) {
|
||||||
std.Trace(args...)
|
std.Trace(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug logs a message at level Debug on the standard logger.
|
// Debug logs a message at level [DebugLevel] on the standard logger.
|
||||||
func Debug(args ...interface{}) {
|
func Debug(args ...any) {
|
||||||
std.Debug(args...)
|
std.Debug(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print logs a message at level Info on the standard logger.
|
// Print logs a message at level [InfoLevel] on the standard logger.
|
||||||
func Print(args ...interface{}) {
|
func Print(args ...any) {
|
||||||
std.Print(args...)
|
std.Print(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info logs a message at level Info on the standard logger.
|
// Info logs a message at level [InfoLevel] on the standard logger.
|
||||||
func Info(args ...interface{}) {
|
func Info(args ...any) {
|
||||||
std.Info(args...)
|
std.Info(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warn logs a message at level Warn on the standard logger.
|
// Warn logs a message at level [WarnLevel] on the standard logger.
|
||||||
func Warn(args ...interface{}) {
|
func Warn(args ...any) {
|
||||||
std.Warn(args...)
|
std.Warn(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning logs a message at level Warn on the standard logger.
|
// Warning logs a message at level [WarnLevel] on the standard logger.
|
||||||
func Warning(args ...interface{}) {
|
func Warning(args ...any) {
|
||||||
std.Warning(args...)
|
std.Warning(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error logs a message at level Error on the standard logger.
|
// Error logs a message at level [ErrorLevel] on the standard logger.
|
||||||
func Error(args ...interface{}) {
|
func Error(args ...any) {
|
||||||
std.Error(args...)
|
std.Error(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panic logs a message at level Panic on the standard logger.
|
// Panic logs a message at level [PanicLevel] on the standard logger.
|
||||||
func Panic(args ...interface{}) {
|
func Panic(args ...any) {
|
||||||
std.Panic(args...)
|
std.Panic(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
// Fatal logs a message at level [FatalLevel] on the standard logger,
|
||||||
func Fatal(args ...interface{}) {
|
// then exits the process with status 1.
|
||||||
|
func Fatal(args ...any) {
|
||||||
std.Fatal(args...)
|
std.Fatal(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TraceFn logs a message from a func at level Trace on the standard logger.
|
// TraceFn logs a message from a func at level [TraceLevel] on the standard logger.
|
||||||
func TraceFn(fn LogFunction) {
|
func TraceFn(fn LogFunction) {
|
||||||
std.TraceFn(fn)
|
std.TraceFn(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebugFn logs a message from a func at level Debug on the standard logger.
|
// DebugFn logs a message from a func at level [DebugLevel] on the standard logger.
|
||||||
func DebugFn(fn LogFunction) {
|
func DebugFn(fn LogFunction) {
|
||||||
std.DebugFn(fn)
|
std.DebugFn(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintFn logs a message from a func at level Info on the standard logger.
|
// PrintFn logs a message from a func at level [InfoLevel] on the standard logger.
|
||||||
func PrintFn(fn LogFunction) {
|
func PrintFn(fn LogFunction) {
|
||||||
std.PrintFn(fn)
|
std.PrintFn(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InfoFn logs a message from a func at level Info on the standard logger.
|
// InfoFn logs a message from a func at level [InfoLevel] on the standard logger.
|
||||||
func InfoFn(fn LogFunction) {
|
func InfoFn(fn LogFunction) {
|
||||||
std.InfoFn(fn)
|
std.InfoFn(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarnFn logs a message from a func at level Warn on the standard logger.
|
// WarnFn logs a message from a func at level [WarnLevel] on the standard logger.
|
||||||
func WarnFn(fn LogFunction) {
|
func WarnFn(fn LogFunction) {
|
||||||
std.WarnFn(fn)
|
std.WarnFn(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarningFn logs a message from a func at level Warn on the standard logger.
|
// WarningFn logs a message from a func at level [WarnLevel] on the standard logger.
|
||||||
func WarningFn(fn LogFunction) {
|
func WarningFn(fn LogFunction) {
|
||||||
std.WarningFn(fn)
|
std.WarningFn(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorFn logs a message from a func at level Error on the standard logger.
|
// ErrorFn logs a message from a func at level [ErrorLevel] on the standard logger.
|
||||||
func ErrorFn(fn LogFunction) {
|
func ErrorFn(fn LogFunction) {
|
||||||
std.ErrorFn(fn)
|
std.ErrorFn(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PanicFn logs a message from a func at level Panic on the standard logger.
|
// PanicFn logs a message from a func at level [PanicLevel] on the standard logger.
|
||||||
func PanicFn(fn LogFunction) {
|
func PanicFn(fn LogFunction) {
|
||||||
std.PanicFn(fn)
|
std.PanicFn(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FatalFn logs a message from a func at level Fatal on the standard logger then the process will exit with status set to 1.
|
// FatalFn logs a message from a func at level [FatalLevel] on the standard logger,
|
||||||
|
// then exits the process with status 1.
|
||||||
func FatalFn(fn LogFunction) {
|
func FatalFn(fn LogFunction) {
|
||||||
std.FatalFn(fn)
|
std.FatalFn(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tracef logs a message at level Trace on the standard logger.
|
// Tracef logs a message at level [TraceLevel] on the standard logger.
|
||||||
func Tracef(format string, args ...interface{}) {
|
func Tracef(format string, args ...any) {
|
||||||
std.Tracef(format, args...)
|
std.Tracef(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debugf logs a message at level Debug on the standard logger.
|
// Debugf logs a message at level [DebugLevel] on the standard logger.
|
||||||
func Debugf(format string, args ...interface{}) {
|
func Debugf(format string, args ...any) {
|
||||||
std.Debugf(format, args...)
|
std.Debugf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Printf logs a message at level Info on the standard logger.
|
// Printf logs a message at level [InfoLevel] on the standard logger.
|
||||||
func Printf(format string, args ...interface{}) {
|
func Printf(format string, args ...any) {
|
||||||
std.Printf(format, args...)
|
std.Printf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infof logs a message at level Info on the standard logger.
|
// Infof logs a message at level [InfoLevel] on the standard logger.
|
||||||
func Infof(format string, args ...interface{}) {
|
func Infof(format string, args ...any) {
|
||||||
std.Infof(format, args...)
|
std.Infof(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warnf logs a message at level Warn on the standard logger.
|
// Warnf logs a message at level [WarnLevel] on the standard logger.
|
||||||
func Warnf(format string, args ...interface{}) {
|
func Warnf(format string, args ...any) {
|
||||||
std.Warnf(format, args...)
|
std.Warnf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warningf logs a message at level Warn on the standard logger.
|
// Warningf logs a message at level [WarnLevel] on the standard logger.
|
||||||
func Warningf(format string, args ...interface{}) {
|
func Warningf(format string, args ...any) {
|
||||||
std.Warningf(format, args...)
|
std.Warningf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorf logs a message at level Error on the standard logger.
|
// Errorf logs a message at level [ErrorLevel] on the standard logger.
|
||||||
func Errorf(format string, args ...interface{}) {
|
func Errorf(format string, args ...any) {
|
||||||
std.Errorf(format, args...)
|
std.Errorf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panicf logs a message at level Panic on the standard logger.
|
// Panicf logs a message at level [PanicLevel] on the standard logger.
|
||||||
func Panicf(format string, args ...interface{}) {
|
func Panicf(format string, args ...any) {
|
||||||
std.Panicf(format, args...)
|
std.Panicf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
// Fatalf logs a message at level [FatalLevel] on the standard logger,
|
||||||
func Fatalf(format string, args ...interface{}) {
|
// then exits the process with status 1.
|
||||||
|
func Fatalf(format string, args ...any) {
|
||||||
std.Fatalf(format, args...)
|
std.Fatalf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traceln logs a message at level Trace on the standard logger.
|
// Traceln logs a message at level [TraceLevel] on the standard logger.
|
||||||
func Traceln(args ...interface{}) {
|
func Traceln(args ...any) {
|
||||||
std.Traceln(args...)
|
std.Traceln(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debugln logs a message at level Debug on the standard logger.
|
// Debugln logs a message at level [DebugLevel] on the standard logger.
|
||||||
func Debugln(args ...interface{}) {
|
func Debugln(args ...any) {
|
||||||
std.Debugln(args...)
|
std.Debugln(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Println logs a message at level Info on the standard logger.
|
// Println logs a message at level [InfoLevel] on the standard logger.
|
||||||
func Println(args ...interface{}) {
|
func Println(args ...any) {
|
||||||
std.Println(args...)
|
std.Println(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infoln logs a message at level Info on the standard logger.
|
// Infoln logs a message at level [InfoLevel] on the standard logger.
|
||||||
func Infoln(args ...interface{}) {
|
func Infoln(args ...any) {
|
||||||
std.Infoln(args...)
|
std.Infoln(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warnln logs a message at level Warn on the standard logger.
|
// Warnln logs a message at level [WarnLevel] on the standard logger.
|
||||||
func Warnln(args ...interface{}) {
|
func Warnln(args ...any) {
|
||||||
std.Warnln(args...)
|
std.Warnln(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warningln logs a message at level Warn on the standard logger.
|
// Warningln logs a message at level [WarnLevel] on the standard logger.
|
||||||
func Warningln(args ...interface{}) {
|
func Warningln(args ...any) {
|
||||||
std.Warningln(args...)
|
std.Warningln(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errorln logs a message at level Error on the standard logger.
|
// Errorln logs a message at level [ErrorLevel] on the standard logger.
|
||||||
func Errorln(args ...interface{}) {
|
func Errorln(args ...any) {
|
||||||
std.Errorln(args...)
|
std.Errorln(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panicln logs a message at level Panic on the standard logger.
|
// Panicln logs a message at level [PanicLevel] on the standard logger.
|
||||||
func Panicln(args ...interface{}) {
|
func Panicln(args ...any) {
|
||||||
std.Panicln(args...)
|
std.Panicln(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
// Fatalln logs a message at level [FatalLevel] on the standard logger,
|
||||||
func Fatalln(args ...interface{}) {
|
// then exits the process with status 1.
|
||||||
|
func Fatalln(args ...any) {
|
||||||
std.Fatalln(args...)
|
std.Fatalln(args...)
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-17
@@ -2,27 +2,40 @@ package logrus
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// Default key names for the default fields
|
|
||||||
const (
|
const (
|
||||||
|
// defaultTimestampFormat is the layout used to format entry timestamps
|
||||||
|
// when a formatter has not specified a custom TimestampFormat.
|
||||||
|
// It follows time.RFC3339 and is applied unless timestamps are disabled.
|
||||||
defaultTimestampFormat = time.RFC3339
|
defaultTimestampFormat = time.RFC3339
|
||||||
FieldKeyMsg = "msg"
|
|
||||||
FieldKeyLevel = "level"
|
// defaultFields is the number of commonly included predefined log entry fields
|
||||||
FieldKeyTime = "time"
|
// (msg, level, time). It is used as a capacity hint when constructing
|
||||||
FieldKeyLogrusError = "logrus_error"
|
// intermediate collections during formatting (for example, the fixed key list).
|
||||||
FieldKeyFunc = "func"
|
//
|
||||||
FieldKeyFile = "file"
|
// It does not include the optional "logrus_error", "func", or "file" fields.
|
||||||
|
defaultFields = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
// Default key names for the default fields
|
||||||
// `Entry`. It exposes all the fields, including the default ones:
|
const (
|
||||||
|
FieldKeyMsg = "msg"
|
||||||
|
FieldKeyLevel = "level"
|
||||||
|
FieldKeyTime = "time"
|
||||||
|
FieldKeyLogrusError = "logrus_error"
|
||||||
|
FieldKeyFunc = "func"
|
||||||
|
FieldKeyFile = "file"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Formatter is implemented by types that format log entries. It receives an
|
||||||
|
// [*Entry], which contains:
|
||||||
//
|
//
|
||||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
// - entry.Message: the message passed to logging methods such as [Info], [Warn], [Error]
|
||||||
// * `entry.Data["time"]`. The timestamp.
|
// - entry.Time: the timestamp
|
||||||
// * `entry.Data["level"]. The level the entry was logged at.
|
// - entry.Level: the log level
|
||||||
//
|
//
|
||||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
// Additional fields added with [WithField] or [WithFields] are available in
|
||||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
// [Entry.Data]. Format should return the formatted log entry as a byte slice,
|
||||||
// logged to `logger.Out`.
|
// which is written to [Logger.Out].
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
Format(*Entry) ([]byte, error)
|
Format(*Entry) ([]byte, error)
|
||||||
}
|
}
|
||||||
@@ -30,12 +43,12 @@ type Formatter interface {
|
|||||||
// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when
|
// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when
|
||||||
// dumping it. If this code wasn't there doing:
|
// dumping it. If this code wasn't there doing:
|
||||||
//
|
//
|
||||||
// logrus.WithField("level", 1).Info("hello")
|
// logrus.WithField("level", 1).Info("hello")
|
||||||
//
|
//
|
||||||
// Would just silently drop the user provided level. Instead with this code
|
// Would just silently drop the user provided level. Instead with this code
|
||||||
// it'll logged as:
|
// it'll logged as:
|
||||||
//
|
//
|
||||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||||
//
|
//
|
||||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||||
// avoid code duplication between the two default formatters.
|
// avoid code duplication between the two default formatters.
|
||||||
|
|||||||
+23
-14
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fieldKey string
|
type fieldKey string
|
||||||
@@ -20,7 +21,13 @@ func (f FieldMap) resolve(key fieldKey) string {
|
|||||||
return string(key)
|
return string(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSONFormatter formats logs into parsable json
|
// JSONFormatter formats logs into parsable JSON.
|
||||||
|
//
|
||||||
|
// Fields from [Entry.Data] are included in the JSON object together with the
|
||||||
|
// standard fields derived from the entry. If a field conflicts with a standard
|
||||||
|
// field, it is prefixed with "fields.". Standard field names can be customized
|
||||||
|
// through FieldMap. When DataKey is set, fields from [Entry.Data] are nested
|
||||||
|
// under that key instead.
|
||||||
type JSONFormatter struct {
|
type JSONFormatter struct {
|
||||||
// TimestampFormat sets the format used for marshaling timestamps.
|
// TimestampFormat sets the format used for marshaling timestamps.
|
||||||
// The format to use is the same than for time.Format or time.Parse from the standard
|
// The format to use is the same than for time.Format or time.Parse from the standard
|
||||||
@@ -61,7 +68,8 @@ type JSONFormatter struct {
|
|||||||
|
|
||||||
// Format renders a single log entry
|
// Format renders a single log entry
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
data := make(Fields, len(entry.Data)+4)
|
caller := entry.Caller
|
||||||
|
data := make(Fields, len(entry.Data)+defaultFields)
|
||||||
for k, v := range entry.Data {
|
for k, v := range entry.Data {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case error:
|
case error:
|
||||||
@@ -73,13 +81,14 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.DataKey != "" {
|
if f.DataKey != "" && len(entry.Data) > 0 {
|
||||||
newData := make(Fields, 4)
|
newData := make(Fields, defaultFields+1)
|
||||||
newData[f.DataKey] = data
|
newData[f.DataKey] = data
|
||||||
data = newData
|
data = newData
|
||||||
}
|
}
|
||||||
|
|
||||||
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
hasCaller := caller != nil
|
||||||
|
prefixFieldClashes(data, f.FieldMap, hasCaller)
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
timestampFormat := f.TimestampFormat
|
||||||
if timestampFormat == "" {
|
if timestampFormat == "" {
|
||||||
@@ -94,11 +103,13 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
||||||
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
||||||
if entry.HasCaller() {
|
if caller != nil {
|
||||||
funcVal := entry.Caller.Function
|
var funcVal, fileVal string
|
||||||
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
|
||||||
if f.CallerPrettyfier != nil {
|
if f.CallerPrettyfier != nil {
|
||||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
funcVal, fileVal = f.CallerPrettyfier(caller)
|
||||||
|
} else {
|
||||||
|
funcVal = caller.Function
|
||||||
|
fileVal = caller.File + ":" + strconv.FormatInt(int64(caller.Line), 10)
|
||||||
}
|
}
|
||||||
if funcVal != "" {
|
if funcVal != "" {
|
||||||
data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
|
data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
|
||||||
@@ -108,11 +119,9 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var b *bytes.Buffer
|
b := entry.Buffer
|
||||||
if entry.Buffer != nil {
|
if b == nil {
|
||||||
b = entry.Buffer
|
b = new(bytes.Buffer)
|
||||||
} else {
|
|
||||||
b = &bytes.Buffer{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
encoder := json.NewEncoder(b)
|
encoder := json.NewEncoder(b)
|
||||||
|
|||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ansiReset = "\x1b[0m" // reset attributes
|
||||||
|
ansiRed = "\x1b[31m" // red
|
||||||
|
ansiYellow = "\x1b[33m" // yellow
|
||||||
|
ansiCyan = "\x1b[36m" // cyan
|
||||||
|
ansiDimCyan = "\x1b[2;36m" // dim cyan
|
||||||
|
ansiDimWhite = "\x1b[2;37m" // dim white (light gray)
|
||||||
|
)
|
||||||
|
|
||||||
|
type lvlPrefix struct {
|
||||||
|
full string
|
||||||
|
truncated string
|
||||||
|
padded string
|
||||||
|
}
|
||||||
|
|
||||||
|
func colorize(level Level, s string) string {
|
||||||
|
color := ansiCyan
|
||||||
|
switch level {
|
||||||
|
case TraceLevel:
|
||||||
|
color = ansiDimWhite
|
||||||
|
case DebugLevel:
|
||||||
|
color = ansiDimCyan
|
||||||
|
case WarnLevel:
|
||||||
|
color = ansiYellow
|
||||||
|
case ErrorLevel, FatalLevel, PanicLevel:
|
||||||
|
color = ansiRed
|
||||||
|
case InfoLevel:
|
||||||
|
color = ansiCyan
|
||||||
|
}
|
||||||
|
return color + s + ansiReset
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatLevel(level Level, disableTrunc, pad bool, maxLen int) string {
|
||||||
|
upper := strings.ToUpper(level.String())
|
||||||
|
|
||||||
|
if pad && maxLen > len(upper) {
|
||||||
|
upper += strings.Repeat(" ", maxLen-len(upper))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pad && !disableTrunc && len(upper) > 4 {
|
||||||
|
upper = upper[:4]
|
||||||
|
}
|
||||||
|
|
||||||
|
return colorize(level, upper)
|
||||||
|
}
|
||||||
|
|
||||||
|
var levelPrefixOnce = sync.OnceValues(func() (map[Level]lvlPrefix, lvlPrefix) {
|
||||||
|
var maxLevel Level
|
||||||
|
maxLen := 0
|
||||||
|
for _, lvl := range AllLevels {
|
||||||
|
if lvl > maxLevel {
|
||||||
|
maxLevel = lvl
|
||||||
|
}
|
||||||
|
if l := len(lvl.String()); l > maxLen {
|
||||||
|
maxLen = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := make(map[Level]lvlPrefix, len(AllLevels))
|
||||||
|
for _, lvl := range AllLevels {
|
||||||
|
prefix[lvl] = lvlPrefix{
|
||||||
|
full: formatLevel(lvl, true, false, maxLen),
|
||||||
|
truncated: formatLevel(lvl, false, false, maxLen),
|
||||||
|
padded: formatLevel(lvl, true, true, maxLen),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unknownLevel := maxLevel + 1
|
||||||
|
unknown := lvlPrefix{
|
||||||
|
full: formatLevel(unknownLevel, true, false, maxLen),
|
||||||
|
truncated: formatLevel(unknownLevel, false, false, maxLen),
|
||||||
|
padded: formatLevel(unknownLevel, true, true, maxLen),
|
||||||
|
}
|
||||||
|
|
||||||
|
return prefix, unknown
|
||||||
|
})
|
||||||
|
|
||||||
|
func levelPrefix(level Level, disableTrunc, pad bool) string {
|
||||||
|
prefix, unknown := levelPrefixOnce()
|
||||||
|
|
||||||
|
p, ok := prefix[level]
|
||||||
|
if !ok {
|
||||||
|
p = unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case pad:
|
||||||
|
return p.padded
|
||||||
|
case !disableTrunc:
|
||||||
|
return p.truncated
|
||||||
|
default:
|
||||||
|
return p.full
|
||||||
|
}
|
||||||
|
}
|
||||||
+107
-49
@@ -12,17 +12,19 @@ import (
|
|||||||
// LogFunction For big messages, it can be more efficient to pass a function
|
// LogFunction For big messages, it can be more efficient to pass a function
|
||||||
// and only call it if the log level is actually enables rather than
|
// and only call it if the log level is actually enables rather than
|
||||||
// generating the log message and then checking if the level is enabled
|
// generating the log message and then checking if the level is enabled
|
||||||
type LogFunction func() []interface{}
|
type LogFunction func() []any
|
||||||
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||||
// file, or leave it default which is `os.Stderr`. You can also set this to
|
// file, or leave it default which is `os.Stderr`. You can also set this to
|
||||||
// something more adventurous, such as logging to Kafka.
|
// something more adventurous, such as logging to Kafka.
|
||||||
Out io.Writer
|
Out io.Writer
|
||||||
|
|
||||||
// Hooks for the logger instance. These allow firing events based on logging
|
// Hooks for the logger instance. These allow firing events based on logging
|
||||||
// levels and log entries. For example, to send errors to an error tracking
|
// levels and log entries. For example, to send errors to an error tracking
|
||||||
// service, log to StatsD or dump the core on fatal errors.
|
// service, log to StatsD or dump the core on fatal errors.
|
||||||
Hooks LevelHooks
|
Hooks LevelHooks
|
||||||
|
|
||||||
// All log entries pass through the formatter before logged to Out. The
|
// All log entries pass through the formatter before logged to Out. The
|
||||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||||
@@ -38,37 +40,44 @@ type Logger struct {
|
|||||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||||
// logged.
|
// logged.
|
||||||
Level Level
|
Level Level
|
||||||
|
|
||||||
// Used to sync writing to the log. Locking is enabled by Default
|
// Used to sync writing to the log. Locking is enabled by Default
|
||||||
mu MutexWrap
|
mu mutexWrap
|
||||||
|
|
||||||
// Reusable empty entry
|
// Reusable empty entry
|
||||||
entryPool sync.Pool
|
entryPool sync.Pool
|
||||||
|
|
||||||
// Function to exit the application, defaults to `os.Exit()`
|
// Function to exit the application, defaults to `os.Exit()`
|
||||||
ExitFunc exitFunc
|
ExitFunc func(int)
|
||||||
|
|
||||||
// The buffer pool used to format the log. If it is nil, the default global
|
// The buffer pool used to format the log. If it is nil, the default global
|
||||||
// buffer pool will be used.
|
// buffer pool will be used.
|
||||||
BufferPool BufferPool
|
BufferPool BufferPool
|
||||||
}
|
}
|
||||||
|
|
||||||
type exitFunc func(int)
|
// MutexWrap is the mutex implementation used by [Logger].
|
||||||
|
//
|
||||||
|
// Deprecated: MutexWrap is an implementation detail of Logger and should not be used directly.
|
||||||
|
type MutexWrap = mutexWrap
|
||||||
|
|
||||||
type MutexWrap struct {
|
type mutexWrap struct {
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
disabled bool
|
disabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mw *MutexWrap) Lock() {
|
func (mw *mutexWrap) Lock() {
|
||||||
if !mw.disabled {
|
if !mw.disabled {
|
||||||
mw.lock.Lock()
|
mw.lock.Lock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mw *MutexWrap) Unlock() {
|
func (mw *mutexWrap) Unlock() {
|
||||||
if !mw.disabled {
|
if !mw.disabled {
|
||||||
mw.lock.Unlock()
|
mw.lock.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mw *MutexWrap) Disable() {
|
func (mw *mutexWrap) Disable() {
|
||||||
mw.disabled = true
|
mw.disabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +113,7 @@ func (logger *Logger) newEntry() *Entry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) releaseEntry(entry *Entry) {
|
func (logger *Logger) releaseEntry(entry *Entry) {
|
||||||
entry.Data = map[string]interface{}{}
|
entry.Data = map[string]any{}
|
||||||
logger.entryPool.Put(entry)
|
logger.entryPool.Put(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +121,7 @@ func (logger *Logger) releaseEntry(entry *Entry) {
|
|||||||
// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to
|
// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to
|
||||||
// this new returned entry.
|
// this new returned entry.
|
||||||
// If you want multiple fields, use `WithFields`.
|
// If you want multiple fields, use `WithFields`.
|
||||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
func (logger *Logger) WithField(key string, value any) *Entry {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
defer logger.releaseEntry(entry)
|
defer logger.releaseEntry(entry)
|
||||||
return entry.WithField(key, value)
|
return entry.WithField(key, value)
|
||||||
@@ -148,7 +157,12 @@ func (logger *Logger) WithTime(t time.Time) *Entry {
|
|||||||
return entry.WithTime(t)
|
return entry.WithTime(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
|
// Logf logs a formatted message at the specified level.
|
||||||
|
//
|
||||||
|
// Using Logf with [PanicLevel] or [FatalLevel] intentionally does not
|
||||||
|
// trigger a panic or exit. Logf treats the level as logging severity only;
|
||||||
|
// use [Logger.Panicf] or [Logger.Fatalf] when those side effects are desired.
|
||||||
|
func (logger *Logger) Logf(level Level, format string, args ...any) {
|
||||||
if logger.IsLevelEnabled(level) {
|
if logger.IsLevelEnabled(level) {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Logf(level, format, args...)
|
entry.Logf(level, format, args...)
|
||||||
@@ -156,49 +170,55 @@ func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Tracef(format string, args ...interface{}) {
|
func (logger *Logger) Tracef(format string, args ...any) {
|
||||||
logger.Logf(TraceLevel, format, args...)
|
logger.Logf(TraceLevel, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
func (logger *Logger) Debugf(format string, args ...any) {
|
||||||
logger.Logf(DebugLevel, format, args...)
|
logger.Logf(DebugLevel, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
func (logger *Logger) Infof(format string, args ...any) {
|
||||||
logger.Logf(InfoLevel, format, args...)
|
logger.Logf(InfoLevel, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
func (logger *Logger) Printf(format string, args ...any) {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Printf(format, args...)
|
entry.Printf(format, args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
func (logger *Logger) Warnf(format string, args ...any) {
|
||||||
logger.Logf(WarnLevel, format, args...)
|
logger.Logf(WarnLevel, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
func (logger *Logger) Warningf(format string, args ...any) {
|
||||||
logger.Warnf(format, args...)
|
logger.Warnf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
func (logger *Logger) Errorf(format string, args ...any) {
|
||||||
logger.Logf(ErrorLevel, format, args...)
|
logger.Logf(ErrorLevel, format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
func (logger *Logger) Fatalf(format string, args ...any) {
|
||||||
logger.Logf(FatalLevel, format, args...)
|
logger.Logf(FatalLevel, format, args...)
|
||||||
logger.Exit(1)
|
logger.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
func (logger *Logger) Panicf(format string, args ...any) {
|
||||||
logger.Logf(PanicLevel, format, args...)
|
if logger.IsLevelEnabled(PanicLevel) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
entry.Panicf(format, args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log will log a message at the level given as parameter.
|
// Log logs a message at the specified level.
|
||||||
// Warning: using Log at Panic or Fatal level will not respectively Panic nor Exit.
|
//
|
||||||
// For this behaviour Logger.Panic or Logger.Fatal should be used instead.
|
// Using Log with [PanicLevel] or [FatalLevel] intentionally does not
|
||||||
func (logger *Logger) Log(level Level, args ...interface{}) {
|
// trigger a panic or exit. Log treats the level as logging severity only;
|
||||||
|
// use [Logger.Panic] or [Logger.Fatal] when those side effects are desired.
|
||||||
|
func (logger *Logger) Log(level Level, args ...any) {
|
||||||
if logger.IsLevelEnabled(level) {
|
if logger.IsLevelEnabled(level) {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Log(level, args...)
|
entry.Log(level, args...)
|
||||||
@@ -206,6 +226,11 @@ func (logger *Logger) Log(level Level, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LogFn logs a message returned by fn at the specified level.
|
||||||
|
//
|
||||||
|
// Using LogFn with [PanicLevel] or [FatalLevel] intentionally does not
|
||||||
|
// trigger a panic or exit. LogFn treats the level as logging severity only;
|
||||||
|
// use [Logger.PanicFn] or [Logger.FatalFn] when those side effects are desired.
|
||||||
func (logger *Logger) LogFn(level Level, fn LogFunction) {
|
func (logger *Logger) LogFn(level Level, fn LogFunction) {
|
||||||
if logger.IsLevelEnabled(level) {
|
if logger.IsLevelEnabled(level) {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
@@ -214,43 +239,47 @@ func (logger *Logger) LogFn(level Level, fn LogFunction) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Trace(args ...interface{}) {
|
func (logger *Logger) Trace(args ...any) {
|
||||||
logger.Log(TraceLevel, args...)
|
logger.Log(TraceLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Debug(args ...interface{}) {
|
func (logger *Logger) Debug(args ...any) {
|
||||||
logger.Log(DebugLevel, args...)
|
logger.Log(DebugLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Info(args ...interface{}) {
|
func (logger *Logger) Info(args ...any) {
|
||||||
logger.Log(InfoLevel, args...)
|
logger.Log(InfoLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Print(args ...interface{}) {
|
func (logger *Logger) Print(args ...any) {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Print(args...)
|
entry.Print(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warn(args ...interface{}) {
|
func (logger *Logger) Warn(args ...any) {
|
||||||
logger.Log(WarnLevel, args...)
|
logger.Log(WarnLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warning(args ...interface{}) {
|
func (logger *Logger) Warning(args ...any) {
|
||||||
logger.Warn(args...)
|
logger.Warn(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Error(args ...interface{}) {
|
func (logger *Logger) Error(args ...any) {
|
||||||
logger.Log(ErrorLevel, args...)
|
logger.Log(ErrorLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Fatal(args ...interface{}) {
|
func (logger *Logger) Fatal(args ...any) {
|
||||||
logger.Log(FatalLevel, args...)
|
logger.Log(FatalLevel, args...)
|
||||||
logger.Exit(1)
|
logger.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Panic(args ...interface{}) {
|
func (logger *Logger) Panic(args ...any) {
|
||||||
logger.Log(PanicLevel, args...)
|
if logger.IsLevelEnabled(PanicLevel) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
entry.Panic(args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) TraceFn(fn LogFunction) {
|
func (logger *Logger) TraceFn(fn LogFunction) {
|
||||||
@@ -289,10 +318,19 @@ func (logger *Logger) FatalFn(fn LogFunction) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) PanicFn(fn LogFunction) {
|
func (logger *Logger) PanicFn(fn LogFunction) {
|
||||||
logger.LogFn(PanicLevel, fn)
|
if logger.IsLevelEnabled(PanicLevel) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
entry.Panic(fn()...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Logln(level Level, args ...interface{}) {
|
// Logln logs a message at the specified level with Println-style spacing.
|
||||||
|
//
|
||||||
|
// Using Logln with [PanicLevel] or [FatalLevel] intentionally does not
|
||||||
|
// trigger a panic or exit. Logln treats the level as logging severity only;
|
||||||
|
// use [Logger.Panicln] or [Logger.Fatalln] when those side effects are desired.
|
||||||
|
func (logger *Logger) Logln(level Level, args ...any) {
|
||||||
if logger.IsLevelEnabled(level) {
|
if logger.IsLevelEnabled(level) {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Logln(level, args...)
|
entry.Logln(level, args...)
|
||||||
@@ -300,43 +338,47 @@ func (logger *Logger) Logln(level Level, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Traceln(args ...interface{}) {
|
func (logger *Logger) Traceln(args ...any) {
|
||||||
logger.Logln(TraceLevel, args...)
|
logger.Logln(TraceLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Debugln(args ...interface{}) {
|
func (logger *Logger) Debugln(args ...any) {
|
||||||
logger.Logln(DebugLevel, args...)
|
logger.Logln(DebugLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Infoln(args ...interface{}) {
|
func (logger *Logger) Infoln(args ...any) {
|
||||||
logger.Logln(InfoLevel, args...)
|
logger.Logln(InfoLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Println(args ...interface{}) {
|
func (logger *Logger) Println(args ...any) {
|
||||||
entry := logger.newEntry()
|
entry := logger.newEntry()
|
||||||
entry.Println(args...)
|
entry.Println(args...)
|
||||||
logger.releaseEntry(entry)
|
logger.releaseEntry(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warnln(args ...interface{}) {
|
func (logger *Logger) Warnln(args ...any) {
|
||||||
logger.Logln(WarnLevel, args...)
|
logger.Logln(WarnLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Warningln(args ...interface{}) {
|
func (logger *Logger) Warningln(args ...any) {
|
||||||
logger.Warnln(args...)
|
logger.Warnln(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Errorln(args ...interface{}) {
|
func (logger *Logger) Errorln(args ...any) {
|
||||||
logger.Logln(ErrorLevel, args...)
|
logger.Logln(ErrorLevel, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
func (logger *Logger) Fatalln(args ...any) {
|
||||||
logger.Logln(FatalLevel, args...)
|
logger.Logln(FatalLevel, args...)
|
||||||
logger.Exit(1)
|
logger.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Panicln(args ...interface{}) {
|
func (logger *Logger) Panicln(args ...any) {
|
||||||
logger.Logln(PanicLevel, args...)
|
if logger.IsLevelEnabled(PanicLevel) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
entry.Panicln(args...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logger *Logger) Exit(code int) {
|
func (logger *Logger) Exit(code int) {
|
||||||
@@ -375,7 +417,22 @@ func (logger *Logger) AddHook(hook Hook) {
|
|||||||
logger.Hooks.Add(hook)
|
logger.Hooks.Add(hook)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsLevelEnabled checks if the log level of the logger is greater than the level param
|
// hooksForLevel returns a snapshot of the hooks registered for the given level.
|
||||||
|
// The returned slice is a shallow copy and may be used without holding logger.mu.
|
||||||
|
func (logger *Logger) hooksForLevel(level Level) []Hook {
|
||||||
|
logger.mu.Lock()
|
||||||
|
hooks := logger.Hooks[level]
|
||||||
|
if len(hooks) == 0 {
|
||||||
|
logger.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := make([]Hook, len(hooks))
|
||||||
|
copy(out, hooks)
|
||||||
|
logger.mu.Unlock()
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLevelEnabled checks if logging for the given level is enabled.
|
||||||
func (logger *Logger) IsLevelEnabled(level Level) bool {
|
func (logger *Logger) IsLevelEnabled(level Level) bool {
|
||||||
return logger.level() >= level
|
return logger.level() >= level
|
||||||
}
|
}
|
||||||
@@ -394,6 +451,7 @@ func (logger *Logger) SetOutput(output io.Writer) {
|
|||||||
logger.Out = output
|
logger.Out = output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetReportCaller sets whether the caller stack frame must be logged.
|
||||||
func (logger *Logger) SetReportCaller(reportCaller bool) {
|
func (logger *Logger) SetReportCaller(reportCaller bool) {
|
||||||
logger.mu.Lock()
|
logger.mu.Lock()
|
||||||
defer logger.mu.Unlock()
|
defer logger.mu.Unlock()
|
||||||
@@ -403,9 +461,9 @@ func (logger *Logger) SetReportCaller(reportCaller bool) {
|
|||||||
// ReplaceHooks replaces the logger hooks and returns the old ones
|
// ReplaceHooks replaces the logger hooks and returns the old ones
|
||||||
func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks {
|
func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks {
|
||||||
logger.mu.Lock()
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
oldHooks := logger.Hooks
|
oldHooks := logger.Hooks
|
||||||
logger.Hooks = hooks
|
logger.Hooks = hooks
|
||||||
logger.mu.Unlock()
|
|
||||||
return oldHooks
|
return oldHooks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+129
-92
@@ -1,13 +1,13 @@
|
|||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fields type, used to pass to [WithFields].
|
// Fields type, used to pass to [WithFields].
|
||||||
type Fields map[string]interface{}
|
type Fields map[string]any
|
||||||
|
|
||||||
// Level type
|
// Level type
|
||||||
//
|
//
|
||||||
@@ -16,39 +16,56 @@ type Level uint32
|
|||||||
|
|
||||||
// Convert the Level to a string. E.g. [PanicLevel] becomes "panic".
|
// Convert the Level to a string. E.g. [PanicLevel] becomes "panic".
|
||||||
func (level Level) String() string {
|
func (level Level) String() string {
|
||||||
if b, err := level.MarshalText(); err == nil {
|
switch level {
|
||||||
return string(b)
|
case TraceLevel:
|
||||||
} else {
|
return "trace"
|
||||||
|
case DebugLevel:
|
||||||
|
return "debug"
|
||||||
|
case InfoLevel:
|
||||||
|
return "info"
|
||||||
|
case WarnLevel:
|
||||||
|
return "warning"
|
||||||
|
case ErrorLevel:
|
||||||
|
return "error"
|
||||||
|
case FatalLevel:
|
||||||
|
return "fatal"
|
||||||
|
case PanicLevel:
|
||||||
|
return "panic"
|
||||||
|
default:
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||||
func ParseLevel(lvl string) (Level, error) {
|
func ParseLevel(lvl string) (Level, error) {
|
||||||
switch strings.ToLower(lvl) {
|
return parseLevel([]byte(lvl))
|
||||||
case "panic":
|
}
|
||||||
return PanicLevel, nil
|
|
||||||
case "fatal":
|
|
||||||
return FatalLevel, nil
|
|
||||||
case "error":
|
|
||||||
return ErrorLevel, nil
|
|
||||||
case "warn", "warning":
|
|
||||||
return WarnLevel, nil
|
|
||||||
case "info":
|
|
||||||
return InfoLevel, nil
|
|
||||||
case "debug":
|
|
||||||
return DebugLevel, nil
|
|
||||||
case "trace":
|
|
||||||
return TraceLevel, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var l Level
|
func parseLevel(b []byte) (Level, error) {
|
||||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
switch {
|
||||||
|
case bytes.EqualFold(b, []byte("panic")):
|
||||||
|
return PanicLevel, nil
|
||||||
|
case bytes.EqualFold(b, []byte("fatal")):
|
||||||
|
return FatalLevel, nil
|
||||||
|
case bytes.EqualFold(b, []byte("error")):
|
||||||
|
return ErrorLevel, nil
|
||||||
|
case bytes.EqualFold(b, []byte("warn")),
|
||||||
|
bytes.EqualFold(b, []byte("warning")):
|
||||||
|
return WarnLevel, nil
|
||||||
|
case bytes.EqualFold(b, []byte("info")):
|
||||||
|
return InfoLevel, nil
|
||||||
|
case bytes.EqualFold(b, []byte("debug")):
|
||||||
|
return DebugLevel, nil
|
||||||
|
case bytes.EqualFold(b, []byte("trace")):
|
||||||
|
return TraceLevel, nil
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("not a valid logrus Level: %q", b)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||||
func (level *Level) UnmarshalText(text []byte) error {
|
func (level *Level) UnmarshalText(text []byte) error {
|
||||||
l, err := ParseLevel(string(text))
|
l, err := parseLevel(text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -60,23 +77,11 @@ func (level *Level) UnmarshalText(text []byte) error {
|
|||||||
|
|
||||||
func (level Level) MarshalText() ([]byte, error) {
|
func (level Level) MarshalText() ([]byte, error) {
|
||||||
switch level {
|
switch level {
|
||||||
case TraceLevel:
|
case TraceLevel, DebugLevel, InfoLevel, WarnLevel, ErrorLevel, FatalLevel, PanicLevel:
|
||||||
return []byte("trace"), nil
|
return []byte(level.String()), nil
|
||||||
case DebugLevel:
|
default:
|
||||||
return []byte("debug"), nil
|
return nil, fmt.Errorf("not a valid logrus level %d", level)
|
||||||
case InfoLevel:
|
|
||||||
return []byte("info"), nil
|
|
||||||
case WarnLevel:
|
|
||||||
return []byte("warning"), nil
|
|
||||||
case ErrorLevel:
|
|
||||||
return []byte("error"), nil
|
|
||||||
case FatalLevel:
|
|
||||||
return []byte("fatal"), nil
|
|
||||||
case PanicLevel:
|
|
||||||
return []byte("panic"), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("not a valid logrus level %d", level)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllLevels exposing all logging levels.
|
// AllLevels exposing all logging levels.
|
||||||
@@ -91,7 +96,7 @@ var AllLevels = []Level{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// These are the different logging levels. You can set the logging level to log
|
// These are the different logging levels. You can set the logging level to log
|
||||||
// on your instance of logger, obtained with `logrus.New()`.
|
// on your instance of logger, obtained with [logrus.New].
|
||||||
const (
|
const (
|
||||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||||
// message passed to Debug, Info, ...
|
// message passed to Debug, Info, ...
|
||||||
@@ -113,78 +118,110 @@ const (
|
|||||||
TraceLevel
|
TraceLevel
|
||||||
)
|
)
|
||||||
|
|
||||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
// Compile-time interface assertions.
|
||||||
var (
|
var (
|
||||||
_ StdLogger = &log.Logger{}
|
_ StdLogger = (*log.Logger)(nil)
|
||||||
_ StdLogger = &Entry{}
|
_ StdLogger = (*Entry)(nil)
|
||||||
_ StdLogger = &Logger{}
|
_ StdLogger = (*Logger)(nil)
|
||||||
|
|
||||||
|
_ FieldLogger = (*Logger)(nil)
|
||||||
|
_ FieldLogger = (*Entry)(nil)
|
||||||
|
_ FieldLogger = Ext1FieldLogger(nil)
|
||||||
|
|
||||||
|
_ DebugLogger = (*Logger)(nil)
|
||||||
|
_ InfoLogger = (*Logger)(nil)
|
||||||
|
_ WarnLogger = (*Logger)(nil)
|
||||||
|
_ ErrorLogger = (*Logger)(nil)
|
||||||
|
_ TraceLogger = (*Logger)(nil)
|
||||||
|
|
||||||
|
_ DebugLogger = (*Entry)(nil)
|
||||||
|
_ InfoLogger = (*Entry)(nil)
|
||||||
|
_ WarnLogger = (*Entry)(nil)
|
||||||
|
_ ErrorLogger = (*Entry)(nil)
|
||||||
|
_ TraceLogger = (*Entry)(nil)
|
||||||
|
|
||||||
|
_ Ext1FieldLogger = (*Logger)(nil)
|
||||||
|
_ Ext1FieldLogger = (*Entry)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// StdLogger is what your logrus-enabled library should take, that way
|
// StdLogger is what your logrus-enabled library should take, that way
|
||||||
// it'll accept a stdlib logger ([log.Logger]) and a logrus logger.
|
// it'll accept a stdlib logger ([log.Logger]) and a logrus logger.
|
||||||
// There's no standard interface, so this is the closest we get, unfortunately.
|
// There's no standard interface, so this is the closest we get, unfortunately.
|
||||||
type StdLogger interface {
|
type StdLogger interface {
|
||||||
Print(...interface{})
|
Print(args ...any)
|
||||||
Printf(string, ...interface{})
|
Printf(format string, args ...any)
|
||||||
Println(...interface{})
|
Println(args ...any)
|
||||||
|
|
||||||
Fatal(...interface{})
|
Fatal(args ...any)
|
||||||
Fatalf(string, ...interface{})
|
Fatalf(format string, args ...any)
|
||||||
Fatalln(...interface{})
|
Fatalln(args ...any)
|
||||||
|
|
||||||
Panic(...interface{})
|
Panic(args ...any)
|
||||||
Panicf(string, ...interface{})
|
Panicf(format string, args ...any)
|
||||||
Panicln(...interface{})
|
Panicln(args ...any)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FieldLogger extends the [StdLogger] interface, generalizing
|
// FieldLogger extends the [StdLogger] interface, generalizing
|
||||||
// the [Entry] and [Logger] types.
|
// the [Entry] and [Logger] types.
|
||||||
type FieldLogger interface {
|
type FieldLogger interface {
|
||||||
WithField(key string, value interface{}) *Entry
|
WithField(key string, value any) *Entry
|
||||||
WithFields(fields Fields) *Entry
|
WithFields(fields Fields) *Entry
|
||||||
WithError(err error) *Entry
|
WithError(err error) *Entry
|
||||||
|
|
||||||
Debugf(format string, args ...interface{})
|
StdLogger
|
||||||
Infof(format string, args ...interface{})
|
DebugLogger
|
||||||
Printf(format string, args ...interface{})
|
InfoLogger
|
||||||
Warnf(format string, args ...interface{})
|
WarnLogger
|
||||||
Warningf(format string, args ...interface{})
|
ErrorLogger
|
||||||
Errorf(format string, args ...interface{})
|
|
||||||
Fatalf(format string, args ...interface{})
|
|
||||||
Panicf(format string, args ...interface{})
|
|
||||||
|
|
||||||
Debug(args ...interface{})
|
// Legacy warning aliases. These are kept on FieldLogger for backwards
|
||||||
Info(args ...interface{})
|
// compatibility, but are intentionally omitted from [WarnLogger].
|
||||||
Print(args ...interface{})
|
|
||||||
Warn(args ...interface{})
|
|
||||||
Warning(args ...interface{})
|
|
||||||
Error(args ...interface{})
|
|
||||||
Fatal(args ...interface{})
|
|
||||||
Panic(args ...interface{})
|
|
||||||
|
|
||||||
Debugln(args ...interface{})
|
Warning(args ...any)
|
||||||
Infoln(args ...interface{})
|
Warningf(format string, args ...any)
|
||||||
Println(args ...interface{})
|
Warningln(args ...any)
|
||||||
Warnln(args ...interface{})
|
|
||||||
Warningln(args ...interface{})
|
|
||||||
Errorln(args ...interface{})
|
|
||||||
Fatalln(args ...interface{})
|
|
||||||
Panicln(args ...interface{})
|
|
||||||
|
|
||||||
// IsDebugEnabled() bool
|
|
||||||
// IsInfoEnabled() bool
|
|
||||||
// IsWarnEnabled() bool
|
|
||||||
// IsErrorEnabled() bool
|
|
||||||
// IsFatalEnabled() bool
|
|
||||||
// IsPanicEnabled() bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ext1FieldLogger (the first extension to [FieldLogger]) is superfluous, it is
|
// DebugLogger provides convenience functions to log messages at level [DebugLevel].
|
||||||
// here for consistency. Do not use. Use [FieldLogger], [Logger] or [Entry]
|
type DebugLogger interface {
|
||||||
// instead.
|
Debug(args ...any)
|
||||||
|
Debugf(format string, args ...any)
|
||||||
|
Debugln(args ...any)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoLogger provides convenience functions to log messages at level [InfoLevel].
|
||||||
|
type InfoLogger interface {
|
||||||
|
Info(args ...any)
|
||||||
|
Infof(format string, args ...any)
|
||||||
|
Infoln(args ...any)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WarnLogger provides convenience functions to log messages at level [WarnLevel].
|
||||||
|
type WarnLogger interface {
|
||||||
|
Warn(args ...any)
|
||||||
|
Warnf(format string, args ...any)
|
||||||
|
Warnln(args ...any)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorLogger provides convenience functions to log messages at level [ErrorLevel].
|
||||||
|
type ErrorLogger interface {
|
||||||
|
Error(args ...any)
|
||||||
|
Errorf(format string, args ...any)
|
||||||
|
Errorln(args ...any)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceLogger provides convenience functions to log messages at level [TraceLevel].
|
||||||
|
type TraceLogger interface {
|
||||||
|
Trace(args ...any)
|
||||||
|
Tracef(format string, args ...any)
|
||||||
|
Traceln(args ...any)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ext1FieldLogger is FieldLogger extended with Trace-level methods.
|
||||||
|
//
|
||||||
|
// New code should prefer the smallest applicable interface, such as
|
||||||
|
// [FieldLogger] or [TraceLogger], or use [Logger] or [Entry] directly.
|
||||||
type Ext1FieldLogger interface {
|
type Ext1FieldLogger interface {
|
||||||
FieldLogger
|
FieldLogger
|
||||||
Tracef(format string, args ...interface{})
|
TraceLogger
|
||||||
Trace(args ...interface{})
|
|
||||||
Traceln(args ...interface{})
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-6
@@ -1,11 +1,7 @@
|
|||||||
// +build appengine
|
//go:build appengine
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import (
|
func checkIfTerminal(_ any) bool {
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkIfTerminal(w io.Writer) bool {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -1,5 +1,4 @@
|
|||||||
// +build darwin dragonfly freebsd netbsd openbsd hurd
|
//go:build (darwin || dragonfly || freebsd || netbsd || openbsd || hurd) && !tinygo
|
||||||
// +build !js
|
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
|
|||||||
-7
@@ -1,7 +0,0 @@
|
|||||||
// +build js
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
func isTerminal(fd int) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
+2
-6
@@ -1,11 +1,7 @@
|
|||||||
// +build js nacl plan9
|
//go:build js || nacl || plan9 || wasi || wasip1 || tinygo
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import (
|
func checkIfTerminal(_ any) bool {
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func checkIfTerminal(w io.Writer) bool {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-2
@@ -1,4 +1,4 @@
|
|||||||
// +build !appengine,!js,!windows,!nacl,!plan9
|
//go:build !appengine && !js && !windows && !nacl && !plan9 && !wasi && !wasip1 && !tinygo
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
@@ -10,7 +10,11 @@ import (
|
|||||||
func checkIfTerminal(w io.Writer) bool {
|
func checkIfTerminal(w io.Writer) bool {
|
||||||
switch v := w.(type) {
|
switch v := w.(type) {
|
||||||
case *os.File:
|
case *os.File:
|
||||||
return isTerminal(int(v.Fd()))
|
fd := v.Fd()
|
||||||
|
if fd > uintptr(^uint(0)>>1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return isTerminal(int(fd))
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -1,3 +1,5 @@
|
|||||||
|
//go:build solaris && !tinygo
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
+1
-4
@@ -1,7 +1,4 @@
|
|||||||
//go:build (linux || aix || zos) && !js && !wasi
|
//go:build (linux || aix || zos) && !tinygo
|
||||||
// +build linux aix zos
|
|
||||||
// +build !js
|
|
||||||
// +build !wasi
|
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
|
|||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
//go:build wasi
|
|
||||||
// +build wasi
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
func isTerminal(fd int) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
-8
@@ -1,8 +0,0 @@
|
|||||||
//go:build wasip1
|
|
||||||
// +build wasip1
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
func isTerminal(fd int) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// +build !appengine,!js,windows
|
//go:build windows && !appengine
|
||||||
|
|
||||||
package logrus
|
package logrus
|
||||||
|
|
||||||
|
|||||||
+317
-174
@@ -3,30 +3,32 @@ package logrus
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var baseTimestamp = time.Now()
|
||||||
red = 31
|
|
||||||
yellow = 33
|
|
||||||
blue = 36
|
|
||||||
gray = 37
|
|
||||||
)
|
|
||||||
|
|
||||||
var baseTimestamp time.Time
|
// TextFormatter formats logs into text.
|
||||||
|
//
|
||||||
func init() {
|
// Output is logfmt-like: key=value pairs separated by spaces. Fields from
|
||||||
baseTimestamp = time.Now()
|
// [Entry.Data] are included together with the standard fields derived from the
|
||||||
}
|
// entry. If a field conflicts with a standard field, it is prefixed with
|
||||||
|
// "fields.". Standard field names can be customized through FieldMap.
|
||||||
// TextFormatter formats logs into text
|
//
|
||||||
|
// Field keys are written as-is (unquoted and unescaped) in the plain
|
||||||
|
// (non-colored) format; only field values may be quoted depending on
|
||||||
|
// DisableQuote, ForceQuote, QuoteEmptyFields, and the value content.
|
||||||
|
//
|
||||||
|
// When colors are enabled, ANSI escape sequences may be added for presentation.
|
||||||
|
// For fully escaped structured output (including safe keys), use JSONFormatter.
|
||||||
type TextFormatter struct {
|
type TextFormatter struct {
|
||||||
// Set to true to bypass checking for a TTY before outputting colors.
|
// Set to true to bypass checking for a TTY before outputting colors.
|
||||||
ForceColors bool
|
ForceColors bool
|
||||||
@@ -64,7 +66,7 @@ type TextFormatter struct {
|
|||||||
// be desired.
|
// be desired.
|
||||||
DisableSorting bool
|
DisableSorting bool
|
||||||
|
|
||||||
// The keys sorting function, when uninitialized it uses sort.Strings.
|
// The keys sorting function, when uninitialized it uses slices.Sort.
|
||||||
SortingFunc func([]string)
|
SortingFunc func([]string)
|
||||||
|
|
||||||
// Disables the truncation of the level text to 4 characters.
|
// Disables the truncation of the level text to 4 characters.
|
||||||
@@ -77,16 +79,22 @@ type TextFormatter struct {
|
|||||||
// QuoteEmptyFields will wrap empty fields in quotes if true
|
// QuoteEmptyFields will wrap empty fields in quotes if true
|
||||||
QuoteEmptyFields bool
|
QuoteEmptyFields bool
|
||||||
|
|
||||||
// Whether the logger's out is to a terminal
|
// Whether the logger's out is to a terminal. Don't use this field
|
||||||
isTerminal bool
|
// directly; use TextFormatter.isTerminal instead.
|
||||||
|
terminal bool
|
||||||
|
|
||||||
// FieldMap allows users to customize the names of keys for default fields.
|
// FieldMap allows users to customize the names of keys for default fields.
|
||||||
|
// Mapped keys are written as-is, so they should be safe for plain-text output.
|
||||||
|
//
|
||||||
// As an example:
|
// As an example:
|
||||||
|
//
|
||||||
// formatter := &TextFormatter{
|
// formatter := &TextFormatter{
|
||||||
// FieldMap: FieldMap{
|
// FieldMap: FieldMap{
|
||||||
// FieldKeyTime: "@timestamp",
|
// FieldKeyTime: "@timestamp",
|
||||||
// FieldKeyLevel: "@level",
|
// FieldKeyLevel: "@level",
|
||||||
// FieldKeyMsg: "@message"}}
|
// FieldKeyMsg: "@message",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
FieldMap FieldMap
|
FieldMap FieldMap
|
||||||
|
|
||||||
// CallerPrettyfier can be set by the user to modify the content
|
// CallerPrettyfier can be set by the user to modify the content
|
||||||
@@ -96,54 +104,78 @@ type TextFormatter struct {
|
|||||||
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
||||||
|
|
||||||
terminalInitOnce sync.Once
|
terminalInitOnce sync.Once
|
||||||
|
|
||||||
// The max length of the level text, generated dynamically on init
|
|
||||||
levelTextMaxLength int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) init(entry *Entry) {
|
func (f *TextFormatter) isTerminal(entry *Entry) bool {
|
||||||
if entry.Logger != nil {
|
if entry == nil || entry.Logger == nil {
|
||||||
f.isTerminal = checkIfTerminal(entry.Logger.Out)
|
// Don't run the terminalInitOnce without a logger, otherwise we'd
|
||||||
}
|
// cache the default (false) forever even if a logger is attached
|
||||||
// Get the max length of the level text
|
// later.
|
||||||
for _, level := range AllLevels {
|
return false
|
||||||
levelTextLength := utf8.RuneCount([]byte(level.String()))
|
|
||||||
if levelTextLength > f.levelTextMaxLength {
|
|
||||||
f.levelTextMaxLength = levelTextLength
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f.terminalInitOnce.Do(func() {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
out := entry.Logger.Out
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
|
||||||
|
f.terminal = checkIfTerminal(out)
|
||||||
|
})
|
||||||
|
|
||||||
|
return f.terminal
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) isColored() bool {
|
func (f *TextFormatter) isColored(isTerminal bool) bool {
|
||||||
isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
|
if f.DisableColors {
|
||||||
|
return false
|
||||||
if f.EnvironmentOverrideColors {
|
|
||||||
switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); {
|
|
||||||
case ok && force != "0":
|
|
||||||
isColored = true
|
|
||||||
case ok && force == "0", os.Getenv("CLICOLOR") == "0":
|
|
||||||
isColored = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return isColored && !f.DisableColors
|
colored := f.ForceColors || isTerminal
|
||||||
|
if !f.EnvironmentOverrideColors {
|
||||||
|
return colored
|
||||||
|
}
|
||||||
|
if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok {
|
||||||
|
return force != "0"
|
||||||
|
}
|
||||||
|
if os.Getenv("CLICOLOR") == "0" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return colored
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format renders a single log entry
|
// Format renders a single log entry
|
||||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
data := make(Fields)
|
data := make(Fields, len(entry.Data))
|
||||||
for k, v := range entry.Data {
|
maps.Copy(data, entry.Data)
|
||||||
data[k] = v
|
isColored := f.isColored(f.isTerminal(entry))
|
||||||
}
|
|
||||||
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
caller := entry.Caller
|
||||||
|
hasCaller := caller != nil
|
||||||
|
prefixFieldClashes(data, f.FieldMap, hasCaller)
|
||||||
keys := make([]string, 0, len(data))
|
keys := make([]string, 0, len(data))
|
||||||
for k := range data {
|
for k := range data {
|
||||||
keys = append(keys, k)
|
keys = append(keys, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
var funcVal, fileVal string
|
b := entry.Buffer
|
||||||
|
if b == nil {
|
||||||
|
b = new(bytes.Buffer)
|
||||||
|
}
|
||||||
|
|
||||||
fixedKeys := make([]string, 0, 4+len(data))
|
if isColored {
|
||||||
|
f.printColored(b, entry, keys, data)
|
||||||
|
} else {
|
||||||
|
f.printPlain(b, entry, keys, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) printPlain(b *bytes.Buffer, entry *Entry, keys []string, data Fields) {
|
||||||
|
caller := entry.Caller
|
||||||
|
hasCaller := caller != nil
|
||||||
|
|
||||||
|
fixedKeys := make([]string, 0, len(keys)+defaultFields)
|
||||||
if !f.DisableTimestamp {
|
if !f.DisableTimestamp {
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
|
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
|
||||||
}
|
}
|
||||||
@@ -154,12 +186,14 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
|||||||
if entry.err != "" {
|
if entry.err != "" {
|
||||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
|
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
|
||||||
}
|
}
|
||||||
if entry.HasCaller() {
|
|
||||||
|
var funcVal, fileVal string
|
||||||
|
if caller != nil {
|
||||||
if f.CallerPrettyfier != nil {
|
if f.CallerPrettyfier != nil {
|
||||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
funcVal, fileVal = f.CallerPrettyfier(caller)
|
||||||
} else {
|
} else {
|
||||||
funcVal = entry.Caller.Function
|
funcVal = caller.Function
|
||||||
fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
fileVal = caller.File + ":" + strconv.FormatInt(int64(caller.Line), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
if funcVal != "" {
|
if funcVal != "" {
|
||||||
@@ -172,152 +206,108 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
|||||||
|
|
||||||
if !f.DisableSorting {
|
if !f.DisableSorting {
|
||||||
if f.SortingFunc == nil {
|
if f.SortingFunc == nil {
|
||||||
sort.Strings(keys)
|
// Default sorting does not sort the "fixed keys";
|
||||||
|
// see https://github.com/sirupsen/logrus/commit/73bc94e60c753099e8bae902f81fbd6e7dd95f26
|
||||||
|
slices.Sort(keys)
|
||||||
fixedKeys = append(fixedKeys, keys...)
|
fixedKeys = append(fixedKeys, keys...)
|
||||||
} else {
|
} else {
|
||||||
if !f.isColored() {
|
fixedKeys = append(fixedKeys, keys...)
|
||||||
fixedKeys = append(fixedKeys, keys...)
|
f.SortingFunc(fixedKeys)
|
||||||
f.SortingFunc(fixedKeys)
|
|
||||||
} else {
|
|
||||||
f.SortingFunc(keys)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fixedKeys = append(fixedKeys, keys...)
|
fixedKeys = append(fixedKeys, keys...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var b *bytes.Buffer
|
for _, key := range fixedKeys {
|
||||||
if entry.Buffer != nil {
|
var value any
|
||||||
b = entry.Buffer
|
switch {
|
||||||
} else {
|
case key == f.FieldMap.resolve(FieldKeyTime):
|
||||||
b = &bytes.Buffer{}
|
if f.TimestampFormat == "" {
|
||||||
}
|
value = entry.Time.Format(defaultTimestampFormat)
|
||||||
|
} else {
|
||||||
f.terminalInitOnce.Do(func() { f.init(entry) })
|
value = entry.Time.Format(f.TimestampFormat)
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
|
||||||
if timestampFormat == "" {
|
|
||||||
timestampFormat = defaultTimestampFormat
|
|
||||||
}
|
|
||||||
if f.isColored() {
|
|
||||||
f.printColored(b, entry, keys, data, timestampFormat)
|
|
||||||
} else {
|
|
||||||
|
|
||||||
for _, key := range fixedKeys {
|
|
||||||
var value interface{}
|
|
||||||
switch {
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyTime):
|
|
||||||
value = entry.Time.Format(timestampFormat)
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyLevel):
|
|
||||||
value = entry.Level.String()
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyMsg):
|
|
||||||
value = entry.Message
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyLogrusError):
|
|
||||||
value = entry.err
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
|
|
||||||
value = funcVal
|
|
||||||
case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
|
|
||||||
value = fileVal
|
|
||||||
default:
|
|
||||||
value = data[key]
|
|
||||||
}
|
}
|
||||||
f.appendKeyValue(b, key, value)
|
case key == f.FieldMap.resolve(FieldKeyLevel):
|
||||||
|
value = entry.Level.String()
|
||||||
|
case key == f.FieldMap.resolve(FieldKeyMsg):
|
||||||
|
value = entry.Message
|
||||||
|
case key == f.FieldMap.resolve(FieldKeyLogrusError):
|
||||||
|
value = entry.err
|
||||||
|
case key == f.FieldMap.resolve(FieldKeyFunc) && hasCaller:
|
||||||
|
value = funcVal
|
||||||
|
case key == f.FieldMap.resolve(FieldKeyFile) && hasCaller:
|
||||||
|
value = fileVal
|
||||||
|
default:
|
||||||
|
value = data[key]
|
||||||
}
|
}
|
||||||
|
f.appendKeyValue(b, key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.WriteByte('\n')
|
b.WriteByte('\n')
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) {
|
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields) {
|
||||||
var levelColor int
|
|
||||||
switch entry.Level {
|
|
||||||
case DebugLevel, TraceLevel:
|
|
||||||
levelColor = gray
|
|
||||||
case WarnLevel:
|
|
||||||
levelColor = yellow
|
|
||||||
case ErrorLevel, FatalLevel, PanicLevel:
|
|
||||||
levelColor = red
|
|
||||||
case InfoLevel:
|
|
||||||
levelColor = blue
|
|
||||||
default:
|
|
||||||
levelColor = blue
|
|
||||||
}
|
|
||||||
|
|
||||||
levelText := strings.ToUpper(entry.Level.String())
|
|
||||||
if !f.DisableLevelTruncation && !f.PadLevelText {
|
|
||||||
levelText = levelText[0:4]
|
|
||||||
}
|
|
||||||
if f.PadLevelText {
|
|
||||||
// Generates the format string used in the next line, for example "%-6s" or "%-7s".
|
|
||||||
// Based on the max level text length.
|
|
||||||
formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s"
|
|
||||||
// Formats the level text by appending spaces up to the max length, for example:
|
|
||||||
// - "INFO "
|
|
||||||
// - "WARNING"
|
|
||||||
levelText = fmt.Sprintf(formatString, levelText)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a single newline if it already exists in the message to keep
|
// Remove a single newline if it already exists in the message to keep
|
||||||
// the behavior of logrus text_formatter the same as the stdlib log package
|
// the behavior of logrus text_formatter the same as the stdlib log package
|
||||||
entry.Message = strings.TrimSuffix(entry.Message, "\n")
|
entry.Message = strings.TrimSuffix(entry.Message, "\n")
|
||||||
|
|
||||||
caller := ""
|
var callerText string
|
||||||
if entry.HasCaller() {
|
if caller := entry.Caller; caller != nil {
|
||||||
funcVal := fmt.Sprintf("%s()", entry.Caller.Function)
|
var funcVal, fileVal string
|
||||||
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
|
||||||
|
|
||||||
if f.CallerPrettyfier != nil {
|
if f.CallerPrettyfier != nil {
|
||||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
funcVal, fileVal = f.CallerPrettyfier(caller)
|
||||||
|
} else {
|
||||||
|
if caller.Function != "" {
|
||||||
|
funcVal = caller.Function + "()"
|
||||||
|
}
|
||||||
|
fileVal = caller.File + ":" + strconv.FormatInt(int64(caller.Line), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileVal == "" {
|
if fileVal == "" {
|
||||||
caller = funcVal
|
callerText = funcVal
|
||||||
} else if funcVal == "" {
|
} else if funcVal == "" {
|
||||||
caller = fileVal
|
callerText = fileVal
|
||||||
} else {
|
} else {
|
||||||
caller = fileVal + " " + funcVal
|
callerText = fileVal + " " + funcVal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
levelText := levelPrefix(entry.Level, f.DisableLevelTruncation, f.PadLevelText)
|
||||||
switch {
|
switch {
|
||||||
case f.DisableTimestamp:
|
case f.DisableTimestamp:
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
|
_, _ = fmt.Fprintf(b, "%s%s %-44s ", levelText, callerText, entry.Message)
|
||||||
case !f.FullTimestamp:
|
case !f.FullTimestamp:
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
|
_, _ = fmt.Fprintf(b, "%s[%04d]%s %-44s ", levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), callerText, entry.Message)
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
|
timestampFormat := f.TimestampFormat
|
||||||
|
if timestampFormat == "" {
|
||||||
|
timestampFormat = defaultTimestampFormat
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(b, "%s[%s]%s %-44s ", levelText, entry.Time.Format(timestampFormat), callerText, entry.Message)
|
||||||
}
|
}
|
||||||
for _, k := range keys {
|
|
||||||
v := data[k]
|
|
||||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
|
|
||||||
f.appendValue(b, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TextFormatter) needsQuoting(text string) bool {
|
if !f.DisableSorting {
|
||||||
if f.ForceQuote {
|
if f.SortingFunc == nil {
|
||||||
return true
|
slices.Sort(keys)
|
||||||
}
|
} else {
|
||||||
if f.QuoteEmptyFields && len(text) == 0 {
|
f.SortingFunc(keys)
|
||||||
return true
|
|
||||||
}
|
|
||||||
if f.DisableQuote {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, ch := range text {
|
|
||||||
//nolint:staticcheck // QF1001: could apply De Morgan's law
|
|
||||||
if !((ch >= 'a' && ch <= 'z') ||
|
|
||||||
(ch >= 'A' && ch <= 'Z') ||
|
|
||||||
(ch >= '0' && ch <= '9') ||
|
|
||||||
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
// Keys use the same color as the level-prefix.
|
||||||
|
for _, k := range keys {
|
||||||
|
b.WriteByte(' ')
|
||||||
|
b.WriteString(colorize(entry.Level, k))
|
||||||
|
b.WriteByte('=')
|
||||||
|
f.appendValue(b, data[k])
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteByte('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
// appendKeyValue writes key=value. Keys are written verbatim (unquoted/unescaped);
|
||||||
|
// values are subject to quoting/escaping.
|
||||||
|
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value any) {
|
||||||
if b.Len() > 0 {
|
if b.Len() > 0 {
|
||||||
b.WriteByte(' ')
|
b.WriteByte(' ')
|
||||||
}
|
}
|
||||||
@@ -326,15 +316,168 @@ func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interf
|
|||||||
f.appendValue(b, value)
|
f.appendValue(b, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
func (f *TextFormatter) appendValue(b *bytes.Buffer, value any) {
|
||||||
stringVal, ok := value.(string)
|
// Fast paths.
|
||||||
if !ok {
|
switch v := value.(type) {
|
||||||
stringVal = fmt.Sprint(value)
|
case string:
|
||||||
|
f.appendString(b, v)
|
||||||
|
return
|
||||||
|
case []byte:
|
||||||
|
f.appendBytes(b, v)
|
||||||
|
return
|
||||||
|
case bool:
|
||||||
|
var raw [8]byte
|
||||||
|
f.appendBytes(b, strconv.AppendBool(raw[:0], v))
|
||||||
|
return
|
||||||
|
case error:
|
||||||
|
f.appendError(b, v)
|
||||||
|
return
|
||||||
|
case fmt.Stringer:
|
||||||
|
f.appendStringer(b, v)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !f.needsQuoting(stringVal) {
|
// Handle common primitives.
|
||||||
b.WriteString(stringVal)
|
var raw [64]byte
|
||||||
} else {
|
var num []byte
|
||||||
fmt.Fprintf(b, "%q", stringVal)
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case int:
|
||||||
|
num = strconv.AppendInt(raw[:0], int64(v), 10)
|
||||||
|
case int8:
|
||||||
|
num = strconv.AppendInt(raw[:0], int64(v), 10)
|
||||||
|
case int16:
|
||||||
|
num = strconv.AppendInt(raw[:0], int64(v), 10)
|
||||||
|
case int32:
|
||||||
|
num = strconv.AppendInt(raw[:0], int64(v), 10)
|
||||||
|
case int64:
|
||||||
|
num = strconv.AppendInt(raw[:0], v, 10)
|
||||||
|
|
||||||
|
case uint:
|
||||||
|
num = strconv.AppendUint(raw[:0], uint64(v), 10)
|
||||||
|
case uint8:
|
||||||
|
num = strconv.AppendUint(raw[:0], uint64(v), 10)
|
||||||
|
case uint16:
|
||||||
|
num = strconv.AppendUint(raw[:0], uint64(v), 10)
|
||||||
|
case uint32:
|
||||||
|
num = strconv.AppendUint(raw[:0], uint64(v), 10)
|
||||||
|
case uint64:
|
||||||
|
num = strconv.AppendUint(raw[:0], v, 10)
|
||||||
|
case uintptr:
|
||||||
|
num = strconv.AppendUint(raw[:0], uint64(v), 10)
|
||||||
|
|
||||||
|
case float32:
|
||||||
|
num = strconv.AppendFloat(raw[:0], float64(v), 'g', -1, 32)
|
||||||
|
case float64:
|
||||||
|
num = strconv.AppendFloat(raw[:0], v, 'g', -1, 64)
|
||||||
|
|
||||||
|
default:
|
||||||
|
f.appendString(b, fmt.Sprint(value))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.appendNumeric(b, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) appendString(b *bytes.Buffer, s string) {
|
||||||
|
quote := f.ForceQuote || (f.QuoteEmptyFields && len(s) == 0) || (!f.DisableQuote && needsQuoting(s))
|
||||||
|
if !quote {
|
||||||
|
b.WriteString(s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(s) == 0 {
|
||||||
|
b.WriteString(`""`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmp [128]byte
|
||||||
|
b.Write(strconv.AppendQuote(tmp[:0], s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) appendBytes(b *bytes.Buffer, bs []byte) {
|
||||||
|
quote := f.ForceQuote || (f.QuoteEmptyFields && len(bs) == 0) || (!f.DisableQuote && needsQuotingBytes(bs))
|
||||||
|
if !quote {
|
||||||
|
b.Write(bs)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(bs) == 0 {
|
||||||
|
b.WriteString(`""`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var tmp [128]byte
|
||||||
|
b.Write(strconv.AppendQuote(tmp[:0], string(bs)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) appendNumeric(b *bytes.Buffer, out []byte) {
|
||||||
|
if f.ForceQuote {
|
||||||
|
var tmp [128]byte
|
||||||
|
b.Write(strconv.AppendQuote(tmp[:0], string(out)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.Write(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) appendError(b *bytes.Buffer, v error) {
|
||||||
|
defer f.recoverValue(b, v, "Error")
|
||||||
|
|
||||||
|
f.appendString(b, v.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) appendStringer(b *bytes.Buffer, v fmt.Stringer) {
|
||||||
|
defer f.recoverValue(b, v, "String")
|
||||||
|
|
||||||
|
f.appendString(b, v.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) recoverValue(b *bytes.Buffer, v any, method string) {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if rv.Kind() == reflect.Pointer && rv.IsNil() {
|
||||||
|
f.appendString(b, "<nil>")
|
||||||
|
} else {
|
||||||
|
f.appendString(b, fmt.Sprintf("%%!v(PANIC=%s method: %v)", method, r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// needsQuoting returns true if the string contains any byte that
|
||||||
|
// requires quoting. It returns false when every byte is "safe" according
|
||||||
|
// to isSafeByte.
|
||||||
|
func needsQuoting(s string) bool {
|
||||||
|
// use an index loop (avoid rune decoding).
|
||||||
|
for i := range len(s) {
|
||||||
|
c := s[i]
|
||||||
|
if !isSafeByte(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// needsQuotingBytes returns true if the byte slice contains any byte that
|
||||||
|
// requires quoting. It returns false when every byte is "safe" according
|
||||||
|
// to isSafeByte.
|
||||||
|
func needsQuotingBytes(bs []byte) bool {
|
||||||
|
for _, c := range bs {
|
||||||
|
if !isSafeByte(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSafeByte returns true if the byte is allowed unquoted (ASCII and in the allowlist).
|
||||||
|
// It purposely uses byte arithmetic (no runes) for performance.
|
||||||
|
func isSafeByte(ch byte) bool {
|
||||||
|
ok := ch < 0x80 && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9'))
|
||||||
|
if ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch ch {
|
||||||
|
case '-', '.', '_', '/', '@', '^', '+':
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-4
@@ -30,7 +30,7 @@ func (entry *Entry) Writer() *io.PipeWriter {
|
|||||||
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
||||||
reader, writer := io.Pipe()
|
reader, writer := io.Pipe()
|
||||||
|
|
||||||
var printFunc func(args ...interface{})
|
printFunc := entry.Print
|
||||||
|
|
||||||
// Determine which log function to use based on the specified log level
|
// Determine which log function to use based on the specified log level
|
||||||
switch level {
|
switch level {
|
||||||
@@ -48,8 +48,6 @@ func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
|||||||
printFunc = entry.Fatal
|
printFunc = entry.Fatal
|
||||||
case PanicLevel:
|
case PanicLevel:
|
||||||
printFunc = entry.Panic
|
printFunc = entry.Panic
|
||||||
default:
|
|
||||||
printFunc = entry.Print
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start a new goroutine to scan the input and write it to the logger using the specified print function.
|
// Start a new goroutine to scan the input and write it to the logger using the specified print function.
|
||||||
@@ -63,7 +61,7 @@ func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// writerScanner scans the input from the reader and writes it to the logger
|
// writerScanner scans the input from the reader and writes it to the logger
|
||||||
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...any)) {
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
|
|
||||||
// Set the buffer size to the maximum token size to avoid buffer overflows
|
// Set the buffer size to the maximum token size to avoid buffer overflows
|
||||||
|
|||||||
Vendored
+4
-3
@@ -162,10 +162,11 @@ github.com/mattn/go-runewidth
|
|||||||
# github.com/moby/docker-image-spec v1.3.1
|
# github.com/moby/docker-image-spec v1.3.1
|
||||||
## explicit; go 1.18
|
## explicit; go 1.18
|
||||||
github.com/moby/docker-image-spec/specs-go/v1
|
github.com/moby/docker-image-spec/specs-go/v1
|
||||||
# github.com/moby/go-archive v0.3.2
|
# github.com/moby/go-archive v0.3.3
|
||||||
## explicit; go 1.25
|
## explicit; go 1.25
|
||||||
github.com/moby/go-archive
|
github.com/moby/go-archive
|
||||||
github.com/moby/go-archive/compression
|
github.com/moby/go-archive/compression
|
||||||
|
github.com/moby/go-archive/internal/archiveoptions
|
||||||
github.com/moby/go-archive/tarheader
|
github.com/moby/go-archive/tarheader
|
||||||
# github.com/moby/moby/api v1.55.0
|
# github.com/moby/moby/api v1.55.0
|
||||||
## explicit; go 1.24
|
## explicit; go 1.24
|
||||||
@@ -276,8 +277,8 @@ github.com/prometheus/procfs/internal/util
|
|||||||
# github.com/russross/blackfriday/v2 v2.1.0
|
# github.com/russross/blackfriday/v2 v2.1.0
|
||||||
## explicit
|
## explicit
|
||||||
github.com/russross/blackfriday/v2
|
github.com/russross/blackfriday/v2
|
||||||
# github.com/sirupsen/logrus v1.9.4
|
# github.com/sirupsen/logrus v1.10.1
|
||||||
## explicit; go 1.17
|
## explicit; go 1.23
|
||||||
github.com/sirupsen/logrus
|
github.com/sirupsen/logrus
|
||||||
# github.com/spf13/cobra v1.10.2
|
# github.com/spf13/cobra v1.10.2
|
||||||
## explicit; go 1.15
|
## explicit; go 1.15
|
||||||
|
|||||||
Reference in New Issue
Block a user