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. ๐ชต
It provides a leveled structured human readable logger with a small API. Unlike
[standard `log`][stdlog], the Charm logger provides customizable colorful human
readable logging with batteries included.
- Uses [Lip Gloss][lipgloss] to style and colorize the output.
- Colorful, human readable format.
- Ability to customize the time stamp format.
- Skips caller frames and marks function as helpers.
- Leveled logging.
- Text, JSON, and Logfmt formatters.
- Store and retrieve logger in and from context.
- Slog handler.
- Standard log adapter.
## Usage
Use `go get` to download the dependency.
```bash
go get github.com/charmbracelet/log@latest
```
Then, `import` it in your Go files:
```go
import "github.com/charmbracelet/log"
```
The Charm logger comes with a global package-wise logger with timestamps turned
on, and the logging level set to `info`.
```go
log.Debug("Cookie ๐ช") // won't print anything
log.Info("Hello World!")
```
All logging levels accept optional key/value pairs to be printed along with a
message.
```go
err := fmt.Errorf("too much sugar")
log.Error("failed to bake cookies", "err", err)
```
You can use `log.Print()` to print messages without a level prefix.
```go
log.Print("Baking 101")
// 2023/01/04 10:04:06 Baking 101
```
### New loggers
Use `New()` to create new loggers.
```go
logger := log.New(os.Stderr)
if butter {
logger.Warn("chewy!", "butter", true)
}
```
### Levels
Log offers multiple levels to filter your logs on. Available levels are:
```go
log.DebugLevel
log.InfoLevel
log.WarnLevel
log.ErrorLevel
log.FatalLevel
```
Use `log.SetLevel()` to set the log level. You can also create a new logger with
a specific log level using `log.Options{Level: }`.
Use the corresponding function to log a message:
```go
err := errors.New("Baking error 101")
log.Debug(err)
log.Info(err)
log.Warn(err)
log.Error(err)
log.Fatal(err) // this calls os.Exit(1)
log.Print(err) // prints regardless of log level
```
Or use the formatter variant:
```go
format := "%s %d"
log.Debugf(format, "chocolate", 10)
log.Warnf(format, "adding more", 5)
log.Errorf(format, "increasing temp", 420)
log.Fatalf(format, "too hot!", 500) // this calls os.Exit(1)
log.Printf(format, "baking cookies") // prints regardless of log level
// Use these in conjunction with `With(...)` to add more context
log.With("err", err).Errorf("unable to start %s", "oven")
```
### Structured
All the functions above take a message and key-value pairs of anything. The
message can also be of type any.
```go
ingredients := []string{"flour", "butter", "sugar", "chocolate"}
log.Debug("Available ingredients", "ingredients", ingredients)
// DEBUG Available ingredients ingredients="[flour butter sugar chocolate]"
```
### Options
You can customize the logger with options. Use `log.NewWithOptions()` and
`log.Options{}` to customize your new logger.
```go
logger := log.NewWithOptions(os.Stderr, log.Options{
ReportCaller: true,
ReportTimestamp: true,
TimeFormat: time.Kitchen,
Prefix: "Baking ๐ช ",
})
logger.Info("Starting oven!", "degree", 375)
time.Sleep(10 * time.Minute)
logger.Info("Finished baking")
```
You can also use logger setters to customize the logger.
```go
logger := log.New(os.Stderr)
logger.SetReportTimestamp(false)
logger.SetReportCaller(false)
logger.SetLevel(log.DebugLevel)
```
Use `log.SetFormatter()` or `log.Options{Formatter: }` to change the output
format. Available options are:
- `log.TextFormatter` (_default_)
- `log.JSONFormatter`
- `log.LogfmtFormatter`
> **Note** styling only affects the `TextFormatter`. Styling is disabled if the
> output is not a TTY.
For a list of available options, refer to [options.go](./options.go).
### Styles
You can customize the logger styles using [Lipgloss][lipgloss]. The styles are
defined at a global level in [styles.go](./styles.go).
```go
// Override the default error level style.
styles := log.DefaultStyles()
styles.Levels[log.ErrorLevel] = lipgloss.NewStyle().
SetString("ERROR!!").
Padding(0, 1, 0, 1).
Background(lipgloss.Color("204")).
Foreground(lipgloss.Color("0"))
// Add a custom style for key `err`
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")
```
### Sub-logger
Create sub-loggers with their specific fields.
```go
logger := log.NewWithOptions(os.Stderr, log.Options{
Prefix: "Baking ๐ช "
})
batch2 := logger.With("batch", 2, "chocolateChips", true)
batch2.Debug("Preparing batch 2...")
batch2.Debug("Adding chocolate chips")
```
### Format Messages
You can use `fmt.Sprintf()` to format messages.
```go
for item := 1; i <= 100; i++ {
log.Info(fmt.Sprintf("Baking %d/100...", item))
}
```
Or arguments:
```go
for temp := 375; temp <= 400; temp++ {
log.Info("Increasing temperature", "degree", fmt.Sprintf("%dยฐF", temp))
}
```
### Helper Functions
Skip caller frames in helper functions. Similar to what you can do with
`testing.TB().Helper()`.
```go
func startOven(degree int) {
log.Helper()
log.Info("Starting oven", "degree", degree)
}
log.SetReportCaller(true)
startOven(400) // INFO Starting oven degree=400
```
This will use the _caller_ function (`startOven`) line number instead of the
logging function (`log.Info`) to report the source location.
### Slog Handler
You can use Log as an [`log/slog`](https://pkg.go.dev/log/slog) handler. Just
pass a logger instance to Slog and you're good to go.
```go
handler := log.New(os.Stderr)
logger := slog.New(handler)
logger.Error("meow?")
```
### Standard Log Adapter
Some Go libraries, especially the ones in the standard library, will only accept
the [standard logger][stdlog] interface. For instance, the HTTP Server from
`net/http` will only take a `*log.Logger` for its `ErrorLog` field.
For this, you can use the standard log adapter, which simply wraps the logger in
a `*log.Logger` interface.
```go
logger := log.NewWithOptions(os.Stderr, log.Options{Prefix: "http"})
stdlog := logger.StandardLog(log.StandardLogOptions{
ForceLevel: log.ErrorLevel,
})
s := &http.Server{
Addr: ":8080",
Handler: handler,
ErrorLog: stdlog,
}
stdlog.Printf("Failed to make bake request, %s", fmt.Errorf("temperature is too low"))
// ERROR http: Failed to make bake request, temperature is too low
```
## Gum
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 hi\n", line+3),
format: ShortCallerFormatter,
},
{
name: "long caller formatter",
expected: fmt.Sprintf("INFO <%s:%d> hi\n", file, line+3),
format: LongCallerFormatter,
},
{
name: "foo caller formatter",
expected: "INFO hi\n",
format: func(file string, line int, fn string) string {
return "foo"
},
},
{
name: "custom caller formatter",
expected: fmt.Sprintf("INFO <%s:%d:%s.func1> hi\n", file, line+3, fn),
format: func(file string, line int, fn string) string {
return fmt.Sprintf("%s:%d:%s", file, line, fn)
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
buf.Reset()
l.callerFormatter = c.format
hi()
require.Equal(t, c.expected, buf.String())
})
}
}
golang-github-charmbracelet-log-0.4.0/pkg.go 0000664 0000000 0000000 00000014315 14663157754 0020751 0 ustar 00root root 0000000 0000000 package log
import (
"bytes"
"fmt"
"io"
"log"
"os"
"sync"
"time"
"github.com/muesli/termenv"
)
var (
// registry is a map of all registered lipgloss renderers.
registry = sync.Map{}
// defaultLogger is the default global logger instance.
defaultLoggerOnce sync.Once
defaultLogger *Logger
)
// Default returns the default logger. The default logger comes with timestamp enabled.
func Default() *Logger {
defaultLoggerOnce.Do(func() {
if defaultLogger != nil {
// already set via SetDefault.
return
}
defaultLogger = NewWithOptions(os.Stderr, Options{ReportTimestamp: true})
})
return defaultLogger
}
// SetDefault sets the default global logger.
func SetDefault(logger *Logger) {
defaultLogger = logger
}
// New returns a new logger with the default options.
func New(w io.Writer) *Logger {
return NewWithOptions(w, Options{})
}
// NewWithOptions returns a new logger using the provided options.
func NewWithOptions(w io.Writer, o Options) *Logger {
l := &Logger{
b: bytes.Buffer{},
mu: &sync.RWMutex{},
helpers: &sync.Map{},
level: int32(o.Level),
reportTimestamp: o.ReportTimestamp,
reportCaller: o.ReportCaller,
prefix: o.Prefix,
timeFunc: o.TimeFunction,
timeFormat: o.TimeFormat,
formatter: o.Formatter,
fields: o.Fields,
callerFormatter: o.CallerFormatter,
callerOffset: o.CallerOffset,
}
l.SetOutput(w)
l.SetLevel(Level(l.level))
l.SetStyles(DefaultStyles())
if l.callerFormatter == nil {
l.callerFormatter = ShortCallerFormatter
}
if l.timeFunc == nil {
l.timeFunc = func(t time.Time) time.Time { return t }
}
if l.timeFormat == "" {
l.timeFormat = DefaultTimeFormat
}
return l
}
// SetReportTimestamp sets whether to report timestamp for the default logger.
func SetReportTimestamp(report bool) {
Default().SetReportTimestamp(report)
}
// SetReportCaller sets whether to report caller location for the default logger.
func SetReportCaller(report bool) {
Default().SetReportCaller(report)
}
// SetLevel sets the level for the default logger.
func SetLevel(level Level) {
Default().SetLevel(level)
}
// GetLevel returns the level for the default logger.
func GetLevel() Level {
return Default().GetLevel()
}
// SetTimeFormat sets the time format for the default logger.
func SetTimeFormat(format string) {
Default().SetTimeFormat(format)
}
// SetTimeFunction sets the time function for the default logger.
func SetTimeFunction(f TimeFunction) {
Default().SetTimeFunction(f)
}
// SetOutput sets the output for the default logger.
func SetOutput(w io.Writer) {
Default().SetOutput(w)
}
// SetFormatter sets the formatter for the default logger.
func SetFormatter(f Formatter) {
Default().SetFormatter(f)
}
// SetCallerFormatter sets the caller formatter for the default logger.
func SetCallerFormatter(f CallerFormatter) {
Default().SetCallerFormatter(f)
}
// SetCallerOffset sets the caller offset for the default logger.
func SetCallerOffset(offset int) {
Default().SetCallerOffset(offset)
}
// SetPrefix sets the prefix for the default logger.
func SetPrefix(prefix string) {
Default().SetPrefix(prefix)
}
// SetColorProfile force sets the underlying Lip Gloss renderer color profile
// for the TextFormatter.
func SetColorProfile(profile termenv.Profile) {
Default().SetColorProfile(profile)
}
// SetStyles sets the logger styles for the TextFormatter.
func SetStyles(s *Styles) {
Default().SetStyles(s)
}
// GetPrefix returns the prefix for the default logger.
func GetPrefix() string {
return Default().GetPrefix()
}
// With returns a new logger with the given keyvals.
func With(keyvals ...interface{}) *Logger {
return Default().With(keyvals...)
}
// WithPrefix returns a new logger with the given prefix.
func WithPrefix(prefix string) *Logger {
return Default().WithPrefix(prefix)
}
// Helper marks the calling function as a helper
// and skips it for source location information.
// It's the equivalent of testing.TB.Helper().
func Helper() {
Default().helper(1)
}
// Log logs a message with the given level.
func Log(level Level, msg interface{}, keyvals ...interface{}) {
Default().Log(level, msg, keyvals...)
}
// Debug logs a debug message.
func Debug(msg interface{}, keyvals ...interface{}) {
Default().Log(DebugLevel, msg, keyvals...)
}
// Info logs an info message.
func Info(msg interface{}, keyvals ...interface{}) {
Default().Log(InfoLevel, msg, keyvals...)
}
// Warn logs a warning message.
func Warn(msg interface{}, keyvals ...interface{}) {
Default().Log(WarnLevel, msg, keyvals...)
}
// Error logs an error message.
func Error(msg interface{}, keyvals ...interface{}) {
Default().Log(ErrorLevel, msg, keyvals...)
}
// Fatal logs a fatal message and exit.
func Fatal(msg interface{}, keyvals ...interface{}) {
Default().Log(FatalLevel, msg, keyvals...)
os.Exit(1)
}
// Print logs a message with no level.
func Print(msg interface{}, keyvals ...interface{}) {
Default().Log(noLevel, msg, keyvals...)
}
// Logf logs a message with formatting and level.
func Logf(level Level, format string, args ...interface{}) {
Default().Logf(level, format, args...)
}
// Debugf logs a debug message with formatting.
func Debugf(format string, args ...interface{}) {
Default().Log(DebugLevel, fmt.Sprintf(format, args...))
}
// Infof logs an info message with formatting.
func Infof(format string, args ...interface{}) {
Default().Log(InfoLevel, fmt.Sprintf(format, args...))
}
// Warnf logs a warning message with formatting.
func Warnf(format string, args ...interface{}) {
Default().Log(WarnLevel, fmt.Sprintf(format, args...))
}
// Errorf logs an error message with formatting.
func Errorf(format string, args ...interface{}) {
Default().Log(ErrorLevel, fmt.Sprintf(format, args...))
}
// Fatalf logs a fatal message with formatting and exit.
func Fatalf(format string, args ...interface{}) {
Default().Log(FatalLevel, fmt.Sprintf(format, args...))
os.Exit(1)
}
// Printf logs a message with formatting and no level.
func Printf(format string, args ...interface{}) {
Default().Log(noLevel, fmt.Sprintf(format, args...))
}
// StandardLog returns a standard logger from the default logger.
func StandardLog(opts ...StandardLogOptions) *log.Logger {
return Default().StandardLog(opts...)
}
golang-github-charmbracelet-log-0.4.0/pkg_test.go 0000664 0000000 0000000 00000012112 14663157754 0022001 0 ustar 00root root 0000000 0000000 package log
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/muesli/termenv"
"github.com/stretchr/testify/assert"
)
func TestGlobal(t *testing.T) {
var buf bytes.Buffer
SetOutput(&buf)
SetTimeFunction(_zeroTime)
cases := []struct {
name string
expected string
msg string
kvs []interface{}
f func(msg interface{}, kvs ...interface{})
}{
{
name: "default logger info with timestamp",
expected: "0002/01/01 00:00:00 INFO info\n",
msg: "info",
kvs: nil,
f: Info,
},
{
name: "default logger debug with timestamp",
expected: "",
msg: "info",
kvs: nil,
f: Debug,
},
{
name: "default logger error with timestamp",
expected: "0002/01/01 00:00:00 ERRO info\n",
msg: "info",
kvs: nil,
f: Error,
},
}
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())
})
}
}
func TestPrint(t *testing.T) {
var buf bytes.Buffer
SetOutput(&buf)
SetLevel(FatalLevel)
SetTimeFunction(_zeroTime)
SetReportTimestamp(true)
SetReportCaller(false)
SetTimeFormat(DefaultTimeFormat)
SetColorProfile(termenv.ANSI)
Error("error")
Print("print")
assert.Equal(t, "0002/01/01 00:00:00 print\n", buf.String())
}
func TestPrintf(t *testing.T) {
var buf bytes.Buffer
SetOutput(&buf)
SetLevel(FatalLevel)
SetTimeFunction(_zeroTime)
SetReportTimestamp(true)
SetReportCaller(false)
SetTimeFormat(DefaultTimeFormat)
Errorf("error")
Printf("print")
assert.Equal(t, "0002/01/01 00:00:00 print\n", buf.String())
}
func TestFatal(t *testing.T) {
SetReportTimestamp(true)
SetReportCaller(false)
SetTimeFormat(DefaultTimeFormat)
if os.Getenv("FATAL") == "1" {
Fatal("i'm dead")
return
}
cmd := exec.Command(os.Args[0], "-test.run=TestFatal")
cmd.Env = append(os.Environ(), "FATAL=1")
err := cmd.Run()
if e, ok := err.(*exec.ExitError); ok && !e.Success() {
return
}
t.Fatalf("process ran with err %v, want exit status 1", err)
}
func TestFatalf(t *testing.T) {
SetReportTimestamp(true)
SetReportCaller(false)
SetTimeFormat(DefaultTimeFormat)
if os.Getenv("FATAL") == "1" {
Fatalf("i'm %s", "dead")
return
}
cmd := exec.Command(os.Args[0], "-test.run=TestFatalf")
cmd.Env = append(os.Environ(), "FATAL=1")
err := cmd.Run()
if e, ok := err.(*exec.ExitError); ok && !e.Success() {
return
}
t.Fatalf("process ran with err %v, want exit status 1", err)
}
func TestDebugf(t *testing.T) {
var buf bytes.Buffer
SetOutput(&buf)
SetLevel(DebugLevel)
SetTimeFunction(_zeroTime)
SetReportTimestamp(true)
SetReportCaller(true)
SetTimeFormat(DefaultTimeFormat)
_, file, line, _ := runtime.Caller(0)
Debugf("debug %s", "foo")
assert.Equal(t, fmt.Sprintf("0002/01/01 00:00:00 DEBU debug foo\n", filepath.Base(file), line+1), buf.String())
}
func TestInfof(t *testing.T) {
var buf bytes.Buffer
SetOutput(&buf)
SetLevel(InfoLevel)
SetReportTimestamp(false)
SetReportCaller(false)
SetTimeFormat(DefaultTimeFormat)
Infof("info %s", "foo")
assert.Equal(t, "INFO info foo\n", buf.String())
}
func TestWarnf(t *testing.T) {
var buf bytes.Buffer
SetOutput(&buf)
SetLevel(WarnLevel)
SetReportCaller(false)
SetReportTimestamp(true)
SetTimeFunction(_zeroTime)
SetTimeFormat(DefaultTimeFormat)
Warnf("warn %s", "foo")
assert.Equal(t, "0002/01/01 00:00:00 WARN warn foo\n", buf.String())
}
func TestErrorf(t *testing.T) {
var buf bytes.Buffer
SetOutput(&buf)
SetLevel(ErrorLevel)
SetReportCaller(false)
SetReportTimestamp(true)
SetTimeFunction(_zeroTime)
SetTimeFormat(time.Kitchen)
Errorf("error %s", "foo")
assert.Equal(t, "12:00AM ERRO error foo\n", buf.String())
}
func TestWith(t *testing.T) {
var buf bytes.Buffer
SetOutput(&buf)
SetLevel(InfoLevel)
SetReportCaller(false)
SetReportTimestamp(true)
SetTimeFunction(_zeroTime)
SetTimeFormat(DefaultTimeFormat)
With("foo", "bar").Info("info")
assert.Equal(t, "0002/01/01 00:00:00 INFO info foo=bar\n", buf.String())
}
func TestGetLevel(t *testing.T) {
SetLevel(InfoLevel)
assert.Equal(t, InfoLevel, GetLevel())
}
func TestPrefix(t *testing.T) {
var buf bytes.Buffer
SetOutput(&buf)
SetLevel(WarnLevel)
SetReportCaller(false)
SetReportTimestamp(false)
SetPrefix("prefix")
Warn("info")
assert.Equal(t, "WARN prefix: info\n", buf.String())
assert.Equal(t, "prefix", GetPrefix())
SetPrefix("")
}
func TestFormatter(t *testing.T) {
var buf bytes.Buffer
SetOutput(&buf)
SetLevel(InfoLevel)
SetReportCaller(false)
SetReportTimestamp(false)
SetFormatter(JSONFormatter)
Info("info")
assert.Equal(t, "{\"level\":\"info\",\"msg\":\"info\"}\n", buf.String())
}
func TestWithPrefix(t *testing.T) {
l := WithPrefix("test")
assert.Equal(t, "test", l.prefix)
}
func TestGlobalCustomLevel(t *testing.T) {
var buf bytes.Buffer
lvl := Level(-1)
SetOutput(&buf)
SetLevel(lvl)
SetReportCaller(false)
SetReportTimestamp(false)
SetFormatter(JSONFormatter)
Log(lvl, "info")
Logf(lvl, "hey %s", "you")
assert.Equal(t, "{\"msg\":\"info\"}\n{\"msg\":\"hey you\"}\n", buf.String())
}
golang-github-charmbracelet-log-0.4.0/stdlog.go 0000664 0000000 0000000 00000002732 14663157754 0021464 0 ustar 00root root 0000000 0000000 package log
import (
"log"
"strings"
)
type stdLogWriter struct {
l *Logger
opt *StandardLogOptions
}
func (l *stdLogWriter) Write(p []byte) (n int, err error) {
str := strings.TrimSuffix(string(p), "\n")
if l.opt != nil {
switch l.opt.ForceLevel {
case DebugLevel:
l.l.Debug(str)
case InfoLevel:
l.l.Info(str)
case WarnLevel:
l.l.Warn(str)
case ErrorLevel:
l.l.Error(str)
}
} else {
switch {
case strings.HasPrefix(str, "DEBUG"):
l.l.Debug(strings.TrimSpace(str[5:]))
case strings.HasPrefix(str, "INFO"):
l.l.Info(strings.TrimSpace(str[4:]))
case strings.HasPrefix(str, "WARN"):
l.l.Warn(strings.TrimSpace(str[4:]))
case strings.HasPrefix(str, "ERROR"):
l.l.Error(strings.TrimSpace(str[5:]))
case strings.HasPrefix(str, "ERR"):
l.l.Error(strings.TrimSpace(str[3:]))
default:
l.l.Info(str)
}
}
return len(p), nil
}
// StandardLogOptions can be used to configure the standard log adapter.
type StandardLogOptions struct {
ForceLevel Level
}
// StandardLog returns a standard logger from Logger. The returned logger
// can infer log levels from message prefix. Expected prefixes are DEBUG, INFO,
// WARN, ERROR, and ERR.
func (l *Logger) StandardLog(opts ...StandardLogOptions) *log.Logger {
nl := l.With()
// The caller stack is
// log.Printf() -> l.Output() -> l.out.Write(stdLogger.Write)
nl.callerOffset += 3
sl := &stdLogWriter{
l: nl,
}
if len(opts) > 0 {
sl.opt = &opts[0]
}
return log.New(sl, "", 0)
}
golang-github-charmbracelet-log-0.4.0/stdlog_test.go 0000664 0000000 0000000 00000004534 14663157754 0022525 0 ustar 00root root 0000000 0000000 package log
import (
"bytes"
"fmt"
"log"
"path/filepath"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestStdLog(t *testing.T) {
var buf bytes.Buffer
l := New(&buf)
cases := []struct {
f func(l *log.Logger)
name string
expected string
}{
{
name: "simple",
expected: "INFO info\n",
f: func(l *log.Logger) { l.Print("info") },
},
{
name: "without level",
expected: "INFO coffee\n",
f: func(l *log.Logger) { l.Print("coffee") },
},
{
name: "error level",
expected: "ERRO coffee\n",
f: func(l *log.Logger) { l.Print("ERROR coffee") },
},
}
for _, c := range cases {
buf.Reset()
t.Run(c.name, func(t *testing.T) {
l.SetOutput(&buf)
l.SetTimeFunction(_zeroTime)
c.f(l.StandardLog())
assert.Equal(t, c.expected, buf.String())
})
}
}
func TestStdLog_forceLevel(t *testing.T) {
var buf bytes.Buffer
logger := New(&buf)
cases := []struct {
name string
expected string
level Level
}{
{
name: "debug",
expected: "",
level: DebugLevel,
},
{
name: "info",
expected: "INFO coffee\n",
level: InfoLevel,
},
{
name: "error",
expected: "ERRO coffee\n",
level: ErrorLevel,
},
}
for _, c := range cases {
buf.Reset()
t.Run(c.name, func(t *testing.T) {
l := logger.StandardLog(StandardLogOptions{ForceLevel: c.level})
l.Print("coffee")
assert.Equal(t, c.expected, buf.String())
})
}
}
func TestStdLog_writer(t *testing.T) {
var buf bytes.Buffer
logger := New(&buf)
logger.SetReportCaller(true)
_, file, line, ok := runtime.Caller(0)
require.True(t, ok)
cases := []struct {
name string
expected string
level Level
}{
{
name: "debug",
expected: "",
level: DebugLevel,
},
{
name: "info",
expected: fmt.Sprintf("INFO coffee\n", filepath.Base(file), line+27),
level: InfoLevel,
},
{
name: "error",
expected: fmt.Sprintf("ERRO coffee\n", filepath.Base(file), line+27),
level: ErrorLevel,
},
}
for _, c := range cases {
buf.Reset()
t.Run(c.name, func(t *testing.T) {
l := log.New(logger.StandardLog(StandardLogOptions{ForceLevel: c.level}).Writer(), "", 0)
l.Print("coffee")
assert.Equal(t, c.expected, buf.String())
})
}
}
golang-github-charmbracelet-log-0.4.0/styles.go 0000664 0000000 0000000 00000004137 14663157754 0021514 0 ustar 00root root 0000000 0000000 package log
import (
"strings"
"github.com/charmbracelet/lipgloss"
)
// Styles defines the styles for the text logger.
type Styles struct {
// Timestamp is the style for timestamps.
Timestamp lipgloss.Style
// Caller is the style for source caller.
Caller lipgloss.Style
// Prefix is the style for prefix.
Prefix lipgloss.Style
// Message is the style for messages.
Message lipgloss.Style
// Key is the style for keys.
Key lipgloss.Style
// Value is the style for values.
Value lipgloss.Style
// Separator is the style for separators.
Separator lipgloss.Style
// Levels are the styles for each level.
Levels map[Level]lipgloss.Style
// Keys overrides styles for specific keys.
Keys map[string]lipgloss.Style
// Values overrides value styles for specific keys.
Values map[string]lipgloss.Style
}
// DefaultStyles returns the default styles.
func DefaultStyles() *Styles {
return &Styles{
Timestamp: lipgloss.NewStyle(),
Caller: lipgloss.NewStyle().Faint(true),
Prefix: lipgloss.NewStyle().Bold(true).Faint(true),
Message: lipgloss.NewStyle(),
Key: lipgloss.NewStyle().Faint(true),
Value: lipgloss.NewStyle(),
Separator: lipgloss.NewStyle().Faint(true),
Levels: map[Level]lipgloss.Style{
DebugLevel: lipgloss.NewStyle().
SetString(strings.ToUpper(DebugLevel.String())).
Bold(true).
MaxWidth(4).
Foreground(lipgloss.Color("63")),
InfoLevel: lipgloss.NewStyle().
SetString(strings.ToUpper(InfoLevel.String())).
Bold(true).
MaxWidth(4).
Foreground(lipgloss.Color("86")),
WarnLevel: lipgloss.NewStyle().
SetString(strings.ToUpper(WarnLevel.String())).
Bold(true).
MaxWidth(4).
Foreground(lipgloss.Color("192")),
ErrorLevel: lipgloss.NewStyle().
SetString(strings.ToUpper(ErrorLevel.String())).
Bold(true).
MaxWidth(4).
Foreground(lipgloss.Color("204")),
FatalLevel: lipgloss.NewStyle().
SetString(strings.ToUpper(FatalLevel.String())).
Bold(true).
MaxWidth(4).
Foreground(lipgloss.Color("134")),
},
Keys: map[string]lipgloss.Style{},
Values: map[string]lipgloss.Style{},
}
}
golang-github-charmbracelet-log-0.4.0/text.go 0000664 0000000 0000000 00000013456 14663157754 0021161 0 ustar 00root root 0000000 0000000 package log
import (
"fmt"
"io"
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
)
const (
separator = "="
indentSeparator = " โ "
)
func (l *Logger) writeIndent(w io.Writer, str string, indent string, newline bool, key string) {
st := l.styles
// kindly borrowed from hclog
for {
nl := strings.IndexByte(str, '\n')
if nl == -1 {
if str != "" {
_, _ = w.Write([]byte(indent))
val := escapeStringForOutput(str, false)
if valueStyle, ok := st.Values[key]; ok {
val = valueStyle.Renderer(l.re).Render(val)
} else {
val = st.Value.Renderer(l.re).Render(val)
}
_, _ = w.Write([]byte(val))
if newline {
_, _ = w.Write([]byte{'\n'})
}
}
return
}
_, _ = w.Write([]byte(indent))
val := escapeStringForOutput(str[:nl], false)
val = st.Value.Renderer(l.re).Render(val)
_, _ = w.Write([]byte(val))
_, _ = w.Write([]byte{'\n'})
str = str[nl+1:]
}
}
func needsEscaping(str string) bool {
for _, b := range str {
if !unicode.IsPrint(b) || b == '"' {
return true
}
}
return false
}
const (
lowerhex = "0123456789abcdef"
)
var bufPool = sync.Pool{
New: func() interface{} {
return new(strings.Builder)
},
}
func escapeStringForOutput(str string, escapeQuotes bool) string {
// kindly borrowed from hclog
if !needsEscaping(str) {
return str
}
bb := bufPool.Get().(*strings.Builder)
bb.Reset()
defer bufPool.Put(bb)
for _, r := range str {
if escapeQuotes && r == '"' {
bb.WriteString(`\"`)
} else if unicode.IsPrint(r) {
bb.WriteRune(r)
} else {
switch r {
case '\a':
bb.WriteString(`\a`)
case '\b':
bb.WriteString(`\b`)
case '\f':
bb.WriteString(`\f`)
case '\n':
bb.WriteString(`\n`)
case '\r':
bb.WriteString(`\r`)
case '\t':
bb.WriteString(`\t`)
case '\v':
bb.WriteString(`\v`)
default:
switch {
case r < ' ':
bb.WriteString(`\x`)
bb.WriteByte(lowerhex[byte(r)>>4])
bb.WriteByte(lowerhex[byte(r)&0xF])
case !utf8.ValidRune(r):
r = 0xFFFD
fallthrough
case r < 0x10000:
bb.WriteString(`\u`)
for s := 12; s >= 0; s -= 4 {
bb.WriteByte(lowerhex[r>>uint(s)&0xF])
}
default:
bb.WriteString(`\U`)
for s := 28; s >= 0; s -= 4 {
bb.WriteByte(lowerhex[r>>uint(s)&0xF])
}
}
}
}
}
return bb.String()
}
func needsQuoting(s string) bool {
for i := 0; i < len(s); {
b := s[i]
if b < utf8.RuneSelf {
if needsQuotingSet[b] {
return true
}
i++
continue
}
r, size := utf8.DecodeRuneInString(s[i:])
if r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r) {
return true
}
i += size
}
return false
}
var needsQuotingSet = [utf8.RuneSelf]bool{
'"': true,
'=': true,
}
func init() {
for i := 0; i < utf8.RuneSelf; i++ {
r := rune(i)
if unicode.IsSpace(r) || !unicode.IsPrint(r) {
needsQuotingSet[i] = true
}
}
}
func writeSpace(w io.Writer, first bool) {
if !first {
w.Write([]byte{' '}) //nolint: errcheck
}
}
func (l *Logger) textFormatter(keyvals ...interface{}) {
st := l.styles
lenKeyvals := len(keyvals)
for i := 0; i < lenKeyvals; i += 2 {
firstKey := i == 0
moreKeys := i < lenKeyvals-2
switch keyvals[i] {
case TimestampKey:
if t, ok := keyvals[i+1].(time.Time); ok {
ts := t.Format(l.timeFormat)
ts = st.Timestamp.Renderer(l.re).Render(ts)
writeSpace(&l.b, firstKey)
l.b.WriteString(ts)
}
case LevelKey:
if level, ok := keyvals[i+1].(Level); ok {
var lvl string
lvlStyle, ok := st.Levels[level]
if !ok {
continue
}
lvl = lvlStyle.Renderer(l.re).String()
if lvl != "" {
writeSpace(&l.b, firstKey)
l.b.WriteString(lvl)
}
}
case CallerKey:
if caller, ok := keyvals[i+1].(string); ok {
caller = fmt.Sprintf("<%s>", caller)
caller = st.Caller.Renderer(l.re).Render(caller)
writeSpace(&l.b, firstKey)
l.b.WriteString(caller)
}
case PrefixKey:
if prefix, ok := keyvals[i+1].(string); ok {
prefix = st.Prefix.Renderer(l.re).Render(prefix + ":")
writeSpace(&l.b, firstKey)
l.b.WriteString(prefix)
}
case MessageKey:
if msg := keyvals[i+1]; msg != nil {
m := fmt.Sprint(msg)
m = st.Message.Renderer(l.re).Render(m)
writeSpace(&l.b, firstKey)
l.b.WriteString(m)
}
default:
sep := separator
indentSep := indentSeparator
sep = st.Separator.Renderer(l.re).Render(sep)
indentSep = st.Separator.Renderer(l.re).Render(indentSep)
key := fmt.Sprint(keyvals[i])
val := fmt.Sprintf("%+v", keyvals[i+1])
raw := val == ""
if raw {
val = `""`
}
if key == "" {
continue
}
actualKey := key
valueStyle := st.Value
if vs, ok := st.Values[actualKey]; ok {
valueStyle = vs
}
if keyStyle, ok := st.Keys[key]; ok {
key = keyStyle.Renderer(l.re).Render(key)
} else {
key = st.Key.Renderer(l.re).Render(key)
}
// Values may contain multiple lines, and that format
// is preserved, with each line prefixed with a " | "
// to show it's part of a collection of lines.
//
// Values may also need quoting, if not all the runes
// in the value string are "normal", like if they
// contain ANSI escape sequences.
if strings.Contains(val, "\n") {
l.b.WriteString("\n ")
l.b.WriteString(key)
l.b.WriteString(sep + "\n")
l.writeIndent(&l.b, val, indentSep, moreKeys, actualKey)
} else if !raw && needsQuoting(val) {
writeSpace(&l.b, firstKey)
l.b.WriteString(key)
l.b.WriteString(sep)
l.b.WriteString(valueStyle.Renderer(l.re).Render(fmt.Sprintf(`"%s"`,
escapeStringForOutput(val, true))))
} else {
val = valueStyle.Renderer(l.re).Render(val)
writeSpace(&l.b, firstKey)
l.b.WriteString(key)
l.b.WriteString(sep)
l.b.WriteString(val)
}
}
}
// Add a newline to the end of the log message.
l.b.WriteByte('\n')
}
golang-github-charmbracelet-log-0.4.0/text_test.go 0000664 0000000 0000000 00000026613 14663157754 0022217 0 ustar 00root root 0000000 0000000 package log
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"time"
"github.com/charmbracelet/lipgloss"
"github.com/muesli/termenv"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func _zeroTime(time.Time) time.Time {
return time.Time{}.AddDate(1, 0, 0)
}
func TestNilStyles(t *testing.T) {
st := DefaultStyles()
l := New(io.Discard)
l.SetStyles(nil)
assert.Equal(t, st, l.styles)
}
func TestTextCaller(t *testing.T) {
var buf bytes.Buffer
logger := New(&buf)
logger.SetReportCaller(true)
// We calculate the caller offset based on the caller line number.
_, 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("INFO info\n", filepath.Base(file), line+14),
msg: "info",
kvs: nil,
f: func(msg interface{}, kvs ...interface{}) {
logger.Info(msg, kvs...)
},
},
{
name: "helper caller",
expected: fmt.Sprintf("INFO info\n", filepath.Base(file), line+58),
msg: "info",
kvs: nil,
f: func(msg interface{}, kvs ...interface{}) {
logger.Helper()
logger.Info(msg, kvs...)
},
},
{
name: "nested helper caller",
expected: fmt.Sprintf("INFO info\n", filepath.Base(file), line+37),
msg: "info",
kvs: nil,
f: func(msg interface{}, kvs ...interface{}) {
fun := func(msg interface{}, kvs ...interface{}) {
logger.Helper()
logger.Info(msg, kvs...)
}
fun(msg, kvs...)
},
},
{
name: "double nested helper caller",
expected: fmt.Sprintf("INFO info\n", filepath.Base(file), line+58),
msg: "info",
kvs: nil,
f: func(msg interface{}, kvs ...interface{}) {
logger.Helper()
fun := func(msg interface{}, kvs ...interface{}) {
logger.Helper()
logger.Info(msg, kvs...)
}
fun(msg, kvs...)
},
},
}
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())
})
}
}
func TestTextLogger(t *testing.T) {
var buf bytes.Buffer
logger := New(&buf)
cases := []struct {
name string
expected string
msg string
kvs []interface{}
f func(msg interface{}, kvs ...interface{})
}{
{
name: "simple message",
expected: "INFO info\n",
msg: "info",
kvs: nil,
f: logger.Info,
},
{
name: "ignored message",
expected: "",
msg: "this is a debug message",
kvs: nil,
f: logger.Debug,
},
{
name: "message with keyvals",
expected: "INFO info key1=val1 key2=val2\n",
msg: "info",
kvs: []interface{}{"key1", "val1", "key2", "val2"},
f: logger.Info,
},
{
name: "error message with keyvals",
expected: "ERRO info key1=val1 key2=val2\n",
msg: "info",
kvs: []interface{}{"key1", "val1", "key2", "val2"},
f: logger.Error,
},
{
name: "error message with multiline",
expected: "ERRO info\n key1=\n โ val1\n โ val2\n",
msg: "info",
kvs: []interface{}{"key1", "val1\nval2"},
f: logger.Error,
},
{
name: "odd number of keyvals",
expected: "ERRO info key1=val1 key2=val2 key3=\"missing value\"\n",
msg: "info",
kvs: []interface{}{"key1", "val1", "key2", "val2", "key3"},
f: logger.Error,
},
{
name: "error field",
expected: "ERRO info key1=\"error value\"\n",
msg: "info",
kvs: []interface{}{"key1", errors.New("error value")},
f: logger.Error,
},
{
name: "struct field",
expected: "ERRO info key1={foo:bar}\n",
msg: "info",
kvs: []interface{}{"key1", struct{ foo string }{foo: "bar"}},
f: logger.Error,
},
{
name: "struct field quoted",
expected: "ERRO info key1=\"{foo:bar baz}\"\n",
msg: "info",
kvs: []interface{}{"key1", struct{ foo string }{foo: "bar baz"}},
f: logger.Error,
},
{
name: "slice of strings",
expected: "ERRO info key1=\"[foo bar]\"\n",
msg: "info",
kvs: []interface{}{"key1", []string{"foo", "bar"}},
f: logger.Error,
},
{
name: "slice of structs",
expected: "ERRO info key1=\"[{foo:bar} {foo:baz}]\"\n",
msg: "info",
kvs: []interface{}{"key1", []struct{ foo string }{{foo: "bar"}, {foo: "baz"}}},
f: logger.Error,
},
{
name: "slice of errors",
expected: "ERRO info key1=\"[error value1 error value2]\"\n",
msg: "info",
kvs: []interface{}{"key1", []error{errors.New("error value1"), errors.New("error value2")}},
f: logger.Error,
},
{
name: "map of strings",
expected: "ERRO info key1=\"map[baz:qux foo:bar]\"\n",
msg: "info",
kvs: []interface{}{"key1", map[string]string{"foo": "bar", "baz": "qux"}},
f: logger.Error,
},
}
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())
})
}
}
func TestTextHelper(t *testing.T) {
var buf bytes.Buffer
logger := New(&buf)
logger.SetReportCaller(true)
helper := func() {
logger.Helper()
logger.Info("helper func")
}
helper()
_, file, line, ok := runtime.Caller(0)
require.True(t, ok)
assert.Equal(t, fmt.Sprintf("INFO helper func\n", filepath.Base(file), line-1), buf.String())
}
func TestTextFatal(t *testing.T) {
var buf bytes.Buffer
logger := New(&buf)
logger.SetReportCaller(true)
if os.Getenv("FATAL") == "1" {
logger.Fatal("i'm dead")
logger.Fatalf("bye %s", "bye")
return
}
cmd := exec.Command(os.Args[0], "-test.run=TestTextFatal")
cmd.Env = append(os.Environ(), "FATAL=1")
err := cmd.Run()
if e, ok := err.(*exec.ExitError); ok && !e.Success() {
return
}
t.Fatalf("process ran with err %v, want exit status 1", err)
}
func TestTextValueStyles(t *testing.T) {
var buf bytes.Buffer
logger := New(&buf)
logger.SetColorProfile(termenv.ANSI256)
lipgloss.SetColorProfile(termenv.ANSI256)
st := DefaultStyles()
st.Value = lipgloss.NewStyle().Bold(true)
st.Values["key3"] = st.Value.Copy().Underline(true)
logger.SetStyles(st)
cases := []struct {
name string
expected string
msg string
kvs []interface{}
f func(msg interface{}, kvs ...interface{})
}{
{
name: "simple message",
expected: fmt.Sprintf("%s info\n", st.Levels[InfoLevel]),
msg: "info",
kvs: nil,
f: logger.Info,
},
{
name: "ignored message",
expected: "",
msg: "this is a debug message",
kvs: nil,
f: logger.Debug,
},
{
name: "message with keyvals",
expected: fmt.Sprintf(
"%s info %s%s%s %s%s%s\n",
st.Levels[InfoLevel],
st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render("val1"),
st.Key.Render("key2"), st.Separator.Render(separator), st.Value.Render("val2"),
),
msg: "info",
kvs: []interface{}{"key1", "val1", "key2", "val2"},
f: logger.Info,
},
{
name: "error message with multiline",
expected: fmt.Sprintf(
"%s info\n %s%s\n%s%s\n%s%s\n",
st.Levels[ErrorLevel],
st.Key.Render("key1"), st.Separator.Render(separator),
st.Separator.Render(indentSeparator), st.Value.Render("val1"),
st.Separator.Render(indentSeparator), st.Value.Render("val2"),
),
msg: "info",
kvs: []interface{}{"key1", "val1\nval2"},
f: logger.Error,
},
{
name: "error message with keyvals",
expected: fmt.Sprintf(
"%s info %s%s%s %s%s%s\n",
st.Levels[ErrorLevel],
st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render("val1"),
st.Key.Render("key2"), st.Separator.Render(separator), st.Value.Render("val2"),
),
msg: "info",
kvs: []interface{}{"key1", "val1", "key2", "val2"},
f: logger.Error,
},
{
name: "odd number of keyvals",
expected: fmt.Sprintf(
"%s info %s%s%s %s%s%s %s%s%s\n",
st.Levels[ErrorLevel],
st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render("val1"),
st.Key.Render("key2"), st.Separator.Render(separator), st.Value.Render("val2"),
st.Key.Render("key3"), st.Separator.Render(separator), st.Values["key3"].Render(`"missing value"`),
),
msg: "info",
kvs: []interface{}{"key1", "val1", "key2", "val2", "key3"},
f: logger.Error,
},
{
name: "error field",
expected: fmt.Sprintf(
"%s info %s%s%s\n",
st.Levels[ErrorLevel],
st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render(`"error value"`),
),
msg: "info",
kvs: []interface{}{"key1", errors.New("error value")},
f: logger.Error,
},
{
name: "struct field",
expected: fmt.Sprintf(
"%s info %s%s%s\n",
st.Levels[InfoLevel],
st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render("{foo:bar}"),
),
msg: "info",
kvs: []interface{}{"key1", struct{ foo string }{foo: "bar"}},
f: logger.Info,
},
{
name: "struct field quoted",
expected: fmt.Sprintf(
"%s info %s%s%s\n",
st.Levels[InfoLevel],
st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render(`"{foo:bar baz}"`),
),
msg: "info",
kvs: []interface{}{"key1", struct{ foo string }{foo: "bar baz"}},
f: logger.Info,
},
{
name: "slice of strings",
expected: fmt.Sprintf(
"%s info %s%s%s\n",
st.Levels[ErrorLevel],
st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render(`"[foo bar]"`),
),
msg: "info",
kvs: []interface{}{"key1", []string{"foo", "bar"}},
f: logger.Error,
},
{
name: "slice of structs",
expected: fmt.Sprintf(
"%s info %s%s%s\n",
st.Levels[ErrorLevel],
st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render(`"[{foo:bar} {foo:baz}]"`),
),
msg: "info",
kvs: []interface{}{"key1", []struct{ foo string }{{foo: "bar"}, {foo: "baz"}}},
f: logger.Error,
},
{
name: "slice of errors",
expected: fmt.Sprintf(
"%s info %s%s%s\n",
st.Levels[ErrorLevel],
st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render(`"[error value1 error value2]"`),
),
msg: "info",
kvs: []interface{}{"key1", []error{errors.New("error value1"), errors.New("error value2")}},
f: logger.Error,
},
{
name: "map of strings",
expected: fmt.Sprintf(
"%s info %s%s%s\n",
st.Levels[ErrorLevel],
st.Key.Render("key1"), st.Separator.Render(separator), st.Value.Render(`"map[baz:qux foo:bar]"`),
),
msg: "info",
kvs: []interface{}{"key1", map[string]string{"foo": "bar", "baz": "qux"}},
f: logger.Error,
},
}
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())
})
}
}
func TestColorProfile(t *testing.T) {
cases := []termenv.Profile{
termenv.Ascii,
termenv.ANSI,
termenv.ANSI256,
termenv.TrueColor,
}
l := New(io.Discard)
for _, p := range cases {
l.SetColorProfile(p)
assert.Equal(t, p, l.re.ColorProfile())
}
}
func TestCustomLevelStyle(t *testing.T) {
var buf bytes.Buffer
l := New(&buf)
st := DefaultStyles()
lvl := Level(1234)
st.Levels[lvl] = lipgloss.NewStyle().Bold(true).SetString("FUNKY")
l.SetStyles(st)
l.SetLevel(lvl)
l.Log(lvl, "foobar")
assert.Equal(t, "FUNKY foobar\n", buf.String())
}