Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move to github.com/grafana/loki-client-go #5

Merged
merged 4 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ jobs:
- name: Remove xxx_test.go files
run: rm -rf *_test.go ./examples ./images

- name: Set library version in version.go
run: sed -i 's/VERSION/${{ inputs.semver }}/g' version.go

# cleanup test dependencies
- name: Cleanup dependencies
run: go mod tidy
Expand Down
39 changes: 24 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,54 +43,60 @@ A [Loki](https://grafana.com/oss/loki/) Handler for [slog](https://pkg.go.dev/lo
## 🚀 Install

```sh
go get github.com/samber/slog-loki/v2
go get github.com/samber/slog-loki/v3
```

**Compatibility**: go >= 1.21

No breaking changes will be made to exported APIs before v3.0.0.
No breaking changes will be made to exported APIs before v4.0.0.

## 💡 Usage

GoDoc: [https://pkg.go.dev/github.com/samber/slog-loki/v2](https://pkg.go.dev/github.com/samber/slog-loki/v2)
GoDoc: [https://pkg.go.dev/github.com/samber/slog-loki/v3](https://pkg.go.dev/github.com/samber/slog-loki/v3)

### Handler options

```go
type Option struct {
// log level (default: debug)
Level slog.Leveler

// loki endpoint
Endpoint string
// log batching
BatchWait time.Duration
BatchEntriesNumber int

// loki
Client *loki.Client

// optional: customize webhook event builder
Converter Converter

// optional: see slog.HandlerOptions
AddSource bool
ReplaceAttr func(groups []string, a slog.Attr) slog.Attr
}
```

Attributes will be injected in log payload.

Attributes added to records are not accepted.

Other global parameters:

```go
slogloki.LogLevels = map[slog.Level]promtail.LogLevel{...}
slogloki.SourceKey = "source"
slogloki.ErrorKeys = []string{"error", "err"}
```

### Example

```go
import (
slogloki "github.com/samber/slog-loki/v2"
slogloki "github.com/samber/slog-loki/v3"
"log/slog"
)

func main() {
endpoint := "http://localhost:3100/api/prom/push"
// setup loki client
config, _ := loki.NewDefaultConfig("http://localhost:3100/loki/api/v1/push")
config.TenantID = "xyz"
client, _ := loki.New(config)

logger := slog.New(slogloki.Option{Level: slog.LevelDebug, Endpoint: endpoint}.NewLokiHandler())
logger := slog.New(slogloki.Option{Level: slog.LevelDebug, Client: client}.NewLokiHandler())
logger = logger.
With("environment", "dev").
With("release", "v1.0.0")
Expand All @@ -100,6 +106,9 @@ func main() {

// log user signup
logger.Info("user registration")

// stop loki client and purge buffers
client.Stop()
}
```

Expand Down
81 changes: 40 additions & 41 deletions converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,54 @@ package slogloki

import (
"fmt"
"strconv"
"strings"

"log/slog"
"strconv"

"github.com/prometheus/common/model"
slogcommon "github.com/samber/slog-common"
)

func attrToLabelMap(base string, attrs []slog.Attr, labels *map[string]string) {
for i := range attrs {
attrToValue(base, attrs[i], labels)
}
}
var SourceKey = "source"
var ErrorKeys = []string{"error", "err"}

type Converter func(addSource bool, replaceAttr func(groups []string, a slog.Attr) slog.Attr, loggerAttr []slog.Attr, groups []string, record *slog.Record) model.LabelSet

func attrToValue(base string, attr slog.Attr, labels *map[string]string) {
k := attr.Key
v := attr.Value
kind := v.Kind()

switch kind {
case slog.KindAny:
(*labels)[base+k] = slogcommon.AnyValueToString(v)
case slog.KindLogValuer:
(*labels)[base+k] = slogcommon.AnyValueToString(v)
case slog.KindGroup:
attrToLabelMap(base+k+".", v.Group(), labels)
case slog.KindInt64:
(*labels)[base+k] = fmt.Sprintf("%d", v.Int64())
case slog.KindUint64:
(*labels)[base+k] = fmt.Sprintf("%d", v.Uint64())
case slog.KindFloat64:
(*labels)[base+k] = fmt.Sprintf("%f", v.Float64())
case slog.KindString:
(*labels)[base+k] = v.String()
case slog.KindBool:
(*labels)[base+k] = strconv.FormatBool(v.Bool())
case slog.KindDuration:
(*labels)[base+k] = v.Duration().String()
case slog.KindTime:
(*labels)[base+k] = v.Time().UTC().String()
default:
(*labels)[base+k] = slogcommon.AnyValueToString(v)
func DefaultConverter(addSource bool, replaceAttr func(groups []string, a slog.Attr) slog.Attr, loggerAttr []slog.Attr, groups []string, record *slog.Record) model.LabelSet {
// aggregate all attributes
attrs := slogcommon.AppendRecordAttrsToAttrs(loggerAttr, groups, record)

// developer formatters
attrs = slogcommon.ReplaceError(attrs, ErrorKeys...)
if addSource {
attrs = append(attrs, slogcommon.Source(SourceKey, record))
}
attrs = slogcommon.ReplaceAttrs(replaceAttr, []string{}, attrs...)
attrs = append(attrs, slog.String("level", record.Level.String()))

// handler formatter
output := slogcommon.AttrsToMap(attrs...)

labelSet := model.LabelSet{}
flatten("", output, labelSet)

return labelSet
}

func mapToLabels(input map[string]string) string {
labelsList := []string{}
for k, v := range input {
labelsList = append(labelsList, fmt.Sprintf(`%s="%s"`, k, v))
// https://stackoverflow.com/questions/64419565/how-to-efficiently-flatten-a-map
func flatten(prefix string, src map[string]any, dest model.LabelSet) {
if len(prefix) > 0 {
prefix += "."
}
for k, v := range src {
switch child := v.(type) {
case map[string]any:
flatten(prefix+k, child, dest)
case []any:
for i := 0; i < len(child); i++ {
dest[model.LabelName(prefix+k+"."+strconv.Itoa(i))] = model.LabelValue(fmt.Sprintf("%v", child[i]))
}
default:
dest[model.LabelName(prefix+k)] = model.LabelValue(fmt.Sprintf("%v", v))
}
}
return fmt.Sprintf(`{%s}`, strings.Join(labelsList, ", "))
}
11 changes: 8 additions & 3 deletions example/example.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package main

import (
slogloki "github.com/samber/slog-loki/v2"
"github.com/grafana/loki-client-go/loki"
slogloki "github.com/samber/slog-loki/v3"

"log/slog"
)

func main() {
endpoint := "http://localhost:3100/api/prom/push"
config, _ := loki.NewDefaultConfig("http://localhost:3100/loki/api/v1/push")
config.TenantID = "xyz"
client, _ := loki.New(config)

logger := slog.New(slogloki.Option{Level: slog.LevelDebug, Endpoint: endpoint}.NewLokiHandler())
logger := slog.New(slogloki.Option{Level: slog.LevelDebug, Client: client}.NewLokiHandler())
logger = logger.With("release", "v1.0.0")

logger.Error("A message")

client.Stop()
}
38 changes: 36 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,18 +1,52 @@
module github.com/samber/slog-loki/v2
module github.com/samber/slog-loki/v3

go 1.21

require (
github.com/afiskon/promtail-client v0.0.0-20190305142237-506f3f921e9c
github.com/grafana/loki-client-go v0.0.0-20230116142646-e7494d0ef70c
github.com/prometheus/common v0.34.0
github.com/samber/slog-common v0.11.0
go.uber.org/goleak v1.2.1
)

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dennwc/varint v1.0.0 // indirect
github.com/go-kit/kit v0.10.0 // indirect
github.com/go-kit/log v0.2.0 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/grafana/regexp v0.0.0-20220304095617-2e8d9baf4ac2 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.12.1 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/prometheus/prometheus v0.35.0 // indirect
github.com/samber/lo v1.38.1 // indirect
github.com/stretchr/testify v1.8.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.0.0-20220325170049-de3da57026de // indirect
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb // indirect
google.golang.org/grpc v1.45.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading
Loading