Compare commits

..

No commits in common. "v3" and "v3.11.15" have entirely different histories.
v3 ... v3.11.15

42 changed files with 162 additions and 515 deletions

View File

@ -1,51 +0,0 @@
name: coverage
on:
push:
branches: [ main, v3, v4 ]
pull_request:
branches: [ main, v3, v4 ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/checkout@v4
with:
filter: 'blob:none'
- name: setup go
uses: actions/setup-go@v5
with:
cache-dependency-path: "**/*.sum"
go-version: 'stable'
- name: test coverage
run: |
go test -v -cover ./... -coverprofile coverage.out -coverpkg ./...
go tool cover -func coverage.out -o coverage.out
- name: coverage badge
uses: tj-actions/coverage-badge-go@v1
with:
green: 80
filename: coverage.out
- uses: stefanzweifel/git-auto-commit-action@v4
id: auto-commit-action
with:
commit_message: Apply Code Coverage Badge
skip_fetch: true
skip_checkout: true
file_pattern: ./README.md
- name: Push Changes
if: steps.auto-commit-action.outputs.changes_detected == 'true'
uses: ad-m/github-push-action@master
with:
github_token: ${{ github.token }}
branch: ${{ github.ref }}

View File

@ -1,5 +1,4 @@
# Micro # Micro [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Doc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/unistack-org/micro/v3?tab=overview) [![Status](https://git.unistack.org/unistack-org/micro/actions/workflows/job_tests.yml/badge.svg?branch=v3)](https://git.unistack.org/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Av3+event%3Apush) [![Lint](https://goreportcard.com/badge/go.unistack.org/micro/v3)](https://goreportcard.com/report/go.unistack.org/micro/v3)
[![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![Doc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/unistack-org/micro/v3?tab=overview) [![Status](https://git.unistack.org/unistack-org/micro/actions/workflows/job_tests.yml/badge.svg?branch=v3)](https://git.unistack.org/unistack-org/micro/actions?query=workflow%3Abuild+branch%3Av3+event%3Apush) [![Lint](https://goreportcard.com/badge/go.unistack.org/micro/v3)](https://goreportcard.com/report/go.unistack.org/micro/v3)![Coverage](https://img.shields.io/badge/Coverage-44.9%25-yellow)
Micro is a standard library for microservices. Micro is a standard library for microservices.

View File

@ -15,15 +15,6 @@ func FromContext(ctx context.Context) (Broker, bool) {
return c, ok return c, ok
} }
// MustContext returns broker from passed context
func MustContext(ctx context.Context) Broker {
b, ok := FromContext(ctx)
if !ok {
panic("missing broker")
}
return b
}
// NewContext savess broker in context // NewContext savess broker in context
func NewContext(ctx context.Context, s Broker) context.Context { func NewContext(ctx context.Context, s Broker) context.Context {
if ctx == nil { if ctx == nil {

View File

@ -109,7 +109,7 @@ func (m *memoryBroker) Init(opts ...broker.Option) error {
m.funcSubscribe = m.fnSubscribe m.funcSubscribe = m.fnSubscribe
m.funcBatchSubscribe = m.fnBatchSubscribe m.funcBatchSubscribe = m.fnBatchSubscribe
m.opts.Hooks.EachPrev(func(hook options.Hook) { m.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) { switch h := hook.(type) {
case broker.HookPublish: case broker.HookPublish:
m.funcPublish = h(m.funcPublish) m.funcPublish = h(m.funcPublish)

View File

@ -59,7 +59,7 @@ func (b *NoopBroker) Init(opts ...Option) error {
b.funcSubscribe = b.fnSubscribe b.funcSubscribe = b.fnSubscribe
b.funcBatchSubscribe = b.fnBatchSubscribe b.funcBatchSubscribe = b.fnBatchSubscribe
b.opts.Hooks.EachPrev(func(hook options.Hook) { b.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) { switch h := hook.(type) {
case HookPublish: case HookPublish:
b.funcPublish = h(b.funcPublish) b.funcPublish = h(b.funcPublish)

View File

@ -15,15 +15,6 @@ func FromContext(ctx context.Context) (Client, bool) {
return c, ok return c, ok
} }
// MustContext get client from context
func MustContext(ctx context.Context) Client {
c, ok := FromContext(ctx)
if !ok {
panic("missing client")
}
return c
}
// NewContext put client in context // NewContext put client in context
func NewContext(ctx context.Context, c Client) context.Context { func NewContext(ctx context.Context, c Client) context.Context {
if ctx == nil { if ctx == nil {

View File

@ -194,7 +194,7 @@ func (n *noopClient) Init(opts ...Option) error {
n.funcPublish = n.fnPublish n.funcPublish = n.fnPublish
n.funcBatchPublish = n.fnBatchPublish n.funcBatchPublish = n.fnBatchPublish
n.opts.Hooks.EachPrev(func(hook options.Hook) { n.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) { switch h := hook.(type) {
case HookCall: case HookCall:
n.funcCall = h(n.funcCall) n.funcCall = h(n.funcCall)

View File

@ -15,15 +15,6 @@ func FromContext(ctx context.Context) (Codec, bool) {
return c, ok return c, ok
} }
// MustContext returns codec from context
func MustContext(ctx context.Context) Codec {
c, ok := FromContext(ctx)
if !ok {
panic("missing codec")
}
return c
}
// NewContext put codec in context // NewContext put codec in context
func NewContext(ctx context.Context, c Codec) context.Context { func NewContext(ctx context.Context, c Codec) context.Context {
if ctx == nil { if ctx == nil {

View File

@ -15,15 +15,6 @@ func FromContext(ctx context.Context) (Config, bool) {
return c, ok return c, ok
} }
// MustContext returns store from context
func MustContext(ctx context.Context) Config {
c, ok := FromContext(ctx)
if !ok {
panic("missing config")
}
return c
}
// NewContext put store in context // NewContext put store in context
func NewContext(ctx context.Context, c Config) context.Context { func NewContext(ctx context.Context, c Config) context.Context {
if ctx == nil { if ctx == nil {

View File

@ -37,7 +37,7 @@ func (c *defaultConfig) Init(opts ...Option) error {
c.funcLoad = c.fnLoad c.funcLoad = c.fnLoad
c.funcSave = c.fnSave c.funcSave = c.fnSave
c.opts.Hooks.EachPrev(func(hook options.Hook) { c.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) { switch h := hook.(type) {
case HookLoad: case HookLoad:
c.funcLoad = h(c.funcLoad) c.funcLoad = h(c.funcLoad)

View File

@ -8,6 +8,7 @@ import (
"time" "time"
"go.unistack.org/micro/v3/config" "go.unistack.org/micro/v3/config"
mid "go.unistack.org/micro/v3/util/id"
mtime "go.unistack.org/micro/v3/util/time" mtime "go.unistack.org/micro/v3/util/time"
) )
@ -114,6 +115,8 @@ func TestDefault(t *testing.T) {
if conf.IDValue == "" { if conf.IDValue == "" {
t.Fatalf("id value empty") t.Fatalf("id value empty")
} else if len(conf.IDValue) != mid.DefaultSize {
t.Fatalf("id value invalid: %s", conf.IDValue)
} }
_ = conf _ = conf
// t.Logf("%#+v\n", conf) // t.Logf("%#+v\n", conf)

View File

@ -15,15 +15,6 @@ func FromContext(ctx context.Context) (Flow, bool) {
return c, ok return c, ok
} }
// MustContext returns Flow from context
func MustContext(ctx context.Context) Flow {
f, ok := FromContext(ctx)
if !ok {
panic("missing flow")
}
return f
}
// NewContext stores Flow to context // NewContext stores Flow to context
func NewContext(ctx context.Context, f Flow) context.Context { func NewContext(ctx context.Context, f Flow) context.Context {
if ctx == nil { if ctx == nil {

7
go.mod
View File

@ -1,14 +1,14 @@
module go.unistack.org/micro/v3 module go.unistack.org/micro/v3
go 1.23.4 go 1.22.0
toolchain go1.23.4
require ( require (
dario.cat/mergo v1.0.1 dario.cat/mergo v1.0.1
github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/KimMachineGun/automemlimit v0.6.1 github.com/KimMachineGun/automemlimit v0.6.1
github.com/ash3in/uuidv8 v1.0.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/matoous/go-nanoid v1.5.1
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5 github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5
go.uber.org/automaxprocs v1.6.0 go.uber.org/automaxprocs v1.6.0
@ -31,7 +31,6 @@ require (
github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/stretchr/testify v1.10.0 // indirect github.com/stretchr/testify v1.10.0 // indirect
go.uber.org/goleak v1.3.0 // indirect go.uber.org/goleak v1.3.0 // indirect

8
go.sum
View File

@ -4,8 +4,6 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20O
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/KimMachineGun/automemlimit v0.6.1 h1:ILa9j1onAAMadBsyyUJv5cack8Y1WT26yLj/V+ulKp8= github.com/KimMachineGun/automemlimit v0.6.1 h1:ILa9j1onAAMadBsyyUJv5cack8Y1WT26yLj/V+ulKp8=
github.com/KimMachineGun/automemlimit v0.6.1/go.mod h1:T7xYht7B8r6AG/AqFcUdc7fzd2bIdBKmepfP2S1svPY= github.com/KimMachineGun/automemlimit v0.6.1/go.mod h1:T7xYht7B8r6AG/AqFcUdc7fzd2bIdBKmepfP2S1svPY=
github.com/ash3in/uuidv8 v1.0.1 h1:dIq1XRkWT8lGA7N5s7WRTB4V3k49WTBLvILz7aCLp80=
github.com/ash3in/uuidv8 v1.0.1/go.mod h1:EoyUgCtxNBnrnpc9efw5rVN1cQ+LFGCoJiFuD6maOMw=
github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok= github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok=
github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE= github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE=
github.com/containerd/cgroups/v3 v3.0.4 h1:2fs7l3P0Qxb1nKWuJNFiwhp2CqiKzho71DQkDrHJIo4= github.com/containerd/cgroups/v3 v3.0.4 h1:2fs7l3P0Qxb1nKWuJNFiwhp2CqiKzho71DQkDrHJIo4=
@ -42,8 +40,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/matoous/go-nanoid v1.5.1 h1:aCjdvTyO9LLnTIi0fgdXhOPPvOHjpXN6Ik9DaNjIct4=
github.com/matoous/go-nanoid v1.5.1/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
@ -61,8 +57,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5 h1:G/FZtUu7a6NTWl3KUHMV9jkLAh/Rvtf03NWMHaEDl+E= github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5 h1:G/FZtUu7a6NTWl3KUHMV9jkLAh/Rvtf03NWMHaEDl+E=
github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I= github.com/silas/dag v0.0.0-20220518035006-a7e85ada93c5/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=

View File

@ -4,6 +4,17 @@ import "context"
type loggerKey struct{} type loggerKey struct{}
// MustContext returns logger from passed context or DefaultLogger if empty
func MustContext(ctx context.Context) Logger {
if ctx == nil {
return DefaultLogger.Clone()
}
if l, ok := ctx.Value(loggerKey{}).(Logger); ok && l != nil {
return l
}
return DefaultLogger.Clone()
}
// FromContext returns logger from passed context // FromContext returns logger from passed context
func FromContext(ctx context.Context) (Logger, bool) { func FromContext(ctx context.Context) (Logger, bool) {
if ctx == nil { if ctx == nil {
@ -13,15 +24,6 @@ func FromContext(ctx context.Context) (Logger, bool) {
return l, ok return l, ok
} }
// MustContext returns logger from passed context or DefaultLogger if empty
func MustContext(ctx context.Context) Logger {
l, ok := FromContext(ctx)
if !ok {
panic("missing logger")
}
return l
}
// NewContext stores logger into passed context // NewContext stores logger into passed context
func NewContext(ctx context.Context, l Logger) context.Context { func NewContext(ctx context.Context, l Logger) context.Context {
if ctx == nil { if ctx == nil {

View File

@ -30,6 +30,7 @@ type Options struct {
StacktraceKey string StacktraceKey string
// Name holds the logger name // Name holds the logger name
Name string Name string
// Out holds the output writer // Out holds the output writer
Out io.Writer Out io.Writer
// Context holds exernal options // Context holds exernal options
@ -38,10 +39,12 @@ type Options struct {
Meter meter.Meter Meter meter.Meter
// TimeFunc used to obtain current time // TimeFunc used to obtain current time
TimeFunc func() time.Time TimeFunc func() time.Time
// Fields holds additional metadata // Fields holds additional metadata
Fields []interface{} Fields []interface{}
// ContextAttrFuncs contains funcs that executed before log func on context // ContextAttrFuncs contains funcs that executed before log func on context
ContextAttrFuncs []ContextAttrFunc ContextAttrFuncs []ContextAttrFunc
// callerSkipCount number of frmaes to skip // callerSkipCount number of frmaes to skip
CallerSkipCount int CallerSkipCount int
// The logging level the logger should log // The logging level the logger should log
@ -103,9 +106,7 @@ func WithAddFields(fields ...interface{}) Option {
} }
} }
} }
if len(fields) > 0 { o.Fields = append(o.Fields, fields...)
o.Fields = append(o.Fields, fields...)
}
} else { } else {
o.Fields = append(o.Fields, fields...) o.Fields = append(o.Fields, fields...)
} }

View File

@ -2,7 +2,6 @@ package slog
import ( import (
"context" "context"
"io"
"log/slog" "log/slog"
"os" "os"
"reflect" "reflect"
@ -11,7 +10,6 @@ import (
"strconv" "strconv"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time"
"go.unistack.org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/semconv" "go.unistack.org/micro/v3/semconv"
@ -144,7 +142,6 @@ func (s *slogLogger) Fields(fields ...interface{}) logger.Logger {
s.mu.RUnlock() s.mu.RUnlock()
l := &slogLogger{opts: options} l := &slogLogger{opts: options}
logger.WithAddFields(fields...)(&l.opts)
if len(options.ContextAttrFuncs) == 0 { if len(options.ContextAttrFuncs) == 0 {
options.ContextAttrFuncs = logger.DefaultContextAttrFuncs options.ContextAttrFuncs = logger.DefaultContextAttrFuncs
@ -226,10 +223,6 @@ func (s *slogLogger) Error(ctx context.Context, msg string, attrs ...interface{}
func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) { func (s *slogLogger) Fatal(ctx context.Context, msg string, attrs ...interface{}) {
s.printLog(ctx, logger.FatalLevel, msg, attrs...) s.printLog(ctx, logger.FatalLevel, msg, attrs...)
if closer, ok := s.opts.Out.(io.Closer); ok {
closer.Close()
}
time.Sleep(1 * time.Second)
os.Exit(1) os.Exit(1)
} }
@ -276,7 +269,7 @@ func (s *slogLogger) printLog(ctx context.Context, lvl logger.Level, msg string,
} }
} }
if (s.opts.AddStacktrace || lvl == logger.FatalLevel) || (s.opts.AddStacktrace && lvl == logger.ErrorLevel) { if s.opts.AddStacktrace && lvl == logger.ErrorLevel {
stackInfo := make([]byte, 1024*1024) stackInfo := make([]byte, 1024*1024)
if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 { if stackSize := runtime.Stack(stackInfo, false); stackSize > 0 {
traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1) traceLines := reTrace.Split(string(stackInfo[:stackSize]), -1)

View File

@ -15,47 +15,7 @@ import (
"go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/metadata"
) )
func TestStacktrace(t *testing.T) { func TestWithDuplicate(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.ErrorLevel), logger.WithOutput(buf),
WithHandlerFunc(slog.NewTextHandler),
logger.WithAddStacktrace(true),
)
if err := l.Init(logger.WithFields("key1", "val1")); err != nil {
t.Fatal(err)
}
l.Error(ctx, "msg1", errors.New("err"))
if !bytes.Contains(buf.Bytes(), []byte(`slog_test.go:29`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestWithFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.InfoLevel), logger.WithOutput(buf),
WithHandlerFunc(slog.NewTextHandler),
logger.WithDedupKeys(true),
)
if err := l.Init(logger.WithFields("key1", "val1")); err != nil {
t.Fatal(err)
}
l.Info(ctx, "msg1")
l = l.Fields("key1", "val2")
l.Info(ctx, "msg2")
if !bytes.Contains(buf.Bytes(), []byte(`msg=msg2 key1=val1`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestWithDedupKeysWithAddFields(t *testing.T) {
ctx := context.TODO() ctx := context.TODO()
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.InfoLevel), logger.WithOutput(buf), l := NewLogger(logger.WithLevel(logger.InfoLevel), logger.WithOutput(buf),

View File

@ -24,17 +24,6 @@ func FromIncomingContext(ctx context.Context) (Metadata, bool) {
return md.md, ok return md.md, ok
} }
// MustIncomingContext returns metadata from incoming ctx
// returned metadata shoud not be modified or race condition happens.
// If metadata not exists panics.
func MustIncomingContext(ctx context.Context) Metadata {
md, ok := FromIncomingContext(ctx)
if !ok {
panic("missing metadata")
}
return md
}
// FromOutgoingContext returns metadata from outgoing ctx // FromOutgoingContext returns metadata from outgoing ctx
// returned metadata shoud not be modified or race condition happens // returned metadata shoud not be modified or race condition happens
func FromOutgoingContext(ctx context.Context) (Metadata, bool) { func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
@ -48,17 +37,6 @@ func FromOutgoingContext(ctx context.Context) (Metadata, bool) {
return md.md, ok return md.md, ok
} }
// MustOutgoingContext returns metadata from outgoing ctx
// returned metadata shoud not be modified or race condition happens.
// If metadata not exists panics.
func MustOutgoingContext(ctx context.Context) Metadata {
md, ok := FromOutgoingContext(ctx)
if !ok {
panic("missing metadata")
}
return md
}
// FromContext returns metadata from the given context // FromContext returns metadata from the given context
// returned metadata shoud not be modified or race condition happens // returned metadata shoud not be modified or race condition happens
func FromContext(ctx context.Context) (Metadata, bool) { func FromContext(ctx context.Context) (Metadata, bool) {
@ -72,16 +50,6 @@ func FromContext(ctx context.Context) (Metadata, bool) {
return md.md, ok return md.md, ok
} }
// MustContext returns metadata from the given context
// returned metadata shoud not be modified or race condition happens
func MustContext(ctx context.Context) Metadata {
md, ok := FromContext(ctx)
if !ok {
panic("missing metadata")
}
return md
}
// NewContext creates a new context with the given metadata // NewContext creates a new context with the given metadata
func NewContext(ctx context.Context, md Metadata) context.Context { func NewContext(ctx context.Context, md Metadata) context.Context {
if ctx == nil { if ctx == nil {
@ -143,7 +111,7 @@ func AppendOutgoingContext(ctx context.Context, kv ...string) context.Context {
for k, v := range md { for k, v := range md {
omd.Set(k, v) omd.Set(k, v)
} }
return ctx return NewOutgoingContext(ctx, omd)
} }
// AppendIncomingContext apends new md to context // AppendIncomingContext apends new md to context
@ -159,21 +127,5 @@ func AppendIncomingContext(ctx context.Context, kv ...string) context.Context {
for k, v := range md { for k, v := range md {
omd.Set(k, v) omd.Set(k, v)
} }
return ctx return NewIncomingContext(ctx, omd)
}
// AppendContext apends new md to context
func AppendContext(ctx context.Context, kv ...string) context.Context {
md, ok := Pairs(kv...)
if !ok {
return ctx
}
omd, ok := FromContext(ctx)
if !ok {
return NewContext(ctx, md)
}
for k, v := range md {
omd.Set(k, v)
}
return ctx
} }

View File

@ -67,14 +67,6 @@ func (md Metadata) Iterator() *Iterator {
return iter return iter
} }
func (md Metadata) MustGet(key string) string {
val, ok := md.Get(key)
if !ok {
panic("missing metadata key")
}
return val
}
// Get returns value from metadata by key // Get returns value from metadata by key
func (md Metadata) Get(key string) (string, bool) { func (md Metadata) Get(key string) (string, bool) {
// fast path // fast path
@ -111,18 +103,11 @@ func (md Metadata) Del(keys ...string) {
} }
} }
// Copy makes a copy of the metadata
func (md Metadata) CopyTo(dst Metadata) {
for k, v := range md {
dst[k] = v
}
}
// Copy makes a copy of the metadata // Copy makes a copy of the metadata
func Copy(md Metadata, exclude ...string) Metadata { func Copy(md Metadata, exclude ...string) Metadata {
nmd := New(len(md)) nmd := New(len(md))
for k, v := range md { for key, val := range md {
nmd[k] = v nmd.Set(key, val)
} }
nmd.Del(exclude...) nmd.Del(exclude...)
return nmd return nmd
@ -146,7 +131,7 @@ func Merge(omd Metadata, mmd Metadata, overwrite bool) Metadata {
case ok && !overwrite: case ok && !overwrite:
continue continue
case val != "": case val != "":
nmd[key] = val nmd.Set(key, val)
case ok && val == "": case ok && val == "":
nmd.Del(key) nmd.Del(key)
} }
@ -160,8 +145,6 @@ func Pairs(kv ...string) (Metadata, bool) {
return nil, false return nil, false
} }
md := New(len(kv) / 2) md := New(len(kv) / 2)
for idx := 0; idx < len(kv); idx += 2 { md.Set(kv...)
md[kv[idx]] = kv[idx+1]
}
return md, true return md, true
} }

View File

@ -32,8 +32,8 @@ func TestMultipleUsage(t *testing.T) {
m.Del("key1_3") m.Del("key1_3")
return ctx return ctx
}(ctx) }(ctx)
_ = imd t.Logf("imd %#+v", imd)
_ = omd t.Logf("omd %#+v", omd)
} }
func TestMetadataSetMultiple(t *testing.T) { func TestMetadataSetMultiple(t *testing.T) {

View File

@ -15,15 +15,6 @@ func FromContext(ctx context.Context) (Meter, bool) {
return c, ok return c, ok
} }
// MustContext get meter from context
func MustContext(ctx context.Context) Meter {
m, ok := FromContext(ctx)
if !ok {
panic("missing meter")
}
return m
}
// NewContext put meter in context // NewContext put meter in context
func NewContext(ctx context.Context, c Meter) context.Context { func NewContext(ctx context.Context, c Meter) context.Context {
if ctx == nil { if ctx == nil {

View File

@ -119,7 +119,7 @@ func Tracer(t tracer.Tracer) Option {
// NewOptions returns network default options // NewOptions returns network default options
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
ID: id.MustNew(), ID: id.Must(),
Name: "go.micro", Name: "go.micro",
Address: ":0", Address: ":0",
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,

View File

@ -164,7 +164,7 @@ func DialWait(b bool) DialOption {
// NewOptions returns router default options with filled values // NewOptions returns router default options with filled values
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
ID: id.MustNew(), ID: id.Must(),
Address: DefaultAddress, Address: DefaultAddress,
Token: DefaultToken, Token: DefaultToken,
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,

View File

@ -15,15 +15,6 @@ func FromContext(ctx context.Context) (Register, bool) {
return c, ok return c, ok
} }
// MustContext get register from context
func MustContext(ctx context.Context) Register {
r, ok := FromContext(ctx)
if !ok {
panic("missing register")
}
return r
}
// NewContext put register in context // NewContext put register in context
func NewContext(ctx context.Context, c Register) context.Context { func NewContext(ctx context.Context, c Register) context.Context {
if ctx == nil { if ctx == nil {

View File

@ -15,15 +15,6 @@ func FromContext(ctx context.Context) (Router, bool) {
return c, ok return c, ok
} }
// MustContext get router from context
func MustContext(ctx context.Context) Router {
r, ok := FromContext(ctx)
if !ok {
panic("missing router")
}
return r
}
// NewContext put router in context // NewContext put router in context
func NewContext(ctx context.Context, c Router) context.Context { func NewContext(ctx context.Context, c Router) context.Context {
if ctx == nil { if ctx == nil {

View File

@ -80,7 +80,7 @@ func Name(n string) Option {
// NewOptions returns router default options // NewOptions returns router default options
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
ID: id.MustNew(), ID: id.Must(),
Network: DefaultNetwork, Network: DefaultNetwork,
Register: register.DefaultRegister, Register: register.DefaultRegister,
Logger: logger.DefaultLogger, Logger: logger.DefaultLogger,

View File

@ -1,18 +0,0 @@
package semconv
var (
// HeaderTopic is the header name that contains topic name
HeaderTopic = "Micro-Topic"
// HeaderContentType specifies content type of message
HeaderContentType = "Content-Type"
// HeaderEndpoint specifies endpoint in service
HeaderEndpoint = "Micro-Endpoint"
// HeaderService specifies service
HeaderService = "Micro-Service"
// HeaderTimeout specifies timeout of operation
HeaderTimeout = "Micro-Timeout"
// HeaderAuthorization specifies Authorization header
HeaderAuthorization = "Authorization"
// HeaderXRequestID specifies request id
HeaderXRequestID = "X-Request-Id"
)

View File

@ -15,15 +15,6 @@ func FromContext(ctx context.Context) (Server, bool) {
return c, ok return c, ok
} }
// MustContext returns Server from context
func MustContext(ctx context.Context) Server {
s, ok := FromContext(ctx)
if !ok {
panic("missing server")
}
return s
}
// NewContext stores Server to context // NewContext stores Server to context
func NewContext(ctx context.Context, s Server) context.Context { func NewContext(ctx context.Context, s Server) context.Context {
if ctx == nil { if ctx == nil {

View File

@ -723,7 +723,7 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
return nil return nil
} }
opts.Hooks.EachPrev(func(hook options.Hook) { opts.Hooks.EachNext(func(hook options.Hook) {
if h, ok := hook.(HookSubHandler); ok { if h, ok := hook.(HookSubHandler); ok {
fn = h(fn) fn = h(fn)
} }

View File

@ -9,7 +9,6 @@ import (
"go.unistack.org/micro/v3/client" "go.unistack.org/micro/v3/client"
"go.unistack.org/micro/v3/codec" "go.unistack.org/micro/v3/codec"
"go.unistack.org/micro/v3/logger" "go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/server" "go.unistack.org/micro/v3/server"
) )
@ -85,40 +84,3 @@ func TestNoopSub(t *testing.T) {
} }
}() }()
} }
func TestHooks_Wrap(t *testing.T) {
n := 5
fn1 := func(next server.FuncSubHandler) server.FuncSubHandler {
return func(ctx context.Context, msg server.Message) (err error) {
n *= 2
return next(ctx, msg)
}
}
fn2 := func(next server.FuncSubHandler) server.FuncSubHandler {
return func(ctx context.Context, msg server.Message) (err error) {
n -= 10
return next(ctx, msg)
}
}
hs := &options.Hooks{}
hs.Append(server.HookSubHandler(fn1), server.HookSubHandler(fn2))
var fn = func(ctx context.Context, msg server.Message) error {
return nil
}
hs.EachPrev(func(hook options.Hook) {
if h, ok := hook.(server.HookSubHandler); ok {
fn = h(fn)
}
})
if err := fn(nil, nil); err != nil {
t.Fatal(err)
}
if n != 0 {
t.Fatalf("uncorrected hooks call")
}
}

View File

@ -100,7 +100,7 @@ func NewOptions(opts ...Option) Options {
Address: DefaultAddress, Address: DefaultAddress,
Name: DefaultName, Name: DefaultName,
Version: DefaultVersion, Version: DefaultVersion,
ID: id.MustNew(), ID: id.Must(),
Namespace: DefaultNamespace, Namespace: DefaultNamespace,
GracefulTimeout: DefaultGracefulTimeout, GracefulTimeout: DefaultGracefulTimeout,
} }

View File

@ -15,15 +15,6 @@ func FromContext(ctx context.Context) (Store, bool) {
return c, ok return c, ok
} }
// MustContext get store from context
func MustContext(ctx context.Context) Store {
s, ok := FromContext(ctx)
if !ok {
panic("missing store")
}
return s
}
// NewContext put store in context // NewContext put store in context
func NewContext(ctx context.Context, c Store) context.Context { func NewContext(ctx context.Context, c Store) context.Context {
if ctx == nil { if ctx == nil {

View File

@ -123,7 +123,7 @@ func (m *memoryStore) Init(opts ...store.Option) error {
m.funcList = m.fnList m.funcList = m.fnList
m.funcDelete = m.fnDelete m.funcDelete = m.fnDelete
m.opts.Hooks.EachPrev(func(hook options.Hook) { m.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) { switch h := hook.(type) {
case store.HookRead: case store.HookRead:
m.funcRead = h(m.funcRead) m.funcRead = h(m.funcRead)

View File

@ -54,7 +54,7 @@ func (n *noopStore) Init(opts ...Option) error {
n.funcList = n.fnList n.funcList = n.fnList
n.funcDelete = n.fnDelete n.funcDelete = n.fnDelete
n.opts.Hooks.EachPrev(func(hook options.Hook) { n.opts.Hooks.EachNext(func(hook options.Hook) {
switch h := hook.(type) { switch h := hook.(type) {
case HookRead: case HookRead:
n.funcRead = h(n.funcRead) n.funcRead = h(n.funcRead)

View File

@ -18,15 +18,6 @@ func FromContext(ctx context.Context) (Tracer, bool) {
return nil, false return nil, false
} }
// MustContext returns a tracer from context
func MustContext(ctx context.Context) Tracer {
t, ok := FromContext(ctx)
if !ok {
panic("missing tracer")
}
return t
}
// NewContext saves the tracer in the context // NewContext saves the tracer in the context
func NewContext(ctx context.Context, tracer Tracer) context.Context { func NewContext(ctx context.Context, tracer Tracer) context.Context {
if ctx == nil { if ctx == nil {
@ -37,15 +28,6 @@ func NewContext(ctx context.Context, tracer Tracer) context.Context {
type spanKey struct{} type spanKey struct{}
// SpanFromContext returns a span from context
func SpanMustContext(ctx context.Context) Span {
sp, ok := SpanFromContext(ctx)
if !ok {
panic("missing span")
}
return sp
}
// SpanFromContext returns a span from context // SpanFromContext returns a span from context
func SpanFromContext(ctx context.Context) (Span, bool) { func SpanFromContext(ctx context.Context) (Span, bool) {
if ctx == nil { if ctx == nil {

View File

@ -25,7 +25,6 @@ func (t *Tracer) Start(ctx context.Context, name string, opts ...tracer.SpanOpti
name: name, name: name,
ctx: ctx, ctx: ctx,
tracer: t, tracer: t,
labels: options.Labels,
kind: options.Kind, kind: options.Kind,
startTime: time.Now(), startTime: time.Now(),
} }
@ -38,14 +37,6 @@ func (t *Tracer) Start(ctx context.Context, name string, opts ...tracer.SpanOpti
return tracer.NewSpanContext(ctx, span), span return tracer.NewSpanContext(ctx, span), span
} }
type memoryStringer struct {
s string
}
func (s memoryStringer) String() string {
return s.s
}
func (t *Tracer) Flush(_ context.Context) error { func (t *Tracer) Flush(_ context.Context) error {
return nil return nil
} }
@ -61,6 +52,14 @@ func (t *Tracer) Name() string {
return t.opts.Name return t.opts.Name
} }
type noopStringer struct {
s string
}
func (s noopStringer) String() string {
return s.s
}
type Span struct { type Span struct {
ctx context.Context ctx context.Context
tracer tracer.Tracer tracer tracer.Tracer
@ -68,8 +67,8 @@ type Span struct {
statusMsg string statusMsg string
startTime time.Time startTime time.Time
finishTime time.Time finishTime time.Time
traceID memoryStringer traceID noopStringer
spanID memoryStringer spanID noopStringer
events []*Event events []*Event
labels []interface{} labels []interface{}
logs []interface{} logs []interface{}

View File

@ -2,7 +2,6 @@ package tracer
import ( import (
"context" "context"
"time"
"go.unistack.org/micro/v3/util/id" "go.unistack.org/micro/v3/util/id"
) )
@ -21,18 +20,18 @@ func (t *noopTracer) Spans() []Span {
func (t *noopTracer) Start(ctx context.Context, name string, opts ...SpanOption) (context.Context, Span) { func (t *noopTracer) Start(ctx context.Context, name string, opts ...SpanOption) (context.Context, Span) {
options := NewSpanOptions(opts...) options := NewSpanOptions(opts...)
span := &noopSpan{ span := &noopSpan{
name: name, name: name,
ctx: ctx, ctx: ctx,
tracer: t, tracer: t,
startTime: time.Now(), labels: options.Labels,
labels: options.Labels, kind: options.Kind,
kind: options.Kind,
} }
span.spanID.s, _ = id.New() span.spanID.s, _ = id.New()
span.traceID.s, _ = id.New() span.traceID.s, _ = id.New()
if span.ctx == nil { if span.ctx == nil {
span.ctx = context.Background() span.ctx = context.Background()
} }
t.spans = append(t.spans, span)
return NewSpanContext(ctx, span), span return NewSpanContext(ctx, span), span
} }
@ -59,18 +58,23 @@ func (t *noopTracer) Name() string {
return t.opts.Name return t.opts.Name
} }
type noopEvent struct {
name string
labels []interface{}
}
type noopSpan struct { type noopSpan struct {
ctx context.Context ctx context.Context
tracer Tracer tracer Tracer
name string name string
statusMsg string statusMsg string
startTime time.Time traceID noopStringer
finishTime time.Time spanID noopStringer
traceID noopStringer events []*noopEvent
spanID noopStringer labels []interface{}
labels []interface{} logs []interface{}
kind SpanKind kind SpanKind
status SpanStatus status SpanStatus
} }
func (s *noopSpan) TraceID() string { func (s *noopSpan) TraceID() string {
@ -82,7 +86,6 @@ func (s *noopSpan) SpanID() string {
} }
func (s *noopSpan) Finish(_ ...SpanOption) { func (s *noopSpan) Finish(_ ...SpanOption) {
s.finishTime = time.Now()
} }
func (s *noopSpan) Context() context.Context { func (s *noopSpan) Context() context.Context {
@ -94,6 +97,8 @@ func (s *noopSpan) Tracer() Tracer {
} }
func (s *noopSpan) AddEvent(name string, opts ...EventOption) { func (s *noopSpan) AddEvent(name string, opts ...EventOption) {
options := NewEventOptions(opts...)
s.events = append(s.events, &noopEvent{name: name, labels: options.Labels})
} }
func (s *noopSpan) SetName(name string) { func (s *noopSpan) SetName(name string) {
@ -101,6 +106,7 @@ func (s *noopSpan) SetName(name string) {
} }
func (s *noopSpan) AddLogs(kv ...interface{}) { func (s *noopSpan) AddLogs(kv ...interface{}) {
s.logs = append(s.logs, kv...)
} }
func (s *noopSpan) AddLabels(kv ...interface{}) { func (s *noopSpan) AddLabels(kv ...interface{}) {

22
util/id/LICENSE Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2018-2021 Matous Dzivjak <matousdzivjak@gmail.com>
Copyright (c) 2021 Unistack LLC <v.tolstov@unistack.org>
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.

View File

@ -1,154 +1,112 @@
package id package id
import ( import (
"context"
"crypto/rand" "crypto/rand"
"encoding/binary"
"errors" "errors"
"fmt" "math"
"time"
uuidv8 "github.com/ash3in/uuidv8" "go.unistack.org/micro/v3/logger"
nanoid "github.com/matoous/go-nanoid"
) )
var generatedNode [6]byte // DefaultAlphabet is the alphabet used for ID characters by default
var DefaultAlphabet = []rune("6789BCDFGHJKLMNPQRTWbcdfghjkmnpqrtwz")
func init() { // DefaultSize is the size used for ID by default
if _, err := rand.Read(generatedNode[:]); err != nil {
panic(err)
}
}
type Type int
const (
TypeUnspecified Type = iota
TypeNanoid
TypeUUIDv8
)
// DefaultNanoidAlphabet is the alphabet used for ID characters by default
var DefaultNanoidAlphabet = "6789BCDFGHJKLMNPQRTWbcdfghjkmnpqrtwz"
// DefaultNanoidSize is the size used for ID by default
// To get uuid like collision specify 21 // To get uuid like collision specify 21
var DefaultNanoidSize = 16 var DefaultSize = 16
type Generator struct { // getMask generates bit mask used to obtain bits from the random bytes that are used to get index of random character
opts Options // from the alphabet. Example: if the alphabet has 6 = (110)_2 characters it is sufficient to use mask 7 = (111)_2
} func getMask(alphabetSize int) int {
for i := 1; i <= 8; i++ {
func (g *Generator) MustNew() string { mask := (2 << uint(i)) - 1
id, err := g.New() if mask >= alphabetSize-1 {
if err != nil { return mask
panic(err) }
} }
return id return 0
}
func (g *Generator) New() (string, error) {
switch g.opts.Type {
case TypeNanoid:
if len(g.opts.NanoidAlphabet) == 0 || len(g.opts.NanoidAlphabet) > 255 {
return "", errors.New("invalid option, NanoidAlphabet must not be empty and contain no more than 255 chars")
}
if g.opts.NanoidSize <= 0 {
return "", errors.New("invalid option, NanoidSize must be positive integer")
}
return nanoid.Generate(g.opts.NanoidAlphabet, g.opts.NanoidSize)
case TypeUUIDv8:
timestamp := uint64(time.Now().UnixNano())
clockSeq := make([]byte, 2)
if _, err := rand.Read(clockSeq); err != nil {
return "", fmt.Errorf("failed to generate random clock sequence: %w", err)
}
clockSeqValue := binary.BigEndian.Uint16(clockSeq) & 0x0FFF // Mask to 12 bits
return uuidv8.NewWithParams(timestamp, clockSeqValue, g.opts.UUIDNode[:], uuidv8.TimestampBits48)
}
return "", errors.New("invalid option, Type unspecified")
} }
// New returns new id or error // New returns new id or error
func New(opts ...Option) (string, error) { func New(opts ...Option) (string, error) {
options := NewOptions(opts...) options := NewOptions(opts...)
switch options.Type { if len(options.Alphabet) == 0 || len(options.Alphabet) > 255 {
case TypeNanoid: return "", errors.New("alphabet must not be empty and contain no more than 255 chars")
if len(options.NanoidAlphabet) == 0 || len(options.NanoidAlphabet) > 255 { }
return "", errors.New("invalid option, NanoidAlphabet must not be empty and contain no more than 255 chars") if options.Size <= 0 {
} return "", errors.New("size must be positive integer")
if options.NanoidSize <= 0 {
return "", errors.New("invalid option, NanoidSize must be positive integer")
}
return nanoid.Generate(options.NanoidAlphabet, options.NanoidSize)
case TypeUUIDv8:
timestamp := uint64(time.Now().UnixNano())
clockSeq := make([]byte, 2)
if _, err := rand.Read(clockSeq); err != nil {
return "", fmt.Errorf("failed to generate random clock sequence: %w", err)
}
clockSeqValue := binary.BigEndian.Uint16(clockSeq) & 0x0FFF // Mask to 12 bits
return uuidv8.NewWithParams(timestamp, clockSeqValue, options.UUIDNode[:], uuidv8.TimestampBits48)
} }
return "", errors.New("invalid option, Type unspecified") chars := options.Alphabet
mask := getMask(len(chars))
// estimate how many random bytes we will need for the ID, we might actually need more but this is tradeoff
// between average case and worst case
ceilArg := 1.6 * float64(mask*options.Size) / float64(len(options.Alphabet))
step := int(math.Ceil(ceilArg))
id := make([]rune, options.Size)
bytes := make([]byte, step)
for j := 0; ; {
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
for i := 0; i < step; i++ {
currByte := bytes[i] & byte(mask)
if currByte < byte(len(chars)) {
id[j] = chars[currByte]
j++
if j == options.Size {
return string(id[:options.Size]), nil
}
}
}
}
} }
// Must is the same as New but fatals on error // Must is the same as New but fatals on error
func MustNew(opts ...Option) string { func Must(opts ...Option) string {
id, err := New(opts...) id, err := New(opts...)
if err != nil { if err != nil {
panic(err) logger.DefaultLogger.Fatal(context.TODO(), "Must call is failed", err)
} }
return id return id
} }
// Options contains id deneration options // Options contains id deneration options
type Options struct { type Options struct {
Type Type Alphabet []rune
NanoidAlphabet string Size int
NanoidSize int
UUIDNode [6]byte
} }
// Option func signature // Option func signature
type Option func(*Options) type Option func(*Options)
// WithNanoidAlphabet specifies alphabet to use // Alphabet specifies alphabet to use
func WithNanoidAlphabet(alphabet string) Option { func Alphabet(alphabet string) Option {
return func(o *Options) { return func(o *Options) {
o.NanoidAlphabet = alphabet o.Alphabet = []rune(alphabet)
} }
} }
// WithNanoidSize specifies generated id size // Size specifies id size
func WithNanoidSize(size int) Option { func Size(size int) Option {
return func(o *Options) { return func(o *Options) {
o.NanoidSize = size o.Size = size
}
}
// WithUUIDNode specifies node component for UUIDv8
func WithUUIDNode(node [6]byte) Option {
return func(o *Options) {
o.UUIDNode = node
} }
} }
// NewOptions returns new Options struct filled by opts // NewOptions returns new Options struct filled by opts
func NewOptions(opts ...Option) Options { func NewOptions(opts ...Option) Options {
options := Options{ options := Options{
Type: TypeUUIDv8, Alphabet: DefaultAlphabet,
NanoidAlphabet: DefaultNanoidAlphabet, Size: DefaultSize,
NanoidSize: DefaultNanoidSize,
UUIDNode: generatedNode,
} }
for _, o := range opts { for _, o := range opts {
o(&options) o(&options)
} }
return options return options
} }

View File

@ -1,11 +0,0 @@
package id
import "testing"
func TestUUIDv8(t *testing.T) {
id, err := New()
if err != nil {
t.Fatal(err)
}
t.Logf("xxx %s\n", id)
}

View File

@ -113,7 +113,7 @@ func (b *Buffer) Stream() (<-chan *Entry, chan bool) {
defer b.Unlock() defer b.Unlock()
entries := make(chan *Entry, 128) entries := make(chan *Entry, 128)
id := id.MustNew() id := id.Must()
stop := make(chan bool) stop := make(chan bool)
b.streams[id] = &Stream{ b.streams[id] = &Stream{