formatter

package
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 16, 2025 License: MIT Imports: 9 Imported by: 0

README

slog: 属性格式化

tag Go Version GoDoc Build Status Go report Coverage Contributors License

slog 库提供通用格式化器 + 构建自定义格式化器的助手。

处理器:

通用格式化器:

自定义格式化器:

🚀 安装

go get github.com/samber/slog-formatter

兼容性: go >= 1.23

在 v2.0.0 之前,不会对导出的 API 进行破坏性更改。

⚠️ 警告:

  • 在某些情况下,你应该考虑实现 slog.LogValuer 而不是使用此库。
  • 请谨慎使用此库,日志处理可能成本很高 (!)

🚀 快速开始

以下示例有 3 个格式化器,用于匿名化数据、格式化错误和格式化用户。👇

import (
	slogformatter "github.com/samber/slog-formatter"
	"log/slog"
)

formatter1 := slogformatter.FormatByKey("very_private_data", func(v slog.Value) slog.Value {
    return slog.StringValue("***********")
})
formatter2 := slogformatter.ErrorFormatter("error")
formatter3 := slogformatter.FormatByType(func(u User) slog.Value {
	return slog.StringValue(fmt.Sprintf("%s %s", u.firstname, u.lastname))
})

logger := slog.New(
    slogformatter.NewFormatterHandler(formatter1, formatter2, formatter3)(
        slog.NewTextHandler(os.Stdout, nil),
    ),
)

err := fmt.Errorf("an error")
logger.Error("a message",
    slog.Any("very_private_data", "abcd"),
    slog.Any("user", user),
    slog.Any("err", err))

// 输出:
// time=2023-04-10T14:00:0.000000+00:00 level=ERROR msg="a message" error.message="an error" error.type="*errors.errorString" user="John doe" very_private_data="********"

💡 规范

GoDoc: https://pkg.go.dev/github.com/samber/slog-formatter

NewFormatterHandler

返回一个应用格式化器的 slog.Handler。

import (
	slogformatter "github.com/samber/slog-formatter"
	"log/slog"
)

type User struct {
	email     string
	firstname string
	lastname  string
}

formatter1 := slogformatter.FormatByKey("very_private_data", func(v slog.Value) slog.Value {
    return slog.StringValue("***********")
})
formatter2 := slogformatter.ErrorFormatter("error")
formatter3 := slogformatter.FormatByType(func(u User) slog.Value {
	return slog.StringValue(fmt.Sprintf("%s %s", u.firstname, u.lastname))
})

logger := slog.New(
    slogformatter.NewFormatterHandler(formatter1, formatter2, formatter3)(
        slog.NewTextHandler(os.StdErr, nil),
    ),
)

err := fmt.Errorf("an error")
logger.Error("a message",
    slog.Any("very_private_data", "abcd"),
    slog.Any("user", user),
    slog.Any("err", err))

// 输出:
// time=2023-04-10T14:00:0.000000+00:00 level=ERROR msg="a message" error.message="an error" error.type="*errors.errorString" user="John doe" very_private_data="********"
TimeFormatter

time.Time 转换为可读字符串。

slogformatter.NewFormatterHandler(
    slogformatter.TimeFormatter(time.DateTime, time.UTC),
)
UnixTimestampFormatter

time.Time 转换为 unix 时间戳。

slogformatter.NewFormatterHandler(
    slogformatter.UnixTimestampFormatter(time.Millisecond),
)
TimezoneConverter

time.Time 设置为不同的时区。

slogformatter.NewFormatterHandler(
    slogformatter.TimezoneConverter(time.UTC),
)
ErrorFormatter

将 Go error 转换为可读错误。

import (
	slogformatter "github.com/samber/slog-formatter"
	"log/slog"
)

logger := slog.New(
    slogformatter.NewFormatterHandler(
        slogformatter.ErrorFormatter("error"),
    )(
        slog.NewTextHandler(os.Stdout, nil),
    ),
)

err := fmt.Errorf("an error")
logger.Error("a message", slog.Any("error", err))

