pax_global_header 0000666 0000000 0000000 00000000064 14663157754 0014534 g ustar 00root root 0000000 0000000 52 comment=7f417d6527408ca25f828dcd475e36a539bdd59b golang-github-charmbracelet-log-0.4.0/ 0000775 0000000 0000000 00000000000 14663157754 0017635 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/.github/ 0000775 0000000 0000000 00000000000 14663157754 0021175 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/.github/CODEOWNERS 0000664 0000000 0000000 00000000021 14663157754 0022561 0 ustar 00root root 0000000 0000000 * @aymanbagabas golang-github-charmbracelet-log-0.4.0/.github/dependabot.yml 0000664 0000000 0000000 00000001117 14663157754 0024025 0 ustar 00root root 0000000 0000000 version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" labels: - "dependencies" commit-message: prefix: "feat" include: "scope" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" labels: - "dependencies" commit-message: prefix: "chore" include: "scope" - package-ecosystem: "docker" directory: "/" schedule: interval: "daily" labels: - "dependencies" commit-message: prefix: "feat" include: "scope" golang-github-charmbracelet-log-0.4.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14663157754 0023232 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/.github/workflows/build.yml 0000664 0000000 0000000 00000000707 14663157754 0025060 0 ustar 00root root 0000000 0000000 name: build on: [push, pull_request] jobs: build: strategy: matrix: go_version: ["1.19", stable] uses: charmbracelet/meta/.github/workflows/build.yml@main with: go_version: ${{ matrix.go_version }} snapshot: uses: charmbracelet/meta/.github/workflows/snapshot.yml@main secrets: goreleaser_key: ${{ secrets.GORELEASER_KEY }} coverage: uses: charmbracelet/meta/.github/workflows/coverage.yml@main golang-github-charmbracelet-log-0.4.0/.github/workflows/goreleaser.yml 0000664 0000000 0000000 00000001221 14663157754 0026101 0 ustar 00root root 0000000 0000000 name: goreleaser on: push: tags: - v*.*.* concurrency: group: goreleaser cancel-in-progress: true jobs: goreleaser: uses: charmbracelet/meta/.github/workflows/goreleaser.yml@main secrets: docker_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_token: ${{ secrets.DOCKERHUB_TOKEN }} gh_pat: ${{ secrets.PERSONAL_ACCESS_TOKEN }} goreleaser_key: ${{ secrets.GORELEASER_KEY }} fury_token: ${{ secrets.FURY_TOKEN }} nfpm_gpg_key: ${{ secrets.NFPM_GPG_KEY }} nfpm_passphrase: ${{ secrets.NFPM_PASSPHRASE }} # yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json golang-github-charmbracelet-log-0.4.0/.github/workflows/lint.yml 0000664 0000000 0000000 00000000624 14663157754 0024725 0 ustar 00root root 0000000 0000000 name: lint on: push: pull_request: jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version: ^1 - name: golangci-lint uses: golangci/golangci-lint-action@v4 with: # Optional: golangci-lint command line arguments. args: --issues-exit-code=1 golang-github-charmbracelet-log-0.4.0/.github/workflows/nightly.yml 0000664 0000000 0000000 00000000465 14663157754 0025440 0 ustar 00root root 0000000 0000000 name: nightly on: push: branches: - main jobs: nightly: uses: charmbracelet/meta/.github/workflows/nightly.yml@main secrets: docker_username: ${{ secrets.DOCKERHUB_USERNAME }} docker_token: ${{ secrets.DOCKERHUB_TOKEN }} goreleaser_key: ${{ secrets.GORELEASER_KEY }} golang-github-charmbracelet-log-0.4.0/.gitignore 0000664 0000000 0000000 00000000431 14663157754 0021623 0 ustar 00root root 0000000 0000000 *.txt *.gif examples/batch2/batch2 examples/chocolate-chips/chocolate-chips examples/cookie/cookie examples/error/error examples/format/format examples/log/log examples/new/new examples/options/options examples/oven/oven examples/temperature/temperature .vscode .history go.work golang-github-charmbracelet-log-0.4.0/.golangci.yml 0000664 0000000 0000000 00000000715 14663157754 0022224 0 ustar 00root root 0000000 0000000 run: tests: false issues: include: - EXC0001 - EXC0005 - EXC0011 - EXC0012 - EXC0013 max-issues-per-linter: 0 max-same-issues: 0 linters: enable: - bodyclose - dupl - exportloopref - goconst - godot - godox - goimports - goprintffuncname - gosec - misspell - nolintlint - prealloc - revive - rowserrcheck - sqlclosecheck - unconvert - unparam - whitespace golang-github-charmbracelet-log-0.4.0/.goreleaser.yml 0000664 0000000 0000000 00000000117 14663157754 0022565 0 ustar 00root root 0000000 0000000 includes: - from_url: url: charmbracelet/meta/main/goreleaser-lib.yaml golang-github-charmbracelet-log-0.4.0/LICENSE 0000664 0000000 0000000 00000002070 14663157754 0020641 0 ustar 00root root 0000000 0000000 MIT License Copyright (c) 2022-2023 Charmbracelet, Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-charmbracelet-log-0.4.0/README.md 0000664 0000000 0000000 00000032054 14663157754 0021120 0 ustar 00root root 0000000 0000000 # Log
A minimal and colorful Go logging library. ๐ชต
Log integrates with [Gum][gum] to log messages to output. Use `gum log [flags]
[message]` to handle logging in your shell scripts. See
[charmbracelet/gum](https://github.com/charmbracelet/gum#log) for more
information.
[gum]: https://github.com/charmbracelet/gum
[lipgloss]: https://github.com/charmbracelet/lipgloss
[stdlog]: https://pkg.go.dev/log
## License
[MIT](https://github.com/charmbracelet/log/raw/master/LICENSE)
---
Part of [Charm](https://charm.sh).
Charm็ญ็ฑๅผๆบ โข Charm loves open source โข ูุญูู ูุญุจ ุงูู
ุตุงุฏุฑ ุงูู
ูุชูุญุฉ
golang-github-charmbracelet-log-0.4.0/context.go 0000664 0000000 0000000 00000001155 14663157754 0021652 0 ustar 00root root 0000000 0000000 package log
import "context"
// WithContext wraps the given logger in context.
func WithContext(ctx context.Context, logger *Logger) context.Context {
return context.WithValue(ctx, ContextKey, logger)
}
// FromContext returns the logger from the given context.
// This will return the default package logger if no logger
// found in context.
func FromContext(ctx context.Context) *Logger {
if logger, ok := ctx.Value(ContextKey).(*Logger); ok {
return logger
}
return Default()
}
type contextKey struct{ string }
// ContextKey is the key used to store the logger in context.
var ContextKey = contextKey{"log"}
golang-github-charmbracelet-log-0.4.0/context_test.go 0000664 0000000 0000000 00000001204 14663157754 0022704 0 ustar 00root root 0000000 0000000 package log
import (
"bytes"
"context"
"io"
"testing"
"github.com/stretchr/testify/require"
)
func TestLogContext_empty(t *testing.T) {
require.Equal(t, Default(), FromContext(context.TODO()))
}
func TestLogContext_simple(t *testing.T) {
l := New(io.Discard)
ctx := WithContext(context.Background(), l)
require.Equal(t, l, FromContext(ctx))
}
func TestLogContext_fields(t *testing.T) {
var buf bytes.Buffer
l := New(&buf)
l.SetLevel(DebugLevel)
ctx := WithContext(context.Background(), l.With("foo", "bar"))
l = FromContext(ctx)
require.NotNil(t, l)
l.Debug("test")
require.Equal(t, "DEBU test foo=bar\n", buf.String())
}
golang-github-charmbracelet-log-0.4.0/examples/ 0000775 0000000 0000000 00000000000 14663157754 0021453 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/README.md 0000664 0000000 0000000 00000000415 14663157754 0022732 0 ustar 00root root 0000000 0000000 # Examples
For those of you who are new to Go, check out
[pkg.go.dev](https://https://pkg.go.dev/github.com/charmbracelet/log) to
view our library's API. From there, you can search the repo for more detailed
usage examples for whichever functions match your use case.
golang-github-charmbracelet-log-0.4.0/examples/app/ 0000775 0000000 0000000 00000000000 14663157754 0022233 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/app/app.go 0000664 0000000 0000000 00000002236 14663157754 0023345 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"strings"
"time"
"github.com/charmbracelet/log"
)
type cup int
func (c cup) String() string {
s := fmt.Sprintf("%d cup", c)
if c > 1 {
s += "s"
}
return s
}
func startOven(degree int) {
log.Helper()
log.Debug("Starting oven", "temperature", degree)
}
func main() {
log.SetTimeFormat(time.Kitchen)
log.SetLevel(log.DebugLevel)
var (
butter = cup(1)
chocolate = cup(2)
flour = cup(3)
sugar = cup(5)
temp = 375
bakeTime = 10
)
startOven(temp)
time.Sleep(time.Second)
log.Debug("Mixing ingredients", "ingredients",
strings.Join([]string{
butter.String() + " of butter",
chocolate.String() + " of chocolate",
flour.String() + " of flour",
sugar.String() + " of sugar",
}, "\n"),
)
time.Sleep(time.Second)
if sugar > 2 {
log.Warn("That's a lot of sugar", "amount", sugar)
}
log.Info("Baking cookies", "time", fmt.Sprintf("%d minutes", bakeTime))
time.Sleep(2 * time.Second)
log.Info("Increasing temperature", "amount", 300)
temp += 300
time.Sleep(time.Second)
if temp > 500 {
log.Error("Oven is too hot", "temperature", temp)
log.Fatal("The kitchen is on fire ๐ฅ")
}
}
golang-github-charmbracelet-log-0.4.0/examples/app/app.tape 0000664 0000000 0000000 00000000135 14663157754 0023665 0 ustar 00root root 0000000 0000000 Output app.gif
Set FontSize 24
Set Width 1200
Set Height 600
Type "./app" Enter
Sleep 10s
golang-github-charmbracelet-log-0.4.0/examples/batch2/ 0000775 0000000 0000000 00000000000 14663157754 0022616 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/batch2/batch2.go 0000664 0000000 0000000 00000000536 14663157754 0024314 0 ustar 00root root 0000000 0000000 package main
import (
"github.com/charmbracelet/log"
)
func main() {
logger := log.Default().With("batch", 2, "chocolateChips", true)
logger.SetPrefix("baking ๐ช ")
logger.SetReportTimestamp(false)
logger.SetReportCaller(false)
logger.SetLevel(log.DebugLevel)
logger.Debug("Preparing batch 2...")
logger.Debug("Adding chocolate chips")
}
golang-github-charmbracelet-log-0.4.0/examples/batch2/batch2.tape 0000664 0000000 0000000 00000000136 14663157754 0024634 0 ustar 00root root 0000000 0000000 Output batch2.gif
Set Height 300
Set Width 1100
Type "./batch2" Sleep 500ms Enter
Sleep 4s
golang-github-charmbracelet-log-0.4.0/examples/chocolate-chips/ 0000775 0000000 0000000 00000000000 14663157754 0024520 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/chocolate-chips/chocolate-chips.go 0000664 0000000 0000000 00000000645 14663157754 0030121 0 ustar 00root root 0000000 0000000 package main
import (
"github.com/charmbracelet/log"
)
func main() {
logger := log.Default().With()
logger.SetPrefix("Baking ๐ช ")
logger.SetReportTimestamp(false)
logger.SetReportCaller(false)
logger.SetLevel(log.DebugLevel)
logger.Debug("Preparing batch 2...") // DEBUG baking ๐ช: Preparing batch 2...}
batch2 := logger.With("batch", 2, "chocolateChips", true)
batch2.Debug("Adding chocolate chips")
}
golang-github-charmbracelet-log-0.4.0/examples/cookie/ 0000775 0000000 0000000 00000000000 14663157754 0022724 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/cookie/cookie.go 0000664 0000000 0000000 00000000171 14663157754 0024523 0 ustar 00root root 0000000 0000000 package main
import "github.com/charmbracelet/log"
func main() {
log.Debug("Cookie ๐ช")
log.Info("Hello World!")
}
golang-github-charmbracelet-log-0.4.0/examples/cookie/cookie.tape 0000664 0000000 0000000 00000000135 14663157754 0025047 0 ustar 00root root 0000000 0000000 Output cookie.gif
Set Height 250
Set Width 700
Type "./cookie" Sleep 500ms Enter
Sleep 3s
golang-github-charmbracelet-log-0.4.0/examples/error/ 0000775 0000000 0000000 00000000000 14663157754 0022604 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/error/error.go 0000664 0000000 0000000 00000000250 14663157754 0024261 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"github.com/charmbracelet/log"
)
func main() {
err := fmt.Errorf("too much sugar")
log.Error("failed to bake cookies", "err", err)
}
golang-github-charmbracelet-log-0.4.0/examples/error/error.tape 0000664 0000000 0000000 00000000134 14663157754 0024606 0 ustar 00root root 0000000 0000000 Output error.gif
Set Height 250
Set Width 1100
Type "./error" Sleep 500ms Enter
Sleep 3s
golang-github-charmbracelet-log-0.4.0/examples/format/ 0000775 0000000 0000000 00000000000 14663157754 0022743 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/format/format.go 0000664 0000000 0000000 00000000335 14663157754 0024563 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"time"
"github.com/charmbracelet/log"
)
func main() {
for item := 1; item <= 100; item++ {
log.Info(fmt.Sprintf("Baking %d / 100 ...", item))
time.Sleep(100 * time.Millisecond)
}
}
golang-github-charmbracelet-log-0.4.0/examples/format/format.tape 0000664 0000000 0000000 00000000135 14663157754 0025105 0 ustar 00root root 0000000 0000000 Output format.gif
Set Height 400
Set Width 800
Type "./format" Sleep 500ms Enter
Sleep 6s
golang-github-charmbracelet-log-0.4.0/examples/go.mod 0000664 0000000 0000000 00000001274 14663157754 0022565 0 ustar 00root root 0000000 0000000 module examples
go 1.17
replace github.com/charmbracelet/log => ../
require (
github.com/charmbracelet/lipgloss v0.10.0
github.com/charmbracelet/log v0.0.0-00010101000000-000000000000
)
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/sys v0.13.0 // indirect
)
golang-github-charmbracelet-log-0.4.0/examples/go.sum 0000664 0000000 0000000 00000020672 14663157754 0022615 0 ustar 00root root 0000000 0000000 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
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/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
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/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
golang-github-charmbracelet-log-0.4.0/examples/log/ 0000775 0000000 0000000 00000000000 14663157754 0022234 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/log/log.go 0000664 0000000 0000000 00000000136 14663157754 0023344 0 ustar 00root root 0000000 0000000 package main
import "github.com/charmbracelet/log"
func main() {
log.Print("Baking 101")
}
golang-github-charmbracelet-log-0.4.0/examples/log/log.tape 0000664 0000000 0000000 00000000127 14663157754 0023670 0 ustar 00root root 0000000 0000000 Output log.gif
Set Height 250
Set Width 800
Type "./log" Sleep 500ms Enter
Sleep 3s
golang-github-charmbracelet-log-0.4.0/examples/new/ 0000775 0000000 0000000 00000000000 14663157754 0022244 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/new/new.go 0000664 0000000 0000000 00000000226 14663157754 0023364 0 ustar 00root root 0000000 0000000 package main
import (
"os"
"github.com/charmbracelet/log"
)
func main() {
logger := log.New(os.Stderr)
logger.Warn("chewy!", "butter", true)
}
golang-github-charmbracelet-log-0.4.0/examples/new/new.tape 0000664 0000000 0000000 00000000127 14663157754 0023710 0 ustar 00root root 0000000 0000000 Output new.gif
Set Height 250
Set Width 500
Type "./new" Sleep 500ms Enter
Sleep 3s
golang-github-charmbracelet-log-0.4.0/examples/options/ 0000775 0000000 0000000 00000000000 14663157754 0023146 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/options/options.go 0000664 0000000 0000000 00000000547 14663157754 0025176 0 ustar 00root root 0000000 0000000 package main
import (
"os"
"time"
"github.com/charmbracelet/log"
)
func main() {
logger := log.New(os.Stderr)
logger.SetPrefix("Baking ๐ช ")
logger.SetTimeFormat(time.Kitchen)
logger.SetReportTimestamp(true)
logger.SetReportCaller(true)
logger.Info("Starting oven!", "degree", 375)
time.Sleep(3 * time.Second)
logger.Info("Finished baking")
}
golang-github-charmbracelet-log-0.4.0/examples/options/options.tape 0000664 0000000 0000000 00000000140 14663157754 0025507 0 ustar 00root root 0000000 0000000 Output options.gif
Set Height 300
Set Width 1200
Type "./options" Sleep 500ms Enter
Sleep 6s
golang-github-charmbracelet-log-0.4.0/examples/oven/ 0000775 0000000 0000000 00000000000 14663157754 0022422 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/oven/oven.go 0000664 0000000 0000000 00000000313 14663157754 0023715 0 ustar 00root root 0000000 0000000 package main
import "github.com/charmbracelet/log"
func startOven(degree int) {
log.Helper()
log.Info("Starting oven", "degree", degree)
}
func main() {
log.SetReportCaller(true)
startOven(400)
}
golang-github-charmbracelet-log-0.4.0/examples/oven/oven.tape 0000664 0000000 0000000 00000000132 14663157754 0024240 0 ustar 00root root 0000000 0000000 Output oven.gif
Set Height 250
Set Width 1200
Type "./oven" Sleep 500ms Enter
Sleep 3s
golang-github-charmbracelet-log-0.4.0/examples/slog/ 0000775 0000000 0000000 00000000000 14663157754 0022417 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/slog/main.go 0000664 0000000 0000000 00000000713 14663157754 0023673 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"log/slog"
"os"
"time"
"github.com/charmbracelet/log"
)
func main() {
// baseline
fmt.Println(time.Now().UTC().Format(time.RFC3339), "foo")
fmt.Println(time.Now().Format(time.RFC3339), "bar")
handler := log.NewWithOptions(os.Stdout, log.Options{
ReportTimestamp: true,
TimeFunction: log.NowUTC,
TimeFormat: time.RFC3339,
})
handler.Info("foobar")
logger := slog.New(handler)
logger.Info("foobar")
}
golang-github-charmbracelet-log-0.4.0/examples/styles/ 0000775 0000000 0000000 00000000000 14663157754 0022776 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/styles/styles.go 0000664 0000000 0000000 00000001161 14663157754 0024647 0 ustar 00root root 0000000 0000000 package main
import (
"os"
"time"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/log"
)
func main() {
// Set up our custom styles
styles := log.DefaultStyles()
styles.Levels[log.ErrorLevel] = lipgloss.NewStyle().
SetString("ERROR!!").
Padding(0, 1, 0, 1).
Background(lipgloss.Color("204")).
Foreground(lipgloss.Color("0"))
styles.Keys["err"] = lipgloss.NewStyle().Foreground(lipgloss.Color("204"))
styles.Values["err"] = lipgloss.NewStyle().Bold(true)
logger := log.New(os.Stderr)
logger.SetStyles(styles)
logger.Error("Whoops!", "err", "kitchen on fire")
time.Sleep(3 * time.Second)
}
golang-github-charmbracelet-log-0.4.0/examples/styles/styles.tape 0000664 0000000 0000000 00000000135 14663157754 0025173 0 ustar 00root root 0000000 0000000 Output styles.gif
Set Height 250
Set Width 700
Type "./styles" Sleep 500ms Enter
Sleep 3s
golang-github-charmbracelet-log-0.4.0/examples/temperature/ 0000775 0000000 0000000 00000000000 14663157754 0024010 5 ustar 00root root 0000000 0000000 golang-github-charmbracelet-log-0.4.0/examples/temperature/temperature.go 0000664 0000000 0000000 00000000365 14663157754 0026700 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"time"
"github.com/charmbracelet/log"
)
func main() {
for temp := 375; temp <= 400; temp++ {
log.Info("Increasing temperature", "degree", fmt.Sprintf("%dยฐF", temp))
time.Sleep(100 * time.Millisecond)
}
}
golang-github-charmbracelet-log-0.4.0/examples/temperature/temperature.tape 0000664 0000000 0000000 00000000150 14663157754 0027214 0 ustar 00root root 0000000 0000000 Output temperature.gif
Set Height 400
Set Width 1100
Type "./temperature" Sleep 500ms Enter
Sleep 6s
golang-github-charmbracelet-log-0.4.0/formatter.go 0000664 0000000 0000000 00000001341 14663157754 0022166 0 ustar 00root root 0000000 0000000 package log
// Formatter is a formatter for log messages.
type Formatter uint8
const (
// TextFormatter is a formatter that formats log messages as text. Suitable for
// console output and log files.
TextFormatter Formatter = iota
// JSONFormatter is a formatter that formats log messages as JSON.
JSONFormatter
// LogfmtFormatter is a formatter that formats log messages as logfmt.
LogfmtFormatter
)
var (
// TimestampKey is the key for the timestamp.
TimestampKey = "time"
// MessageKey is the key for the message.
MessageKey = "msg"
// LevelKey is the key for the level.
LevelKey = "level"
// CallerKey is the key for the caller.
CallerKey = "caller"
// PrefixKey is the key for the prefix.
PrefixKey = "prefix"
)
golang-github-charmbracelet-log-0.4.0/go.mod 0000664 0000000 0000000 00000001350 14663157754 0020742 0 ustar 00root root 0000000 0000000 module github.com/charmbracelet/log
go 1.19
require (
github.com/charmbracelet/lipgloss v0.10.0
github.com/go-logfmt/logfmt v0.6.0
github.com/muesli/termenv v0.15.2
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
)
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sys v0.13.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
golang-github-charmbracelet-log-0.4.0/go.sum 0000664 0000000 0000000 00000006144 14663157754 0020775 0 ustar 00root root 0000000 0000000 github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
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/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
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/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
golang-github-charmbracelet-log-0.4.0/json.go 0000664 0000000 0000000 00000002250 14663157754 0021134 0 ustar 00root root 0000000 0000000 package log
import (
"encoding/json"
"fmt"
"time"
)
func (l *Logger) jsonFormatter(keyvals ...interface{}) {
m := make(map[string]interface{}, len(keyvals)/2)
for i := 0; i < len(keyvals); i += 2 {
switch keyvals[i] {
case TimestampKey:
if t, ok := keyvals[i+1].(time.Time); ok {
m[TimestampKey] = t.Format(l.timeFormat)
}
case LevelKey:
if level, ok := keyvals[i+1].(Level); ok {
m[LevelKey] = level.String()
}
case CallerKey:
if caller, ok := keyvals[i+1].(string); ok {
m[CallerKey] = caller
}
case PrefixKey:
if prefix, ok := keyvals[i+1].(string); ok {
m[PrefixKey] = prefix
}
case MessageKey:
if msg := keyvals[i+1]; msg != nil {
m[MessageKey] = fmt.Sprint(msg)
}
default:
var (
key string
val interface{}
)
switch k := keyvals[i].(type) {
case fmt.Stringer:
key = k.String()
case error:
key = k.Error()
default:
key = fmt.Sprint(k)
}
switch v := keyvals[i+1].(type) {
case error:
val = v.Error()
case fmt.Stringer:
val = v.String()
default:
val = v
}
m[key] = val
}
}
e := json.NewEncoder(&l.b)
e.SetEscapeHTML(false)
_ = e.Encode(m)
}
golang-github-charmbracelet-log-0.4.0/json_test.go 0000664 0000000 0000000 00000011235 14663157754 0022176 0 ustar 00root root 0000000 0000000 package log
import (
"bytes"
"errors"
"fmt"
"path/filepath"
"runtime"
"testing"
"github.com/stretchr/testify/require"
)
func TestJson(t *testing.T) {
var buf bytes.Buffer
l := New(&buf)
l.SetFormatter(JSONFormatter)
cases := []struct {
name string
expected string
msg string
kvs []interface{}
f func(msg interface{}, kvs ...interface{})
}{
{
name: "default logger info with timestamp",
expected: "{\"level\":\"info\",\"msg\":\"info\"}\n",
msg: "info",
kvs: nil,
f: l.Info,
},
{
name: "default logger debug with timestamp",
expected: "",
msg: "info",
kvs: nil,
f: l.Debug,
},
{
name: "default logger error with timestamp",
expected: "{\"level\":\"error\",\"msg\":\"info\"}\n",
msg: "info",
kvs: nil,
f: l.Error,
},
{
name: "multiline message",
expected: "{\"level\":\"error\",\"msg\":\"info\\ninfo\"}\n",
msg: "info\ninfo",
kvs: nil,
f: l.Error,
},
{
name: "multiline kvs",
expected: "{\"level\":\"error\",\"msg\":\"info\",\"multiline\":\"info\\ninfo\"}\n",
msg: "info",
kvs: []interface{}{"multiline", "info\ninfo"},
f: l.Error,
},
{
name: "odd number of kvs",
expected: "{\"baz\":\"missing value\",\"foo\":\"bar\",\"level\":\"error\",\"msg\":\"info\"}\n",
msg: "info",
kvs: []interface{}{"foo", "bar", "baz"},
f: l.Error,
},
{
name: "error field",
expected: "{\"error\":\"error message\",\"level\":\"error\",\"msg\":\"info\"}\n",
msg: "info",
kvs: []interface{}{"error", errors.New("error message")},
f: l.Error,
},
{
name: "struct field",
expected: "{\"level\":\"info\",\"msg\":\"info\",\"struct\":{}}\n",
msg: "info",
kvs: []interface{}{"struct", struct{ foo string }{foo: "bar"}},
f: l.Info,
},
{
name: "slice field",
expected: "{\"level\":\"info\",\"msg\":\"info\",\"slice\":[1,2,3]}\n",
msg: "info",
kvs: []interface{}{"slice", []int{1, 2, 3}},
f: l.Info,
},
{
name: "slice of structs",
expected: "{\"level\":\"info\",\"msg\":\"info\",\"slice\":[{},{}]}\n",
msg: "info",
kvs: []interface{}{"slice", []struct{ foo string }{{foo: "bar"}, {foo: "baz"}}},
f: l.Info,
},
{
name: "slice of strings",
expected: "{\"level\":\"info\",\"msg\":\"info\",\"slice\":[\"foo\",\"bar\"]}\n",
msg: "info",
kvs: []interface{}{"slice", []string{"foo", "bar"}},
f: l.Info,
},
{
name: "slice of errors",
expected: "{\"level\":\"info\",\"msg\":\"info\",\"slice\":[{},{}]}\n",
msg: "info",
kvs: []interface{}{"slice", []error{errors.New("error message1"), errors.New("error message2")}},
f: l.Info,
},
{
name: "map of strings",
expected: "{\"level\":\"info\",\"map\":{\"a\":\"b\",\"foo\":\"bar\"},\"msg\":\"info\"}\n",
msg: "info",
kvs: []interface{}{"map", map[string]string{"a": "b", "foo": "bar"}},
f: l.Info,
},
}
for _, c := range cases {
buf.Reset()
t.Run(c.name, func(t *testing.T) {
c.f(c.msg, c.kvs...)
require.Equal(t, c.expected, buf.String())
})
}
}
func TestJsonCaller(t *testing.T) {
var buf bytes.Buffer
l := New(&buf)
l.SetFormatter(JSONFormatter)
l.SetReportCaller(true)
l.SetLevel(DebugLevel)
_, file, line, _ := runtime.Caller(0)
cases := []struct {
name string
expected string
msg string
kvs []interface{}
f func(msg interface{}, kvs ...interface{})
}{
{
name: "simple caller",
expected: fmt.Sprintf("{\"caller\":\"log/%s:%d\",\"level\":\"info\",\"msg\":\"info\"}\n", filepath.Base(file), line+30),
msg: "info",
kvs: nil,
f: l.Info,
},
{
name: "nested caller",
expected: fmt.Sprintf("{\"caller\":\"log/%s:%d\",\"level\":\"info\",\"msg\":\"info\"}\n", filepath.Base(file), line+30),
msg: "info",
kvs: nil,
f: func(msg interface{}, kvs ...interface{}) {
l.Helper()
l.Info(msg, kvs...)
},
},
}
for _, c := range cases {
buf.Reset()
t.Run(c.name, func(t *testing.T) {
c.f(c.msg, c.kvs...)
require.Equal(t, c.expected, buf.String())
})
}
}
func TestJsonCustomKey(t *testing.T) {
var buf bytes.Buffer
oldTsKey := TimestampKey
defer func() {
TimestampKey = oldTsKey
}()
TimestampKey = "time"
logger := New(&buf)
logger.SetTimeFunction(_zeroTime)
logger.SetFormatter(JSONFormatter)
logger.SetReportTimestamp(true)
logger.Info("info")
require.Equal(t, "{\"level\":\"info\",\"msg\":\"info\",\"time\":\"0002/01/01 00:00:00\"}\n", buf.String())
}
golang-github-charmbracelet-log-0.4.0/level.go 0000664 0000000 0000000 00000002554 14663157754 0021301 0 ustar 00root root 0000000 0000000 package log
import (
"errors"
"fmt"
"math"
"strings"
)
// Level is a logging level.
type Level int32
const (
// DebugLevel is the debug level.
DebugLevel Level = -4
// InfoLevel is the info level.
InfoLevel Level = 0
// WarnLevel is the warn level.
WarnLevel Level = 4
// ErrorLevel is the error level.
ErrorLevel Level = 8
// FatalLevel is the fatal level.
FatalLevel Level = 12
// noLevel is used with log.Print.
noLevel Level = math.MaxInt32
)
// String returns the string representation of the level.
func (l Level) String() string {
switch l {
case DebugLevel:
return "debug"
case InfoLevel:
return "info"
case WarnLevel:
return "warn"
case ErrorLevel:
return "error"
case FatalLevel:
return "fatal"
default:
return ""
}
}
// ErrInvalidLevel is an error returned when parsing an invalid level string.
var ErrInvalidLevel = errors.New("invalid level")
// ParseLevel converts level in string to Level type. Default level is InfoLevel.
func ParseLevel(level string) (Level, error) {
switch strings.ToLower(level) {
case DebugLevel.String():
return DebugLevel, nil
case InfoLevel.String():
return InfoLevel, nil
case WarnLevel.String():
return WarnLevel, nil
case ErrorLevel.String():
return ErrorLevel, nil
case FatalLevel.String():
return FatalLevel, nil
default:
return 0, fmt.Errorf("%w: %q", ErrInvalidLevel, level)
}
}
golang-github-charmbracelet-log-0.4.0/level_121.go 0000664 0000000 0000000 00000000467 14663157754 0021665 0 ustar 00root root 0000000 0000000 //go:build go1.21
// +build go1.21
package log
import "log/slog"
// fromSlogLevel converts slog.Level to log.Level.
var fromSlogLevel = map[slog.Level]Level{
slog.LevelDebug: DebugLevel,
slog.LevelInfo: InfoLevel,
slog.LevelWarn: WarnLevel,
slog.LevelError: ErrorLevel,
slog.Level(12): FatalLevel,
}
golang-github-charmbracelet-log-0.4.0/level_no121.go 0000664 0000000 0000000 00000000506 14663157754 0022214 0 ustar 00root root 0000000 0000000 //go:build !go1.21
// +build !go1.21
package log
import "golang.org/x/exp/slog"
// fromSlogLevel converts slog.Level to log.Level.
var fromSlogLevel = map[slog.Level]Level{
slog.LevelDebug: DebugLevel,
slog.LevelInfo: InfoLevel,
slog.LevelWarn: WarnLevel,
slog.LevelError: ErrorLevel,
slog.Level(12): FatalLevel,
}
golang-github-charmbracelet-log-0.4.0/level_test.go 0000664 0000000 0000000 00000002425 14663157754 0022335 0 ustar 00root root 0000000 0000000 package log
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDefaultLevel(t *testing.T) {
var level Level
assert.Equal(t, InfoLevel, level)
}
func TestParseLevel(t *testing.T) {
testCases := []struct {
name string
input string
expected Level
error error
}{
{
name: "Parse debug",
input: "debug",
expected: DebugLevel,
error: nil,
},
{
name: "Parse info",
input: "Info",
expected: InfoLevel,
error: nil,
},
{
name: "Parse warn",
input: "WARN",
expected: WarnLevel,
error: nil,
},
{
name: "Parse error",
input: "error",
expected: ErrorLevel,
error: nil,
},
{
name: "Parse fatal",
input: "FATAL",
expected: FatalLevel,
error: nil,
},
{
name: "Default",
input: "",
expected: InfoLevel,
error: fmt.Errorf("%w: %q", ErrInvalidLevel, ""),
},
{
name: "Wrong level, set INFO",
input: "WRONG_LEVEL",
expected: InfoLevel,
error: fmt.Errorf("%w: %q", ErrInvalidLevel, "WRONG_LEVEL"),
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
lvl, err := ParseLevel(tc.input)
assert.Equal(t, tc.expected, lvl)
assert.Equal(t, tc.error, err)
})
}
}
golang-github-charmbracelet-log-0.4.0/logfmt.go 0000664 0000000 0000000 00000001335 14663157754 0021456 0 ustar 00root root 0000000 0000000 package log
import (
"errors"
"fmt"
"time"
"github.com/go-logfmt/logfmt"
)
func (l *Logger) logfmtFormatter(keyvals ...interface{}) {
e := logfmt.NewEncoder(&l.b)
for i := 0; i < len(keyvals); i += 2 {
switch keyvals[i] {
case TimestampKey:
if t, ok := keyvals[i+1].(time.Time); ok {
keyvals[i+1] = t.Format(l.timeFormat)
}
default:
if key := fmt.Sprint(keyvals[i]); key != "" {
keyvals[i] = key
}
}
err := e.EncodeKeyval(keyvals[i], keyvals[i+1])
if err != nil && errors.Is(err, logfmt.ErrUnsupportedValueType) {
// If the value is not supported by logfmt, we try to convert it to a string.
_ = e.EncodeKeyval(keyvals[i], fmt.Sprintf("%+v", keyvals[i+1]))
}
}
_ = e.EndRecord()
}
golang-github-charmbracelet-log-0.4.0/logfmt_test.go 0000664 0000000 0000000 00000006260 14663157754 0022517 0 ustar 00root root 0000000 0000000 package log
import (
"bytes"
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLogfmt(t *testing.T) {
var buf bytes.Buffer
l := New(&buf)
l.SetFormatter(LogfmtFormatter)
cases := []struct {
name string
expected string
msg string
kvs []interface{}
f func(msg interface{}, kvs ...interface{})
}{
{
name: "simple",
expected: "level=info msg=info\n",
msg: "info",
kvs: nil,
f: l.Info,
},
{
name: "ignored message",
expected: "",
msg: "info",
kvs: nil,
f: l.Debug,
},
{
name: "message with keyvals",
expected: "level=info msg=info foo=bar\n",
msg: "info",
kvs: []interface{}{"foo", "bar"},
f: l.Info,
},
{
name: "message with multiline keyvals",
expected: "level=info msg=info foo=\"bar\\nbaz\"\n",
msg: "info",
kvs: []interface{}{"foo", "bar\nbaz"},
f: l.Info,
},
{
name: "multiline message",
expected: "level=info msg=\"info\\nfoo\"\n",
msg: "info\nfoo",
kvs: nil,
f: l.Info,
},
{
name: "message with error",
expected: "level=info msg=info err=\"foo: bar\"\n",
msg: "info",
kvs: []interface{}{"err", errors.New("foo: bar")},
f: l.Info,
},
{
name: "odd number of keyvals",
expected: "level=info msg=info foo=bar baz=\"missing value\"\n",
msg: "info",
kvs: []interface{}{"foo", "bar", "baz"},
f: l.Info,
},
{
name: "struct field",
expected: "level=info msg=info foo=\"{bar:foo bar}\"\n",
msg: "info",
kvs: []interface{}{"foo", struct{ bar string }{"foo bar"}},
f: l.Info,
},
{
name: "multiple struct fields",
expected: "level=info msg=info foo={bar:baz} foo2={bar:baz}\n",
msg: "info",
kvs: []interface{}{"foo", struct{ bar string }{"baz"}, "foo2", struct{ bar string }{"baz"}},
f: l.Info,
},
{
name: "slice of structs",
expected: "level=info msg=info foo=\"[{bar:baz} {bar:baz}]\"\n",
msg: "info",
kvs: []interface{}{"foo", []struct{ bar string }{{"baz"}, {"baz"}}},
f: l.Info,
},
{
name: "slice of strings",
expected: "level=info msg=info foo=\"[bar baz]\"\n",
msg: "info",
kvs: []interface{}{"foo", []string{"bar", "baz"}},
f: l.Info,
},
{
name: "slice of errors",
expected: "level=info msg=info foo=\"[error1 error2]\"\n",
msg: "info",
kvs: []interface{}{"foo", []error{errors.New("error1"), errors.New("error2")}},
f: l.Info,
},
{
name: "map of strings",
expected: "level=info msg=info foo=map[bar:baz]\n",
msg: "info",
kvs: []interface{}{"foo", map[string]string{"bar": "baz"}},
f: l.Info,
},
{
name: "map with interface",
expected: "level=info msg=info foo=map[bar:baz]\n",
msg: "info",
kvs: []interface{}{"foo", map[string]interface{}{"bar": "baz"}},
f: l.Info,
},
}
for _, c := range cases {
buf.Reset()
t.Run(c.name, func(t *testing.T) {
c.f(c.msg, c.kvs...)
assert.Equal(t, c.expected, buf.String())
})
}
}
golang-github-charmbracelet-log-0.4.0/logger.go 0000664 0000000 0000000 00000022754 14663157754 0021455 0 ustar 00root root 0000000 0000000 package log
import (
"bytes"
"fmt"
"io"
"os"
"runtime"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/termenv"
)
// ErrMissingValue is returned when a key is missing a value.
var ErrMissingValue = fmt.Errorf("missing value")
// LoggerOption is an option for a logger.
type LoggerOption = func(*Logger)
// Logger is a Logger that implements Logger.
type Logger struct {
w io.Writer
b bytes.Buffer
mu *sync.RWMutex
re *lipgloss.Renderer
isDiscard uint32
level int32
prefix string
timeFunc TimeFunction
timeFormat string
callerOffset int
callerFormatter CallerFormatter
formatter Formatter
reportCaller bool
reportTimestamp bool
fields []interface{}
helpers *sync.Map
styles *Styles
}
// Logf logs a message with formatting.
func (l *Logger) Logf(level Level, format string, args ...interface{}) {
l.Log(level, fmt.Sprintf(format, args...))
}
// Log logs the given message with the given keyvals for the given level.
func (l *Logger) Log(level Level, msg interface{}, keyvals ...interface{}) {
if atomic.LoadUint32(&l.isDiscard) != 0 {
return
}
// check if the level is allowed
if atomic.LoadInt32(&l.level) > int32(level) {
return
}
var frame runtime.Frame
if l.reportCaller {
// Skip log.log, the caller, and any offset added.
frames := l.frames(l.callerOffset + 2)
for {
f, more := frames.Next()
_, helper := l.helpers.Load(f.Function)
if !helper || !more {
// Found a frame that wasn't a helper function.
// Or we ran out of frames to check.
frame = f
break
}
}
}
l.handle(level, l.timeFunc(time.Now()), []runtime.Frame{frame}, msg, keyvals...)
}
func (l *Logger) handle(level Level, ts time.Time, frames []runtime.Frame, msg interface{}, keyvals ...interface{}) {
var kvs []interface{}
if l.reportTimestamp && !ts.IsZero() {
kvs = append(kvs, TimestampKey, ts)
}
_, ok := l.styles.Levels[level]
if ok {
kvs = append(kvs, LevelKey, level)
}
if l.reportCaller && len(frames) > 0 && frames[0].PC != 0 {
file, line, fn := l.location(frames)
if file != "" {
caller := l.callerFormatter(file, line, fn)
kvs = append(kvs, CallerKey, caller)
}
}
if l.prefix != "" {
kvs = append(kvs, PrefixKey, l.prefix)
}
if msg != nil {
if m := fmt.Sprint(msg); m != "" {
kvs = append(kvs, MessageKey, m)
}
}
// append logger fields
kvs = append(kvs, l.fields...)
if len(l.fields)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
// append the rest
kvs = append(kvs, keyvals...)
if len(keyvals)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
l.mu.Lock()
defer l.mu.Unlock()
switch l.formatter {
case LogfmtFormatter:
l.logfmtFormatter(kvs...)
case JSONFormatter:
l.jsonFormatter(kvs...)
default:
l.textFormatter(kvs...)
}
// WriteTo will reset the buffer
l.b.WriteTo(l.w) //nolint: errcheck
}
// Helper marks the calling function as a helper
// and skips it for source location information.
// It's the equivalent of testing.TB.Helper().
func (l *Logger) Helper() {
l.helper(1)
}
func (l *Logger) helper(skip int) {
var pcs [1]uintptr
// Skip runtime.Callers, and l.helper
n := runtime.Callers(skip+2, pcs[:])
frames := runtime.CallersFrames(pcs[:n])
frame, _ := frames.Next()
l.helpers.LoadOrStore(frame.Function, struct{}{})
}
// frames returns the runtime.Frames for the caller.
func (l *Logger) frames(skip int) *runtime.Frames {
// Copied from testing.T
const maxStackLen = 50
var pc [maxStackLen]uintptr
// Skip runtime.Callers, and l.frame
n := runtime.Callers(skip+2, pc[:])
frames := runtime.CallersFrames(pc[:n])
return frames
}
func (l *Logger) location(frames []runtime.Frame) (file string, line int, fn string) {
if len(frames) == 0 {
return "", 0, ""
}
f := frames[0]
return f.File, f.Line, f.Function
}
// Cleanup a path by returning the last n segments of the path only.
func trimCallerPath(path string, n int) string {
// lovely borrowed from zap
// nb. To make sure we trim the path correctly on Windows too, we
// counter-intuitively need to use '/' and *not* os.PathSeparator here,
// because the path given originates from Go stdlib, specifically
// runtime.Caller() which (as of Mar/17) returns forward slashes even on
// Windows.
//
// See https://github.com/golang/go/issues/3335
// and https://github.com/golang/go/issues/18151
//
// for discussion on the issue on Go side.
// Return the full path if n is 0.
if n <= 0 {
return path
}
// Find the last separator.
idx := strings.LastIndexByte(path, '/')
if idx == -1 {
return path
}
for i := 0; i < n-1; i++ {
// Find the penultimate separator.
idx = strings.LastIndexByte(path[:idx], '/')
if idx == -1 {
return path
}
}
return path[idx+1:]
}
// SetReportTimestamp sets whether the timestamp should be reported.
func (l *Logger) SetReportTimestamp(report bool) {
l.mu.Lock()
defer l.mu.Unlock()
l.reportTimestamp = report
}
// SetReportCaller sets whether the caller location should be reported.
func (l *Logger) SetReportCaller(report bool) {
l.mu.Lock()
defer l.mu.Unlock()
l.reportCaller = report
}
// GetLevel returns the current level.
func (l *Logger) GetLevel() Level {
l.mu.RLock()
defer l.mu.RUnlock()
return Level(l.level)
}
// SetLevel sets the current level.
func (l *Logger) SetLevel(level Level) {
l.mu.Lock()
defer l.mu.Unlock()
atomic.StoreInt32(&l.level, int32(level))
}
// GetPrefix returns the current prefix.
func (l *Logger) GetPrefix() string {
l.mu.RLock()
defer l.mu.RUnlock()
return l.prefix
}
// SetPrefix sets the current prefix.
func (l *Logger) SetPrefix(prefix string) {
l.mu.Lock()
defer l.mu.Unlock()
l.prefix = prefix
}
// SetTimeFormat sets the time format.
func (l *Logger) SetTimeFormat(format string) {
l.mu.Lock()
defer l.mu.Unlock()
l.timeFormat = format
}
// SetTimeFunction sets the time function.
func (l *Logger) SetTimeFunction(f TimeFunction) {
l.mu.Lock()
defer l.mu.Unlock()
l.timeFunc = f
}
// SetOutput sets the output destination.
func (l *Logger) SetOutput(w io.Writer) {
l.mu.Lock()
defer l.mu.Unlock()
if w == nil {
w = os.Stderr
}
l.w = w
var isDiscard uint32
if w == io.Discard {
isDiscard = 1
}
atomic.StoreUint32(&l.isDiscard, isDiscard)
// Reuse cached renderers
if v, ok := registry.Load(w); ok {
l.re = v.(*lipgloss.Renderer)
} else {
l.re = lipgloss.NewRenderer(w, termenv.WithColorCache(true))
registry.Store(w, l.re)
}
}
// SetFormatter sets the formatter.
func (l *Logger) SetFormatter(f Formatter) {
l.mu.Lock()
defer l.mu.Unlock()
l.formatter = f
}
// SetCallerFormatter sets the caller formatter.
func (l *Logger) SetCallerFormatter(f CallerFormatter) {
l.mu.Lock()
defer l.mu.Unlock()
l.callerFormatter = f
}
// SetCallerOffset sets the caller offset.
func (l *Logger) SetCallerOffset(offset int) {
l.mu.Lock()
defer l.mu.Unlock()
l.callerOffset = offset
}
// SetColorProfile force sets the underlying Lip Gloss renderer color profile
// for the TextFormatter.
func (l *Logger) SetColorProfile(profile termenv.Profile) {
l.re.SetColorProfile(profile)
}
// SetStyles sets the logger styles for the TextFormatter.
func (l *Logger) SetStyles(s *Styles) {
if s == nil {
s = DefaultStyles()
}
l.mu.Lock()
defer l.mu.Unlock()
l.styles = s
}
// With returns a new logger with the given keyvals added.
func (l *Logger) With(keyvals ...interface{}) *Logger {
var st Styles
l.mu.Lock()
sl := *l
st = *l.styles
l.mu.Unlock()
sl.b = bytes.Buffer{}
sl.mu = &sync.RWMutex{}
sl.helpers = &sync.Map{}
sl.fields = append(l.fields, keyvals...)
sl.styles = &st
return &sl
}
// WithPrefix returns a new logger with the given prefix.
func (l *Logger) WithPrefix(prefix string) *Logger {
sl := l.With()
sl.SetPrefix(prefix)
return sl
}
// Debug prints a debug message.
func (l *Logger) Debug(msg interface{}, keyvals ...interface{}) {
l.Log(DebugLevel, msg, keyvals...)
}
// Info prints an info message.
func (l *Logger) Info(msg interface{}, keyvals ...interface{}) {
l.Log(InfoLevel, msg, keyvals...)
}
// Warn prints a warning message.
func (l *Logger) Warn(msg interface{}, keyvals ...interface{}) {
l.Log(WarnLevel, msg, keyvals...)
}
// Error prints an error message.
func (l *Logger) Error(msg interface{}, keyvals ...interface{}) {
l.Log(ErrorLevel, msg, keyvals...)
}
// Fatal prints a fatal message and exits.
func (l *Logger) Fatal(msg interface{}, keyvals ...interface{}) {
l.Log(FatalLevel, msg, keyvals...)
os.Exit(1)
}
// Print prints a message with no level.
func (l *Logger) Print(msg interface{}, keyvals ...interface{}) {
l.Log(noLevel, msg, keyvals...)
}
// Debugf prints a debug message with formatting.
func (l *Logger) Debugf(format string, args ...interface{}) {
l.Log(DebugLevel, fmt.Sprintf(format, args...))
}
// Infof prints an info message with formatting.
func (l *Logger) Infof(format string, args ...interface{}) {
l.Log(InfoLevel, fmt.Sprintf(format, args...))
}
// Warnf prints a warning message with formatting.
func (l *Logger) Warnf(format string, args ...interface{}) {
l.Log(WarnLevel, fmt.Sprintf(format, args...))
}
// Errorf prints an error message with formatting.
func (l *Logger) Errorf(format string, args ...interface{}) {
l.Log(ErrorLevel, fmt.Sprintf(format, args...))
}
// Fatalf prints a fatal message with formatting and exits.
func (l *Logger) Fatalf(format string, args ...interface{}) {
l.Log(FatalLevel, fmt.Sprintf(format, args...))
os.Exit(1)
}
// Printf prints a message with no level and formatting.
func (l *Logger) Printf(format string, args ...interface{}) {
l.Log(noLevel, fmt.Sprintf(format, args...))
}
golang-github-charmbracelet-log-0.4.0/logger_121.go 0000664 0000000 0000000 00000003153 14663157754 0022030 0 ustar 00root root 0000000 0000000 //go:build go1.21
// +build go1.21
package log
import (
"context"
"log/slog"
"runtime"
"sync/atomic"
)
// Enabled reports whether the logger is enabled for the given level.
//
// Implements slog.Handler.
func (l *Logger) Enabled(_ context.Context, level slog.Level) bool {
return atomic.LoadInt32(&l.level) <= int32(fromSlogLevel[level])
}
// Handle handles the Record. It will only be called if Enabled returns true.
//
// Implements slog.Handler.
func (l *Logger) Handle(ctx context.Context, record slog.Record) error {
if !l.Enabled(ctx, record.Level) {
return nil
}
fields := make([]interface{}, 0, record.NumAttrs()*2)
record.Attrs(func(a slog.Attr) bool {
fields = append(fields, a.Key, a.Value.String())
return true
})
// Get the caller frame using the record's PC.
frames := runtime.CallersFrames([]uintptr{record.PC})
frame, _ := frames.Next()
l.handle(fromSlogLevel[record.Level], l.timeFunc(record.Time), []runtime.Frame{frame}, record.Message, fields...)
return nil
}
// WithAttrs returns a new Handler with the given attributes added.
//
// Implements slog.Handler.
func (l *Logger) WithAttrs(attrs []slog.Attr) slog.Handler {
fields := make([]interface{}, 0, len(attrs)*2)
for _, attr := range attrs {
fields = append(fields, attr.Key, attr.Value)
}
return l.With(fields...)
}
// WithGroup returns a new Handler with the given group name prepended to the
// current group name or prefix.
//
// Implements slog.Handler.
func (l *Logger) WithGroup(name string) slog.Handler {
if l.prefix != "" {
name = l.prefix + "." + name
}
return l.WithPrefix(name)
}
var _ slog.Handler = (*Logger)(nil)
golang-github-charmbracelet-log-0.4.0/logger_121_test.go 0000664 0000000 0000000 00000005507 14663157754 0023074 0 ustar 00root root 0000000 0000000 //go:build go1.21
// +build go1.21
package log
import (
"bytes"
"testing"
"log/slog"
"github.com/stretchr/testify/assert"
)
func TestSlogSimple(t *testing.T) {
var buf bytes.Buffer
h := New(&buf)
h.SetLevel(DebugLevel)
l := slog.New(h)
cases := []struct {
name string
expected string
msg string
attrs []any
print func(string, ...any)
}{
{
name: "slog debug",
expected: "DEBU slog debug\n",
msg: "slog debug",
print: l.Debug,
attrs: nil,
},
{
name: "slog info",
expected: "INFO slog info\n",
msg: "slog info",
print: l.Info,
attrs: nil,
},
{
name: "slog warn",
expected: "WARN slog warn\n",
msg: "slog warn",
print: l.Warn,
attrs: nil,
},
{
name: "slog error",
expected: "ERRO slog error\n",
msg: "slog error",
print: l.Error,
attrs: nil,
},
{
name: "slog error attrs",
expected: "ERRO slog error foo=bar\n",
msg: "slog error",
print: l.Error,
attrs: []any{"foo", "bar"},
},
}
for _, c := range cases {
buf.Reset()
t.Run(c.name, func(t *testing.T) {
c.print(c.msg, c.attrs...)
assert.Equal(t, c.expected, buf.String())
})
}
}
func TestSlogWith(t *testing.T) {
var buf bytes.Buffer
h := New(&buf)
h.SetLevel(DebugLevel)
l := slog.New(h).With("a", "b")
cases := []struct {
name string
expected string
msg string
attrs []any
print func(string, ...any)
}{
{
name: "slog debug",
expected: "DEBU slog debug a=b foo=bar\n",
msg: "slog debug",
print: l.Debug,
attrs: []any{"foo", "bar"},
},
{
name: "slog info",
expected: "INFO slog info a=b foo=bar\n",
msg: "slog info",
print: l.Info,
attrs: []any{"foo", "bar"},
},
{
name: "slog warn",
expected: "WARN slog warn a=b foo=bar\n",
msg: "slog warn",
print: l.Warn,
attrs: []any{"foo", "bar"},
},
{
name: "slog error",
expected: "ERRO slog error a=b foo=bar\n",
msg: "slog error",
print: l.Error,
attrs: []any{"foo", "bar"},
},
}
for _, c := range cases {
buf.Reset()
t.Run(c.name, func(t *testing.T) {
c.print(c.msg, c.attrs...)
assert.Equal(t, c.expected, buf.String())
})
}
}
func TestSlogWithGroup(t *testing.T) {
var buf bytes.Buffer
h := New(&buf)
l := slog.New(h).WithGroup("charm").WithGroup("bracelet")
cases := []struct {
name string
expected string
msg string
}{
{
name: "simple",
msg: "message",
expected: "INFO charm.bracelet: message\n",
},
{
name: "empty",
msg: "",
expected: "INFO charm.bracelet:\n",
},
}
for _, c := range cases {
buf.Reset()
t.Run(c.name, func(t *testing.T) {
l.Info(c.msg)
assert.Equal(t, c.expected, buf.String())
})
}
}
golang-github-charmbracelet-log-0.4.0/logger_no121.go 0000664 0000000 0000000 00000003104 14663157754 0022361 0 ustar 00root root 0000000 0000000 //go:build !go1.21
// +build !go1.21
package log
import (
"context"
"runtime"
"sync/atomic"
"golang.org/x/exp/slog"
)
// Enabled reports whether the logger is enabled for the given level.
//
// Implements slog.Handler.
func (l *Logger) Enabled(_ context.Context, level slog.Level) bool {
return atomic.LoadInt32(&l.level) <= int32(fromSlogLevel[level])
}
// Handle handles the Record. It will only be called if Enabled returns true.
//
// Implements slog.Handler.
func (l *Logger) Handle(_ context.Context, record slog.Record) error {
fields := make([]interface{}, 0, record.NumAttrs()*2)
record.Attrs(func(a slog.Attr) bool {
fields = append(fields, a.Key, a.Value.String())
return true
})
// Get the caller frame using the record's PC.
frames := runtime.CallersFrames([]uintptr{record.PC})
frame, _ := frames.Next()
l.handle(fromSlogLevel[record.Level], l.timeFunc(record.Time), []runtime.Frame{frame}, record.Message, fields...)
return nil
}
// WithAttrs returns a new Handler with the given attributes added.
//
// Implements slog.Handler.
func (l *Logger) WithAttrs(attrs []slog.Attr) slog.Handler {
fields := make([]interface{}, 0, len(attrs)*2)
for _, attr := range attrs {
fields = append(fields, attr.Key, attr.Value)
}
return l.With(fields...)
}
// WithGroup returns a new Handler with the given group name prepended to the
// current group name or prefix.
//
// Implements slog.Handler.
func (l *Logger) WithGroup(name string) slog.Handler {
if l.prefix != "" {
name = l.prefix + "." + name
}
return l.WithPrefix(name)
}
var _ slog.Handler = (*Logger)(nil)
golang-github-charmbracelet-log-0.4.0/logger_test.go 0000664 0000000 0000000 00000012177 14663157754 0022512 0 ustar 00root root 0000000 0000000 package log
import (
"bytes"
"io"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestSubLogger(t *testing.T) {
var buf bytes.Buffer
l := New(&buf)
cases := []struct {
name string
expected string
msg string
fields []interface{}
kvs []interface{}
}{
{
name: "sub logger nil fields",
expected: "INFO info\n",
msg: "info",
fields: nil,
kvs: nil,
},
{
name: "sub logger info",
expected: "INFO info foo=bar\n",
msg: "info",
fields: []interface{}{"foo", "bar"},
kvs: nil,
},
{
name: "sub logger info with kvs",
expected: "INFO info foo=bar foobar=baz\n",
msg: "info",
fields: []interface{}{"foo", "bar"},
kvs: []interface{}{"foobar", "baz"},
},
{
name: "emoji",
expected: "INFO ๐ ๐ฑ\n",
msg: "๐ ๐ฑ",
fields: nil,
kvs: nil,
},
}
for _, c := range cases {
buf.Reset()
t.Run(c.name, func(t *testing.T) {
l.With(c.fields...).Info(c.msg, c.kvs...)
assert.Equal(t, c.expected, buf.String())
})
}
}
func TestWrongLevel(t *testing.T) {
var buf bytes.Buffer
cases := []struct {
name string
expected string
level Level
}{
{
name: "wrong level",
expected: "",
level: Level(999),
},
{
name: "wrong level negative",
expected: "INFO info\n",
level: Level(-999),
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
buf.Reset()
l := New(&buf)
l.SetLevel(c.level)
l.Info("info")
assert.Equal(t, c.expected, buf.String())
})
}
}
func TestLogFormatter(t *testing.T) {
var buf bytes.Buffer
l := New(&buf)
l.SetLevel(DebugLevel)
cases := []struct {
name string
format string
args []interface{}
fun func(string, ...interface{})
expected string
}{
{
name: "info format",
format: "%s %s",
args: []interface{}{"foo", "bar"},
fun: l.Infof,
expected: "INFO foo bar\n",
},
{
name: "debug format",
format: "%s %s",
args: []interface{}{"foo", "bar"},
fun: l.Debugf,
expected: "DEBU foo bar\n",
},
{
name: "warn format",
format: "%s %s",
args: []interface{}{"foo", "bar"},
fun: l.Warnf,
expected: "WARN foo bar\n",
},
{
name: "error format",
format: "%s %s",
args: []interface{}{"foo", "bar"},
fun: l.Errorf,
expected: "ERRO foo bar\n",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
buf.Reset()
c.fun(c.format, "foo", "bar")
assert.Equal(t, c.expected, buf.String())
})
}
}
func TestEmptyMessage(t *testing.T) {
var buf bytes.Buffer
l := New(&buf)
cases := []struct {
name string
expected string
msg string
fields []interface{}
kvs []interface{}
}{
{
name: "empty message nil fields",
expected: "INFO\n",
msg: "",
fields: nil,
kvs: nil,
},
{
name: "empty message with fields",
expected: "INFO foo=bar\n",
msg: "",
fields: []interface{}{"foo", "bar"},
kvs: nil,
},
{
name: "empty message with fields & kvs",
expected: "INFO foo=bar foobar=baz\n",
msg: "",
fields: []interface{}{"foo", "bar"},
kvs: []interface{}{"foobar", "baz"},
},
}
for _, c := range cases {
buf.Reset()
t.Run(c.name, func(t *testing.T) {
l.With(c.fields...).Info(c.msg, c.kvs...)
assert.Equal(t, c.expected, buf.String())
})
}
}
func TestLogWithPrefix(t *testing.T) {
var buf bytes.Buffer
cases := []struct {
name string
expected string
prefix string
msg string
}{
{
name: "with prefix",
expected: "INFO prefix: info\n",
prefix: "prefix",
msg: "info",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
buf.Reset()
l := New(&buf)
l.SetPrefix(c.prefix)
l.Info(c.msg)
assert.Equal(t, c.expected, buf.String())
})
}
}
func TestLogWithRaceCondition(t *testing.T) {
var buf bytes.Buffer
cases := []struct {
name string
}{
{
name: "must be run with -race",
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
buf.Reset()
l := New(&buf)
var done sync.WaitGroup
done.Add(2)
go func() {
l.With("arg1", "val1", "arg2", "val2")
done.Done()
}()
go func() {
l.Info("kinda long log message")
done.Done()
}()
done.Wait()
})
}
}
func TestRace(t *testing.T) {
t.Parallel()
w := io.Discard
l := New(w)
for i := 0; i < 100; i++ {
t.Run("race", func(t *testing.T) {
t.Parallel()
s := l.StandardLog()
l.Info("foo")
l.GetLevel()
l.Print("foo")
s.Print("foo")
s.Writer().Write([]byte("bar"))
s.Output(1, "baz")
l.SetOutput(w)
l.Debug("foo")
l.SetLevel(InfoLevel)
l.GetPrefix()
o := l.With("foo", "bar")
o.Printf("foo %s", "bar")
o.SetTimeFormat(time.Kitchen)
o.Warn("foo")
o.SetOutput(w)
o.Error("foo")
o.SetFormatter(JSONFormatter)
})
}
}
func TestCustomLevel(t *testing.T) {
var buf bytes.Buffer
level500 := Level(500)
l := New(&buf)
l.SetLevel(level500)
l.Logf(level500, "foo")
assert.Equal(t, "foo\n", buf.String())
}
golang-github-charmbracelet-log-0.4.0/options.go 0000664 0000000 0000000 00000004055 14663157754 0021663 0 ustar 00root root 0000000 0000000 package log
import (
"fmt"
"time"
)
// DefaultTimeFormat is the default time format.
const DefaultTimeFormat = "2006/01/02 15:04:05"
// TimeFunction is a function that returns a time.Time.
type TimeFunction = func(time.Time) time.Time
// NowUTC is a convenient function that returns the
// current time in UTC timezone.
//
// This is to be used as a time function.
// For example:
//
// log.SetTimeFunction(log.NowUTC)
func NowUTC(t time.Time) time.Time {
return t.UTC()
}
// CallerFormatter is the caller formatter.
type CallerFormatter func(string, int, string) string
// ShortCallerFormatter is a caller formatter that returns the last 2 levels of the path
// and line number.
func ShortCallerFormatter(file string, line int, _ string) string {
return fmt.Sprintf("%s:%d", trimCallerPath(file, 2), line)
}
// LongCallerFormatter is a caller formatter that returns the full path and line number.
func LongCallerFormatter(file string, line int, _ string) string {
return fmt.Sprintf("%s:%d", file, line)
}
// Options is the options for the logger.
type Options struct {
// TimeFunction is the time function for the logger. The default is time.Now.
TimeFunction TimeFunction
// TimeFormat is the time format for the logger. The default is "2006/01/02 15:04:05".
TimeFormat string
// Level is the level for the logger. The default is InfoLevel.
Level Level
// Prefix is the prefix for the logger. The default is no prefix.
Prefix string
// ReportTimestamp is whether the logger should report the timestamp. The default is false.
ReportTimestamp bool
// ReportCaller is whether the logger should report the caller location. The default is false.
ReportCaller bool
// CallerFormatter is the caller format for the logger. The default is ShortCallerFormatter.
CallerFormatter CallerFormatter
// CallerOffset is the caller format for the logger. The default is 0.
CallerOffset int
// Fields is the fields for the logger. The default is no fields.
Fields []interface{}
// Formatter is the formatter for the logger. The default is TextFormatter.
Formatter Formatter
}
golang-github-charmbracelet-log-0.4.0/options_test.go 0000664 0000000 0000000 00000003470 14663157754 0022722 0 ustar 00root root 0000000 0000000 package log
import (
"bytes"
"fmt"
"io"
"testing"
"github.com/stretchr/testify/require"
)
func TestOptions(t *testing.T) {
opts := Options{
Level: ErrorLevel,
ReportCaller: true,
Fields: []interface{}{"foo", "bar"},
}
logger := NewWithOptions(io.Discard, opts)
require.Equal(t, ErrorLevel, logger.GetLevel())
require.True(t, logger.reportCaller)
require.False(t, logger.reportTimestamp)
require.Equal(t, []interface{}{"foo", "bar"}, logger.fields)
require.Equal(t, TextFormatter, logger.formatter)
require.Equal(t, DefaultTimeFormat, logger.timeFormat)
require.NotNil(t, logger.timeFunc)
}
func TestCallerFormatter(t *testing.T) {
var buf bytes.Buffer
l := NewWithOptions(&buf, Options{ReportCaller: true})
frames := l.frames(0)
frame, _ := frames.Next()
file, line, fn := frame.File, frame.Line, frame.Function
hi := func() { l.Info("hi") }
cases := []struct {
name string
expected string
format CallerFormatter
}{
{
name: "short caller formatter",
expected: fmt.Sprintf("INFO