// 输出:
// {
//   "time":"2023-04-10T14:00:0.000000+00:00",
//   "level": "ERROR",
//   "msg": "a message",
//   "error": {
//     "message": "an error",
//     "type": "*errors.errorString"
//     "stacktrace": "main.main()\n\t/Users/samber/src/github.com/samber/slog-formatter/example/example.go:108 +0x1c\n"
//   }
// }
HTTPRequestFormatter 和 HTTPResponseFormatter

将 *http.Request 和 *http.Response 转换为可读对象。

import (
	slogformatter "github.com/samber/slog-formatter"
	"log/slog"
)

logger := slog.New(
    slogformatter.NewFormatterHandler(
        slogformatter.HTTPRequestFormatter(false),
        slogformatter.HTTPResponseFormatter(false),
    )(
        slog.NewJSONHandler(os.Stdout, nil),
    ),
)

req, _ := http.NewRequest(http.MethodGet, "https://api.screeb.app", nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-TOKEN", "1234567890")

res, _ := http.DefaultClient.Do(req)

logger.Error("a message",
    slog.Any("request", req),
    slog.Any("response", res))
PIIFormatter

隐藏私人个人身份信息 (PII)。

ID 保持原样。长度超过 5 个字符的值有明文前缀。

import (
	slogformatter "github.com/samber/slog-formatter"
	"log/slog"
)

logger := slog.New(
    slogformatter.NewFormatterHandler(
        slogformatter.PIIFormatter("user"),
    )(
        slog.NewTextHandler(os.Stdout, nil),
    ),
)

logger.
    With(
        slog.Group(
            "user",
            slog.String("id", "bd57ffbd-8858-4cc4-a93b-426cef16de61"),
            slog.String("email", "foobar@example.com"),
            slog.Group(
                "address",
                slog.String("street", "1st street"),
                slog.String("city", "New York"),
                slog.String("country", "USA"),
                slog.Int("zip", 12345),
            ),
        ),
    ).
    Error("an error")

// 输出:
// {
//   "time":"2023-04-10T14:00:0.000000+00:00",
//   "level": "ERROR",
//   "msg": "an error",
//   "user": {
//     "id": "bd57ffbd-8858-4cc4-a93b-426cef16de61",
//     "email": "foob*******",
//     "address": {
//       "street": "1st *******",
//       "city": "New *******",
//       "country": "*******",
//       "zip": "*******"
//     }
//   }
// }
IPAddressFormatter

将 IP 地址转换为 "********"。

import (
	slogformatter "github.com/samber/slog-formatter"
	"log/slog"
)

logger := slog.New(
    slogformatter.NewFormatterHandler(
        slogformatter.IPAddressFormatter("ip_address"),
    )(
        slog.NewTextHandler(os.Stdout, nil),
    ),
)

logger.
    With("ip_address", "1.2.3.4").
    Error("an error")

// 输出:
// {
//   "time":"2023-04-10T14:00:0.000000+00:00",
//   "level": "ERROR",
//   "msg": "an error",
//   "ip_address": "*******",
// }
FlattenFormatterMiddleware

递归展平属性的格式化器中间件。

import (
	slogformatter "github.com/samber/slog-formatter"
	slogmulti "github.com/samber/slog-multi"
	"log/slog"
)

logger := slog.New(
    slogmulti.
        Pipe(slogformatter.FlattenFormatterMiddlewareOptions{Separator: ".", Prefix: "attrs", IgnorePath: false}.NewFlattenFormatterMiddlewareOptions()).
        Handler(slog.NewJSONHandler(os.Stdout, nil)),
)

logger.
    With("email", "samuel@acme.org").
    With("environment", "dev").
    WithGroup("group1").
    With("hello", "world").
    WithGroup("group2").
    With("hello", "world").
    Error("A message", "foo", "bar")

// 输出:
// {
//   "time": "2023-05-20T22:14:55.857065+02:00",
//   "level": "ERROR",
//   "msg": "A message",
//   "attrs.email": "samuel@acme.org",
//   "attrs.environment": "dev",
//   "attrs.group1.hello": "world",
//   "attrs.group1.group2.hello": "world",
//   "foo": "bar"
// }
Format

将每个属性传递到格式化器。

slogformatter.NewFormatterHandler(
    slogformatter.Format(func(groups []string, key string, value slog.Value) slog.Value {
        // 隐藏 "user" 组下的所有内容
        if lo.Contains(groups, "user") {
            return slog.StringValue("****")
        }

        return value
    }),
)
FormatByKind

将匹配 slog.Kind 的属性传递到格式化器。

slogformatter.NewFormatterHandler(
    slogformatter.FormatByKind(slog.KindDuration, func(value slog.Value) slog.Value {
        return ...
    }),
)
FormatByType

将匹配泛型类型的属性传递到格式化器。

slogformatter.NewFormatterHandler(
    // 格式化自定义错误类型
    slogformatter.FormatByType[*customError](func(err *customError) slog.Value {
        return slog.GroupValue(
            slog.Int("code", err.code),
            slog.String("message", err.msg),
        )
    }),
    // 格式化其他错误
    slogformatter.FormatByType[error](func(err error) slog.Value {
        return slog.GroupValue(
            slog.Int("code", err.Error()),
            slog.String("type", reflect.TypeOf(err).String()),
        )
    }),
)

⚠️ 在可能的情况下考虑实现 slog.LogValuer:

type customError struct {
    ...
}

func (customError) Error() string {
    ...
}

// 实现 slog.LogValuer
func (customError) LogValue() slog.Value {
	return slog.StringValue(...)
}
FormatByKey

将匹配键的属性传递到格式化器。

slogformatter.NewFormatterHandler(
    slogformatter.FormatByKey("abcd", func(value slog.Value) slog.Value {
        return ...
    }),
)
FormatByFieldType

将同时匹配键和泛型类型的属性传递到格式化器。

slogformatter.NewFormatterHandler(
    slogformatter.FormatByFieldType[User]("user", func(u User) slog.Value {
        return ...
    }),
)
FormatByGroup

将组下的属性传递到格式化器。

slogformatter.NewFormatterHandler(
    slogformatter.FormatByGroup([]{"user", "address"}, func(attr []slog.Attr) slog.Value {
        return ...
    }),
)
FormatByGroupKey

将组下匹配键的属性传递到格式化器。

slogformatter.NewFormatterHandler(
    slogformatter.FormatByGroupKey([]{"user", "address"}, "country", func(value slog.Value) slog.Value {
        return ...
    }),
)
FormatByGroupKeyType

将组下匹配键且匹配泛型类型的属性传递到格式化器。

slogformatter.NewFormatterHandler(
    slogformatter.FormatByGroupKeyType[string]([]{"user", "address"}, "country", func(value string) slog.Value {
        return ...
    }),
)

🤝 贡献

不要犹豫 ;)

# 安装一些开发依赖
make tools

# 运行测试
make test
# 或
make watch-test

👤 贡献者

贡献者

💫 表达你的支持

如果这个项目对你有帮助,请给一个 ⭐️!

GitHub Sponsors

📝 许可证

版权所有 © 2023 Samuel Berthe

本项目采用 MIT 许可证。

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Formatter

type Formatter func(groups []string, attr slog.Attr) (slog.Value, bool)

func ErrorFormatter

func ErrorFormatter(fieldName string) Formatter

ErrorFormatter transforms a go error into a readable error.

Example:

err := reader.Close()
err = fmt.Errorf("could not close reader: %v", err)
logger.With("error", reader.Close()).Log("error")

passed to ErrorFormatter("error"), will be transformed into:

"error": {
  "message": "could not close reader: file already closed",
  "type": "*io.ErrClosedPipe"
}

func Format

func Format[T any](formatter func([]string, string, slog.Value) slog.Value) Formatter

Format pass every attributes into a formatter.

func FormatByFieldType

func FormatByFieldType[T any](key string, formatter func(T) slog.Value) Formatter

FormatByFieldType pass attributes matching both key and generic type into a formatter.

func FormatByGroup

func FormatByGroup(targetGroup []string, formatter func([]slog.Attr) slog.Value) Formatter

FormatByGroup pass attributes under a group into a formatter.

func FormatByGroupKey

func FormatByGroupKey(targetGroup []string, key string, formatter func(slog.Value) slog.Value) Formatter

FormatByGroupKey pass attributes under a group and matching key, into a formatter.

func FormatByGroupKeyType

func FormatByGroupKeyType[T any](targetGroup []string, key string, formatter func(T) slog.Value) Formatter

FormatByGroupKeyType pass attributes under a group, matching key and matching a generic type, into a formatter.

func FormatByKey

func FormatByKey(key string, formatter func(slog.Value) slog.Value) Formatter

FormatByKey pass attributes matching key into a formatter.

func FormatByKind

func FormatByKind(kind slog.Kind, formatter func(slog.Value) slog.Value) Formatter

FormatByKind pass attributes matching `slog.Kind` into a formatter.

func FormatByType

func FormatByType[T any](formatter func(T) slog.Value) Formatter

FormatByType pass attributes matching generic type into a formatter.

func HTTPRequestFormatter

func HTTPRequestFormatter(ignoreHeaders bool) Formatter

HTTPRequestFormatter transforms a *http.Request into a readable object.

func HTTPResponseFormatter

func HTTPResponseFormatter(ignoreHeaders bool) Formatter

HTTPResponseFormatter transforms a *http.Response into a readable object.

func IPAddressFormatter

func IPAddressFormatter(key string) Formatter

IPAddressFormatter transforms an IP address into "********".

Example:

"context": {
  "ip_address": "bd57ffbd-8858-4cc4-a93b-426cef16de61"
}

passed to IPAddressFormatter("ip_address"), will be transformed into:

"context": {
  "ip_address": "********",
}

func PIIFormatter

func PIIFormatter(key string) Formatter

PIIFormatter transforms any value under provided key into "********". IDs are kept as is.

Example:

  "user": {
    "id": "bd57ffbd-8858-4cc4-a93b-426cef16de61",
    "email": "foobar@example.com",
    "address": {
      "street": "1st street",
	     "city": "New York",
      "country": USA",
	     "zip": 123456
    }
  }

passed to PIIFormatter("user"), will be transformed into:

  "user": {
    "id": "bd57ffbd-8858-4cc4-a93b-426cef16de61",
    "email": "foob********",
    "address": {
      "street": "1st *******",
	     "city": "New *******",
  	   "country": "*******",
	     "zip": "*******"
    }
  }

func TimeFormatter

func TimeFormatter(timeFormat string, location *time.Location) Formatter

TimeFormatter transforms a `time.Time` into a readable string.

func TimezoneConverter

func TimezoneConverter(location *time.Location) Formatter

TimezoneConverter set a `time.Time` to a different timezone.

func UnixTimestampFormatter

func UnixTimestampFormatter(precision time.Duration) Formatter

UnixTimestampFormatter transforms a `time.Time` into a unix timestamp.

type FormatterAdapter

type FormatterAdapter struct {
	*modules.BaseModule
	// contains filtered or unexported fields
}

FormatterAdapter 格式化器模块适配器

func NewFormatterAdapter

func NewFormatterAdapter() *FormatterAdapter

NewFormatterAdapter 创建格式化器适配器

func (*FormatterAdapter) Configure

func (f *FormatterAdapter) Configure(config modules.Config) error

Configure 配置格式化器模块

func (*FormatterAdapter) FormatterFunctions added in v0.1.2

func (f *FormatterAdapter) FormatterFunctions() []func([]string, slog.Attr) (slog.Value, bool)

FormatterFunctions 实现 modules.FormatterProvider,避免反射与 interface{} 转换。

func (*FormatterAdapter) GetFormatters

func (f *FormatterAdapter) GetFormatters() interface{}

GetFormatters 获取格式化器列表(返回兼容的函数类型)

type LogValuerFunc

type LogValuerFunc func(any) (slog.Value, bool)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL